Rails 4 vous permet de définir une relation has_many
comme suit:
class Customer < ActiveRecord::Base
has_many :orders, -> { where processed: true }
end
Donc, chaque fois que vous faites customer.orders
, vous ne recevez que des commandes traitées.
Mais que se passe-t-il si je dois dynamiser la condition where
? Comment puis-je passer un argument à la portée lambda?
Par exemple, je souhaite uniquement que les commandes apparaissent pour le compte auquel le client est actuellement connecté dans un environnement multi-locataire.
Voici ce que j'ai
class Customer < ActiveRecord::Base
has_many :orders, (account) { where(:account_id => account.id) }
end
Mais comment puis-je, dans mon contrôleur ou ma vue, passer le bon compte? Avec le code ci-dessus en place quand je le fais:
customers.orders
Je reçois toutes les commandes pour un compte avec un identifiant égal à 1, apparemment arbitrairement.
Pour ce faire, vous devez définir un sélecteur extensible supplémentaire dans la portée has_many
:
class Customer < ActiveRecord::Base
has_many :orders do
def by_account(account)
# use `self` here to access to current `Customer` record
where(:account_id => account.id)
end
end
end
customers.orders.by_account(account)
L'approche est décrite dans Association Extension
head dans Rails Association
page.
Pour accéder à l'enregistrement Customer
dans la méthode imbriquée, vous ne pouvez accéder qu'à l'objet self
, il doit avoir la valeur de l'enregistrement Customer
actuel.
Sinse of Rails (environ 5.1) vous permet de fusionner la portée de modèles avec la même portée de modèles has_many, par exemple, vous pouvez écrire le même code de la manière suivante dans les deux modèles:
class Customer < ApplicationRecord
has_many :orders
end
class Order < ApplicationRecord
scope :by_account, ->(account) { where(account_id: account.id) }
end
customers.orders.by_account(account)
Vous passez dans une instance de la classe que vous avez définie. Dans votre cas, vous transmettriez un client et vous obtiendriez le compte.
De l'API http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
Il est parfois utile d'avoir accès à l'objet propriétaire lors de la construction de la requête. Le propriétaire est transmis en tant que paramètre au bloc. Par exemple, l'association suivante trouverait tous les événements qui se produisent à la date de naissance de l'utilisateur:
class User < ActiveRecord::Base
has_many :birthday_events, ->(user) { where starts_on: user.birthday },
class_name: 'Event'
end
Dans votre exemple, ce serait:
class Customer < ActiveRecord::Base
has_many :orders, ->(customer) { where(account_id: customer.account.id) }
end
Je sais que c'est vieux, mais comme aucune réponse n'a encore été acceptée, je pensais que l'ajout de mon point de vue sur ce point ne ferait de mal à personne.
Le problème est que, chaque fois que vous transmettez une étendue à une relation has_many
, transmettre l'instance de la classe owner en tant qu'argument est non seulement une possibilité, mais il ne s'agit que de la possibilité uniquement de passer un argument. Je veux dire, vous n'êtes pas autorisé à passer plus d'arguments, et celui-ci toujours sera l'instance de la classe owner.
Alors @RobSobers, quand vous
"obtenir tous les ordres pour compte avec un identifiant égal à 1, apparemment arbitraire."
ce n'est pas arbitraire, vous obtenez tous les ordres avec la variable id
de la variable customer
à laquelle vous avez appelé la relation. Je suppose que votre code était quelque chose comme
Customer.first.orders(@some_account_which_is_ignored_anyway)
Il semble que la relation has_many
ne soit pas censée accepter les arguments.
Personnellement, je préfère la solution de @ МалъСкрылевъ.