Je me demande quelle est la meilleure façon d'afficher des enregistrements uniques à partir d'un has_many, via une relation dans Rails3.
J'ai trois modèles:
class User < ActiveRecord::Base
has_many :orders
has_many :products, :through => :orders
end
class Products < ActiveRecord::Base
has_many :orders
has_many :users, :through => :orders
end
class Order < ActiveRecord::Base
belongs_to :user, :counter_cache => true
belongs_to :product, :counter_cache => true
end
Disons que je veux répertorier tous les produits qu'un client a commandés sur sa page d'exposition.
Ils ont peut-être commandé plusieurs produits plusieurs fois, donc j'utilise counter_cache pour les afficher dans l'ordre de classement décroissant, en fonction du nombre de commandes.
Mais, s'ils ont commandé un produit plusieurs fois, je dois m'assurer que chaque produit n'est répertorié qu'une seule fois.
@products = @user.products.ranked(:limit => 10).uniq!
fonctionne lorsqu'il existe plusieurs enregistrements de commande pour un produit, mais génère une erreur si un produit n'a été commandé qu'une seule fois. (classé est une fonction de tri personnalisée définie ailleurs)
Une autre alternative est:
@products = @user.products.ranked(:limit => 10, :select => "DISTINCT(ID)")
Je ne suis pas sûr d'être sur la bonne approche ici.
Quelqu'un d'autre s'est-il attaqué à cela? Quels problèmes avez-vous rencontrés? Où puis-je en savoir plus sur la différence entre .unique! et DISTINCT ()?
Quelle est la meilleure façon de générer une liste d'enregistrements uniques via une relation has_many, through?
Merci
Avez-vous essayé de spécifier l'option: uniq sur l'association has_many:
has_many :products, :through => :orders, :uniq => true
:uniq
Si vrai, les doublons seront omis de la collection. Utile en conjonction avec: à travers.
MISE À JOUR POUR Rails 4:
Dans Rails 4, has_many :products, :through => :orders, :uniq => true
est obsolète. Au lieu de cela, vous devez maintenant écrire has_many :products, -> { distinct }, through: :orders
. Voir section distincte pour has_many:: via les relations dans la documentation des associations ActiveRecord pour plus d'informations. Merci à Kurt Mueller de l'avoir signalé dans son commentaire.
Notez que uniq: true
a été supprimé des options valides pour has_many
à partir de Rails 4.
Dans Rails 4, vous devez fournir une étendue pour configurer ce type de comportement. Les étendues peuvent être fournies via lambdas, comme ceci:
has_many :products, -> { uniq }, :through => :orders
Le guide Rails couvre cet aspect et d'autres façons d'utiliser les étendues pour filtrer les requêtes de votre relation, faites défiler jusqu'à la section 4.3.3:
http://guides.rubyonrails.org/association_basics.html#has-many-association-reference
Vous pouvez utiliser group_by
. Par exemple, j'ai un panier de galerie de photos pour lequel je souhaite que les articles soient triés par quelle photo (chaque photo peut être commandée plusieurs fois et en tirages de différentes tailles). Cela renvoie ensuite un hachage avec le produit (photo) comme clé et chaque fois qu'il a été commandé peut être répertorié dans le contexte de la photo (ou non). En utilisant cette technique, vous pouvez réellement afficher un historique des commandes pour chaque produit donné. Je ne sais pas si cela vous est utile dans ce contexte, mais je l'ai trouvé très utile. Voici le code
OrdersController#show
@order = Order.find(params[:id])
@order_items_by_photo = @order.order_items.group_by(&:photo)
@order_items_by_photo
ressemble alors à ceci:
=> {#<Photo id: 128>=>[#<OrderItem id: 2, photo_id: 128>, #<OrderItem id: 19, photo_id: 128>]
Vous pouvez donc faire quelque chose comme:
@orders_by_product = @user.orders.group_by(&:product)
Ensuite, lorsque vous obtenez cela dans votre vue, parcourez simplement quelque chose comme ceci:
- for product, orders in @user.orders_by_product
- "#{product.name}: #{orders.size}"
- for order in orders
- output_order_details
De cette façon, vous évitez le problème rencontré lors du retour d'un seul produit, car vous savez toujours qu'il renverra un hachage avec un produit comme clé et un tableau de vos commandes.
Cela peut être exagéré pour ce que vous essayez de faire, mais cela vous donne de belles options (c'est-à-dire des dates commandées, etc.) avec lesquelles travailler en plus de la quantité.