【问题标题】:Rails: Previous and Next record from previous queryRails:上一个查询的上一个和下一个记录
【发布时间】:2011-07-24 02:26:27
【问题描述】:

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

@results = Photo.tagged_with('mountain')

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

假设用户搜索某物,应用找到 5 条记录,[1,2,3,4,5],然后用户点击了照片 #3。

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

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

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

有没有人有任何例子说明如何做这种事情?

【问题讨论】:

标签: ruby-on-rails ruby-on-rails-3 activerecord views


【解决方案1】:

所以,经过多次尝试和错误,这是我想出的:

在我的照片模型中:

# 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 或书签)到之前查询中的另一张照片,则该查询将在会话中持续存在,并且相关照片链接仍将在第二张照片上可见——即使它们不应该出现在某人通过书签加载的照片上。

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

【讨论】:

  • 您可以通过将持久查询的 id 放在下一个/上一个链接和来自搜索页面的链接的 url 中来解决您的 gotcha。这样,如果您看到 ID 并且它与最后一个持久查询匹配,则显示下一个/上一个按钮。如果由于用户键入 URL 或以其他方式访问页面而导致 ID 不存在,请不要显示按钮。
  • @Brian:哦!这是一个很酷的想法,我会努力实现类似的东西!
  • 非常感谢您发布这个,它真的很有用。
  • 如果您遍历受@search.page(params[:page]).per(20) 限制的给定照片数量,您会做什么?
  • 老实说这不是问题。我的应用程序中下一个/上一个的目的基本上是查看附近满足给定标准的相邻记录,但通常不会很多。但是,只需颠倒分配顺序是有意义的:将搜索中的照片地图存储在会话中,然后对其进行分页。
【解决方案2】:

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

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

【讨论】:

  • 做我想做的事。我不只是想要下一张照片,我想要来自特定用户最近搜索结果的下一张照片。我确信我的方法并不完美,并且我列出了我最大的已知“陷阱”,但它确实可以在我的应用程序中根据需要工作。
  • 它没有达到他想要的效果,但对我来说是完美的:)
  • 对我来说,使用 Rails 4 并不能正常工作。你的“下一个”给我“最后一个”,“上一个”给我“第一个”。
  • 与 Rails 4 无关,该代码应该这样做。将first 替换为last,反之亦然,您应该会得到想要的结果。
【解决方案3】:

我写的一个名为 Nexter 的 gem 可以为你做这件事。

您将 AR Scope 组合(也称为 ActiveRelation)和当前对象/记录传递给它,Nexter 将检查 order 子句以构建将获取之前/上一个和之后/下一个记录的 sql。

基本上,它按顺序(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 等...

这是自述文件中的working example

型号:

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])
```

【讨论】:

    【解决方案4】:

    你可以看看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' 标记的此类项目。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-24
      • 2013-07-29
      相关资源
      最近更新 更多