web-dev-qa-db-fra.com

Comment afficher des enregistrements uniques d'une relation has_many via?

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

100
Andy Harvey

Avez-vous essayé de spécifier l'option: uniq sur l'association has_many:

has_many :products, :through => :orders, :uniq => true

De la documentation Rails :

: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.

224
mbreining

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

41
Yoshiki

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é.

5
Josh Kovach