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 lister tous les produits qu'un client a commandés sur sa page de présentation.
Ils ont peut-être commandé certains produits plusieurs fois, j'utilise donc counter_cache pour les afficher par ordre 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. (le classement 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 convaincu que je suis 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
la source
has_many :products, :through => :orders, :uniq => true
est obsolète. Au lieu de cela, vous devriez maintenant écrirehas_many :products, -> { uniq }, through: :orders
.DISTINCT
etORDER BY
, vous pouvez toujours utiliserhas_many :products, -> { unscope(:order).distinct }, through: :orders
has_many :subscribed_locations, -> { unscope(:order).select("DISTINCT ON (locations.id) locations.*") },through: :people_publication_subscription_locations, class_name: 'Location', source: :location
sinon vous obtenezrails ActiveRecord::StatementInvalid: PG::UndefinedFunction: ERROR: could not identify an equality operator for type json
Notez que cela
uniq: true
a été supprimé des options valides pour àhas_many
partir de Rails 4.Dans Rails 4, vous devez fournir une portée pour configurer ce type de comportement. Les portées peuvent être fournies via lambdas, comme ceci:
Le guide des rails couvre ceci 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
la source
undefined method 'except' for #<Array...
et c'était parce que j'ai utilisé à la.uniq
place de.distinct
Vous pourriez utiliser
group_by
. Par exemple, j'ai un panier de galerie de photos pour lequel je souhaite que les articles de commande soient triés par photo (chaque photo peut être commandée plusieurs fois et dans des impressions de tailles différentes). 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 en fait générer 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@order_items_by_photo
alors ressemble à quelque chose comme ceci:Vous pouvez donc faire quelque chose comme:
Ensuite, lorsque vous obtenez ceci dans votre vue, parcourez simplement quelque chose comme ceci:
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 (par exemple, les dates commandées, etc.) avec lesquelles travailler en plus de la quantité.
la source
Sur Rails 6, cela fonctionne parfaitement:
Je n'ai pas réussi à faire fonctionner les autres réponses.
la source