2010-09-27 9 views
2

J'ai un système de vote avec deux modèles: Item (id, name) et Vote (id, item_id, user_id).Aide à l'optimisation de la requête ActiveRecord (système de vote)

Voici le code que j'ai jusqu'à présent:

class Item < ActiveRecord::Base 
    has_many :votes 

    def self.most_popular 
    items = Item.all #where can I optimize here? 
    items.sort {|x,y| x.votes.length <=> y.votes.length}.first #so I don't need to do anything here? 
    end 
end 

Il y a quelques choses de mal à cela, surtout que je récupère tous les enregistrements d'article, utilisez Ruby pour calculer la popularité. Je suis presque certain qu'il existe une solution simple à cela, mais je ne peux pas vraiment mettre le doigt dessus.

Je préférerais de loin rassembler des enregistrements et exécuter les calculs dans la requête initiale. De cette façon, je peux ajouter un simple :limit => 1 (ou LIMIT 1) à la requête.

Toute aide serait géniale - soit réécrire dans tout ActiveRecord ou même dans SQl brut. Ce dernier me donnerait une image beaucoup plus claire de la nature de la requête que je veux exécuter.

Répondre

3

groupe voix par id article, commandez-les en nombre et prenez ensuite l'objet du premier. Dans rails 3 le code pour cela est:

Vote.group(:item_id).order("count(*) DESC").first.item 

dans des rails 2, cela devrait fonctionner:

Vote.all(:order => "count(*) DESC", :group => :item_id).first.item 
+1

Magnifique! Voici la syntaxe Rails 2.x que j'ai dérivé de ceci: Vote.find (: all,: groupe => "item_id",: order => "count (*) DESC",: limite => 1) .first.item – user94154

+0

N'oubliez pas: include =>: item (voir ma réponse) ou vous ferez une requête supplémentaire lorsque vous appelez l'item Vote #. En combinant aussi: limite => 1 et Array # semble d'abord redondant quand vous pouvez simplement appeler Vote # en premier. –

0

probablement il y a une meilleure façon de le faire en ruby, mais dans SQL (MySQL au moins), vous pouvez essayer quelque chose comme ça pour obtenir un classement top 10:

SELECT i.id, i.name, COUNT(v.id) AS total_votes 
FROM Item i 
LEFT JOIN Vote v ON (i.id = v.item_id) 
GROUP BY i.id 
ORDER BY total_votes DESC 
LIMIT 10 
0

Un moyen facile de manipuler est d'ajouter un champ de décompte des voix à l'article, et mise à jour que chaque fois qu'il y a un vote. Rails faisait ça automatiquement pour vous, mais je ne sais pas si c'est toujours le cas dans 2.x et 3.0. Il est assez facile de le faire dans tous les cas en utilisant un pattern Observer ou bien simplement en mettant un "after_save" dans le modèle Vote.

Ensuite, votre requête est très simple, en ajoutant simplement un ordre "VOTE_COUNT DESC" à votre requête.

+0

Merci, mais je ne veux pas faire face à des problèmes d'intégrité des données. Je préfère rester avec "une version de la vérité" sur celui-ci. Peut-être que je vais mettre en œuvre quelque chose comme ça si la performance/mise à l'échelle devient un problème. – user94154

1

sepp2k a la bonne idée. Dans le cas où vous n'utilisez pas Rails 3, l'équivalent est:

Vote.first(:group => :item_id, :order => "count(*) DESC", :include => :item).item