web-dev-qa-db-fra.com

Rails 3 ActiveRecord: ordre par compte sur l'association

J'ai un modèle nommé Song. J'ai également un modèle nommé Listen. A Listenbelongs_to :song, et une chanson :has_many listens (peut être écouté plusieurs fois).

Dans mon modèle, je veux définir une méthode self.top qui devrait renvoyer le top 5 des chansons les plus écoutées. Comment puis-je y parvenir en utilisant le has_many relation?

J'utilise Rails 3.1.

Merci!

59
Christoffer Reijer

Utilisation de étendues nommées :

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
93
clyfe

Encore mieux, utilisez counter_cache qui sera plus rapide car vous ne le ferez que parce que vous utilisez une table dans votre requête

Voici votre cours de chanson:

class Song < ActiveRecord::Base
  has_many :listens

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

Ensuite, votre classe d'écoute:

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

Assurez-vous d'ajouter une migration:

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

Points bonus, ajoutez un test:

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

Ou, si vous souhaitez utiliser la méthode ci-dessus, la voici un peu plus simplement, et en utilisant une méthode de classe plutôt que scope

def self.top
    select('comments.*, COUNT(listens.id) AS listens_count').
      joins(:listens).                                                   
      group('comments.id').
      order('listens_count DESC').
      limit(5)
end
32
Neal

Pour Rails 4.x, essayez ceci si vos lignes sans association sont importantes:

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")
  }
0
Bruno Casali