Rails 3 ActiveRecord:按关联计数排序

60
我有一个名为“Song”的模型。我还有一个名为“Listen”的模型。一个“Listen”属于一个“Song”,而一个歌曲可以有多个“listens”(可以被听多次)。
在我的模型中,我想定义一个名为“self.top”的方法,它应该返回最常被收听的前5首歌曲。如何使用“has_many”关系来实现这一点?
我正在使用Rails 3.1。
谢谢!
3个回答

96

使用命名作用域

class Song
  has_many :listens
  scope :top5,
    select("songs.id, OTHER_ATTRS_YOU_NEED, count(listens.id) AS listens_count").
    joins(:listens).
    group("songs.id").
    order("listens_count DESC").
    limit(5)

Song.top5 # top 5 most listened songs

5
当然,从作用域定义中删除限制语句,然后在控制器中使用以下方式调用:Song.where(:user_id => current_user.id).top.limit(5) - clyfe
11
谢谢。我需要在连接和排序之间添加group("songs.id")。 :) - Christoffer Reijer
11
我猜你正在使用MySQL。在Postgres中,你需要聚合所有属性:.group("listens.id, songs.#{Song.column_names.join(",songs.")}") - EricLarch
1
有没有办法通过仅计算在过去24小时内创建的收听来扩展此范围? - Daniel
1
好的,我想我找到了一个解决方案:Work.select('works.name, count(impressions.id) AS impressions_count').joins(:impressions).where('impressions.created_at >= ?', Time.now - 1.day).group('works.name').order('impressions_count DESC') 这个正确吗? - Daniel
显示剩余5条评论

33

更好的方式是使用counter_cache,因为您在查询中只使用一个表格,所以速度会更快。

这是您的歌曲类:

class Song < ActiveRecord::Base
  has_many :listens

  def self.top
    order('listens_count DESC').limit(5)
  end
end

然后,你的听力课:

class Listen < ActiveRecord::Base
  belongs_to :song, counter_cache: true
end

请确保添加一个迁移:

add_column :comments, :likes_count, :integer, default: 0

加分项,添加测试:

describe '.top' do
  it 'shows most listened songs first' do
    song_one = create(:song)
    song_three = create(:song, listens_count: 3)
    song_two = create(:song, listens_count: 2)

    popular_songs = Song.top

    expect(popular_songs).to eq [song_three, song_two, song_one]
  end
end
或者,如果你想使用上述方法,这里有一个更加简单的版本,使用一个类方法而不是 scope
def self.top
    select('comments.*, COUNT(listens.id) AS listens_count').
      joins(:listens).                                                   
      group('comments.id').
      order('listens_count DESC').
      limit(5)
end

谢谢你告诉我关于counter_cache的事情。以前没见过。:) 但是对我来说不起作用,因为我还需要仅包括属于特定用户的收听记录,这意味着我被困在一些更复杂的查询中。 - Christoffer Reijer
你是不是忘了在SELECT语句的末尾加上一个点,还是说不需要加呢? - lonewarrior556
7
在迁移中,你是不是想用 "listens_count" 而不是 "likes_count"? - Wemmick
Neal,我有一个类似的问题,但它更加复杂,所以我无法解决。你能在这里看一下吗:http://stackoverflow.com/questions/33929227/rails-complex-order-by-with-argument - Sean Magyar

0

对于Rails 4.x,如果您的行没有任何关联,请尝试以下方法:

scope :order_by_my_association, lambda {
    select('comments.*, COUNT(listens.id) AS listens_total')
    .joins("LEFT OUTER JOIN listens ON listens.comment_id = comments.id")
    .group('comments.id')
    .order("listens_total DESC")
  }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接