Rails:上一个查询的上一个和下一个记录

发布于 2024-10-27 06:29:22 字数 610 浏览 1 评论 0原文

我的应用程序有照片,用户可以搜索符合特定条件的照片。假设用户按标签搜索照片,我们会得到如下结果:

@results = Photo.tagged_with('mountain')

现在,@results 将成为具有多条记录的标准 activerecord 查询。这些将显示在网格中,然后用户可以单击照片。这会将用户带到 photos#show 操作。

因此,假设用户搜索某些内容,应用程序找到 5 条记录,[1,2,3,4,5],并且用户单击照片 #3。

photo#show 页面上,我希望能够显示“下一张照片”、“上一张照片”和“返回搜索”。

唯一的其他限制是,如果用户直接浏览到照片(通过另一页或书签等),则不会有逻辑上的“下一张”和“上一张”照片,因为没有查询引导他们找到该照片照片,因此在这种情况下,模板根本不应该呈现与查询相关的内容。

所以,我一直在思考这种事情该怎么做,还真没有太多好的想法。我想我可以做一些事情,比如将查询存储在会话中以便能够返回它,但我不知道如何找到将显示在所选照片左侧和右侧的照片。

有谁有如何做这种事情的例子吗?

My app has photos, and users can search for photos that meet certain criteria. Let's say a user searches for photos by tag, and we get something like this:

@results = Photo.tagged_with('mountain')

Now, @results is going to be a standard activerecord query with multiple records. These would be shown in a grid, and a user can then click on a photo. This would take the users to the photos#show action.

So, lets say the user searches for something and the app finds 5 records, [1,2,3,4,5], and the user clicks on photo #3.

On the photo#show page I'd like to be able to show a "Next Photo", "Previous Photo", and "Back to Search".

The only other constraint is, if the user browses to a photo directly (via another page or a bookmark etc) there wouldn't be a logical "next" and "previous" photo since there wasn't a query that led them to that photo, so in that case the template shouldn't render the query-related content at all.

So, I have been thinking about how to do this kind of thing and I don't really have a lot of good ideas. I suppose I could do something like store the query in session to be able to go back to it, but I don't know how to find the photos that would have shown up to the left and right of the selected photo.

Does anyone have any examples of how to do this kind of thing?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

花桑 2024-11-03 06:29:22

因此,经过多次尝试和错误,我得出了以下结论:

在我的照片模型中:

# NEXT / PREVIOUS FUNCTIONALITY
def previous(query)
  unless query.nil?
    index = query.find_index(self.id)
    prev_id = query[index-1] unless index.zero?
    self.class.find_by_id(prev_id)
  end
end

def next(query)
  unless query.nil?
    index = query.find_index(self.id)
    next_id = query[index+1] unless index == query.size
    self.class.find_by_id(next_id)
  end
end

此方法通过接受这些记录 id 的数组,从搜索或特定文件夹视图中返回下一条和上一条记录。我在创建查询视图的任何控制器视图(即搜索页面和按文件夹浏览页面)中生成该 ID:

例如,我的搜索控制器包含:

def search
  @search = @collection.photos.search(params[:search])
  @photos = @search.page(params[:page]).per(20)
  session[:query] = @photos.map(&:id)
end

然后 photo#show 操作包含:

if session[:query]
  @next_photo = @photo.next(session[:query])
  @prev_photo = @photo.previous(session[:query])
end

最后,我的视图包含:

- if @prev_photo || @next_photo
  #navigation
    .header Related Photos
    .prev
      = link_to image_tag( @prev_photo.file.url :tenth ), collection_photo_path(@collection, @prev_photo) if @prev_photo
      - if @prev_photo
        %span Previous
    .next
      = link_to image_tag( @next_photo.file.url :tenth ), collection_photo_path(@collection, @next_photo) if @next_photo
      - if @next_photo
        %span Next

现在事实证明,这在常规浏览情况下效果很好 - 但有一个我尚未修复的问题:

