【问题标题】:Rails 3 ActiveRecord: Order by count on associationRails 3 ActiveRecord:按关联排序
【发布时间】:2012-01-01 22:29:46
【问题描述】:

我有一个名为 Song 的模型。我还有一个名为Listen 的模型。一个Listenbelongs_to :song,还有一首歌:has_many listens(可以多听)。

在我的模型中,我想定义一个方法self.top,它应该返回听得最多的前 5 首歌曲。如何使用has_many 关系实现这一点?

我正在使用 Rails 3.1。

谢谢!

【问题讨论】:

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


    【解决方案1】:

    使用named scopes

    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
    

    【讨论】:

    • 当然,从范围定义中删除限制语句,然后在控制器调用中像这样:Song.where(:user_id => current_user.id).top.limit(5)
    • 谢谢。我必须在连接和订单之间添加组(“songs.id”)。 :)
    • 我猜你正在使用 MySQL。在 Postgres 中,您需要聚合所有属性: .group("listens.id,songs.#{Song.column_names.join(",songs.")}")
    • 有没有办法通过只计算过去 24 小时内创建的监听来扩展这个范围?
    • 好吧,我想我找到了解决办法: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')这样对吗?
    【解决方案2】:

    更好的是,使用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 的信息。以前没见过。 :) 但这对我不起作用,因为我还需要只包含属于特定用户的监听,这意味着我遇到了一些更复杂的查询。
    • 您是在选择的末尾忘记了一个点还是不需要它?
    • 迁移中的意思是“listens_count”而不是“likes_count”吗?
    • 尼尔,我有一个类似的问题,它有点复杂,所以我无法弄清楚。你能在这里看看吗:stackoverflow.com/questions/33929227/…
    【解决方案3】:

    对于 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")
      }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-20
      相关资源
      最近更新 更多