web-dev-qa-db-fra.com

Comment passer un argument à une étendue d'association has_many dans Rails 4?

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.

28
Rob Sobers

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

Accéder à l'objet propriétaire

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
22
Brad

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 @ МалъСкрылевъ.

0
Misu