理论上,如果用户搜索视图,然后跳转到他们'的照片已经在会话中生成了一个查询。如果出于某种原因,他们随后直接浏览(通过 URL 或书签)到属于上一个查询的另一张照片,则该查询将在会话中持续存在,并且相关照片链接在第二张照片上仍然可见 - 即使它们不应该出现在某人通过书签加载的照片上。

然而,在现实生活中的用例中,这种情况实际上很难重现,而且代码目前运行得很好。在某个时候,当我想出一个很好的解决方案来解决剩下的问题时,我会将其发布,但现在如果有人使用这个想法,请注意这种可能性的存在。

So, after much trial and error, here is what I came up with:

In my Photo model:

# NEXT / PREVIOUS FUNCTIONALITY
def previous(query)
  unless query.nil?
    index = query.find_index(self.id)
    prev_id = query[index-1] unless index.zero?
    self.class.find_by_id(prev_id)
  end
end

def next(query)
  unless query.nil?
    index = query.find_index(self.id)
    next_id = query[index+1] unless index == query.size
    self.class.find_by_id(next_id)
  end
end

This method returns the next and previous record from a search or a particular folder view by accepting an array of those records ids. I generate that ID in any controller view that creates a query view (ie the search page and the browse by folders page):

So, for instance, my search controller contains:

def search
  @search = @collection.photos.search(params[:search])
  @photos = @search.page(params[:page]).per(20)
  session[:query] = @photos.map(&:id)
end

And then the photo#show action contains:

if session[:query]
  @next_photo = @photo.next(session[:query])
  @prev_photo = @photo.previous(session[:query])
end

And lastly, my view contains:

- if @prev_photo || @next_photo
  #navigation
    .header Related Photos
    .prev
      = link_to image_tag( @prev_photo.file.url :tenth ), collection_photo_path(@collection, @prev_photo) if @prev_photo
      - if @prev_photo
        %span Previous
    .next
      = link_to image_tag( @next_photo.file.url :tenth ), collection_photo_path(@collection, @next_photo) if @next_photo
      - if @next_photo
        %span Next

Now it turns out this works great in regular browsing situations -- but there is one gotcha that I have not yet fixed:

Theoretically, if a user searches a view, then jumps to a photo they've generated a query in session. If, for some reason, they then browse directly (via URL or bookmark) to another photo that was part of the previous query, the query will persist in session and the related photos links will still be visible on the second photo -- even though they shouldn't be on a photo someone loaded via bookmark.

However, in real life use cases this situation has actually been pretty difficult to recreate, and the code is working very well for the moment. At some point when I come up with a good fix for that one remaining gotcha I'll post it, but for now if anyone uses this idea just be aware that possibility exists.

自在安然 2024-11-03 06:29:22

安德鲁,你的方法不通用,不能保证正确的结果。有更好的方法来做到这一点。
在您的模型中:

def previous
  Photo.where('photos.id < ?', self.id).first
end

def next
  Photo.where('photos.id > ?', self.id).last
end

在视图中:

- if @photo.previous
  = link_to 'Previous', @photo.previous
- if @photo.next
  = link_to 'Next', @photo.next

Andrew, your method not universal and dont give guaranteed right result. There is better way to do this.
In your model:

def previous
  Photo.where('photos.id < ?', self.id).first
end

def next
  Photo.where('photos.id > ?', self.id).last
end

And in views:

- if @photo.previous
  = link_to 'Previous', @photo.previous
- if @photo.next
  = link_to 'Next', @photo.next
眼泪也成诗 2024-11-03 06:29:22

我写的一个名为 Nexter 的 gem 可以为你做到这一点。

您向其传递 AR 范围组合(又名 ActiveRelation)加上当前对象/记录,Nexter 将检查订单子句来构建将获取之前/上一个和之后/下一个记录的 SQL。

基本上,它按 order(a, b, c) 查看 ActiveRelation#order_values 并得出:

