J'ai un modèle nommé Song
. J'ai également un modèle nommé Listen
. A Listen
belongs_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!
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
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
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")
}