13 votes

Chargement d'une association d'une association singulière à partir d'un objet ActiveRecord instancié dans Rails 5

J'ai un modèle User , que has_one :child et le Child modèle has_one :toy .

Si j'ai une seule instance de la classe User user Comment puis-je charger à la fois l'enfant et le jouet dans une seule requête ?

Voici ce qui ne fonctionne pas :

user.child.toy # 2 queries
user.includes(child: :toy) # can't call includes on a single record
user.child.includes(:toy) # same as above
user.association(:child).scope.eager_load(:toy).first # makes the appropriate query with both child and toy... but doesn't set the object on the user model.
user.child = user.association(:child).scope.eager_load(:toy).first # makes the appropriate query, but also calls another query before setting the child :(

Existe-t-il un moyen de faire cela sans avoir à réinterroger le modèle d'utilisateur, c'est-à-dire que je veux éviter cela ?

User.where(id: user.id).eager_load(child: :toy).first

Déclarations de modèle relatives :

class User < ActiveRecord::Base
  has_one :child
  has_one :toy, through: :child
end

class Child < ActiveRecord::Base
  has_one :toy
  belongs_to :user
end

class Toy < ActiveRecord::Base
  belongs_to :child
end

Mise à jour

Cela fonctionne, mais n'est pas idéal. Je ne pense pas qu'il faille déclarer une autre relation uniquement pour cette raison.

class User < ActiveRecord::Base
  has_one :child
  has_one :toy, through: :child
  has_one :child_with_toy, ->{ eager_loads(:toy) }, class_name: "Child", foreign_key: :parent_id
end

ce qui me permet d'appeler user.child_with_toy pour obtenir le Child et l'objet user.child_with_toy.toy pour obtenir le Toy tout en ne déclenchant qu'une seule requête SQL.

10voto

Nathan Points 2152

Si vous souhaitez charger une association d'une association singulière pour un enregistrement déjà créé, vous pouvez le faire :

user.association(:child).target = user.association(:child).scope.eager_load(:toy).first

Cette approche est similaire à l'une de celles que vous avez énumérées au début de votre question. En particulier, remarquez le .target partie.

ActiveRecord n'est pas optimisé pour ce scénario particulier, et le code est donc assez laid. Pour cette raison, je pencherais fortement en faveur de votre solution :child_with_toy si vous avez vraiment besoin de sauvegarder la requête.

1voto

Dmitry Polushkin Points 920

Il est plus facile de le faire avec Preloader :

ActiveRecord::Associations::Preloader.new.preload(user, child: [:toy])

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X