# pseudo code
where(a = value_of a AND b = value of b AND c > value of c).or
where(a = value_of a AND b > value of b).or
where(a > value of a)

这只是它的要点。它还可以处理关联值,并且可以巧妙地找到前一部分的逆值。要保持搜索(或范围组合)的状态,您可以使用另一个库,例如 siphon、ransack、has_scope等等......

中的工作示例

模型:

class Book
  def nexter=(relation)
    @nexter = Nexter.wrap(relation, self)
  end

  def next
    @nexter.next
  end

  def previous
    @nexter.previous
  end
end

这是自述文件 控制器

class BookController
  before_filter :resource, except: :index

  def resource
    @book_search = BookSearch.new(params[:book_search])

    @book ||= Book.includes([:author]).find(params[:id]).tap do |book|
      book.nexter = siphon(Book.scoped).scope(@book_search)
    end
  end
end

视图:

<%= link_to "previous", book_path(@book.previous, book_search: params[:book_search]) %>
<%= link_to "collection", book_path(book_search: params[:book_search]) %>
<%= link_to "next", book_path(@book.next, book_search: params[:book_search])
```

A gem I wrote called Nexter does it for you.

You pass it an AR Scope combination (aka ActiveRelation) plus the current Object/Record and Nexter will inspect the order clause to build the sql that will fetch the before/previous and after/next records.

Basically it looks at the ActiveRelation#order_values in order(a, b, c) and comes out with :

# pseudo code
where(a = value_of a AND b = value of b AND c > value of c).or
where(a = value_of a AND b > value of b).or
where(a > value of a)

That's only the gist of it. It also works with association values and is clever with finding the inverse values for the previous part. To keep the state of your search (or scope combination) you can use another lib like siphon, ransack, has_scope etc...

Here's a working example from the README

The model :

class Book
  def nexter=(relation)
    @nexter = Nexter.wrap(relation, self)
  end

  def next
    @nexter.next
  end

  def previous
    @nexter.previous
  end
end

The controller

class BookController
  before_filter :resource, except: :index

  def resource
    @book_search = BookSearch.new(params[:book_search])

    @book ||= Book.includes([:author]).find(params[:id]).tap do |book|
      book.nexter = siphon(Book.scoped).scope(@book_search)
    end
  end
end

The view :

<%= link_to "previous", book_path(@book.previous, book_search: params[:book_search]) %>
<%= link_to "collection", book_path(book_search: params[:book_search]) %>
<%= link_to "next", book_path(@book.next, book_search: params[:book_search])
```
飘落散花 2024-11-03 06:29:22

您可以看看 ActsAsAdjacent 中所做的事情:

named_scope :previous, lambda { |i| {:conditions => ["#{self.table_name}.id < ?", i.id], :order => "#{self.table_name}.id DESC"} }
named_scope :next, lambda { |i| {:conditions => ["#{self.table_name}.id > ?", i.id], :order => "#{self.table_name}.id ASC"} }

本质上,它们是作用域(Rails 3 之前的语法),用于检索 ID 小于/大于您传入的记录 ID 的记录。

由于它们是作用域,因此您可以将 previous 与 .first 链接起来,以获取在当前项目,并 .tagged_with('mountain').first 获取第一个标有 'mountain' 的此类项目。

You could take a look at what done in ActsAsAdjacent:

named_scope :previous, lambda { |i| {:conditions => ["#{self.table_name}.id < ?", i.id], :order => "#{self.table_name}.id DESC"} }
named_scope :next, lambda { |i| {:conditions => ["#{self.table_name}.id > ?", i.id], :order => "#{self.table_name}.id ASC"} }

Essentially, they're scopes(pre Rails 3 syntax) to retrieve records that have IDs lesser/greater than the ID of the record you passed in.

Since they're scopes, you can chain previous with .first to get the first item created before the current item, and .tagged_with('mountain').first to get the first such item tagged with 'mountain'.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文