Lorsque vous avez une relation parent-enfant:
class Parent < ActiveRecord::Base has_many :children end class Child < ActiveRecord::Base belongs_to :parent end > parent = parent.find(2) Parent Load (0.6ms) SELECT `parents`.* FROM `parents` WHERE `parents`.`id` = 2 LIMIT 1 > children = parent.children Child Load (1.4ms) SELECT `children`.* FROM `children` WHERE `children`.`parent_id` = 2 > children.to_a Child Load (0.8ms) SELECT `children`.* FROM `children` WHERE `children`.`parent_id` = 2 > children.loaded? => true > children.first.parent Parent Load (0.7ms) SELECT `parents`.* FROM `parents` WHERE `parents`.`id` = 2 LIMIT 1
Cette dernière ligne est ce qui me tue. Pourquoi frappe-t-il la base de données du parent? On dirait qu'il devrait s'en souvenir puisque l'enfant a été chargé via le parent?
3 Réponses :
La solution est l'utilisation de inverse_of
:
class Parent < ActiveRecord::Base has_many :children, inverse_of: :parent end class Child < ActiveRecord::Base belongs_to :parent, inverse_of: :children end > parent = Parent.find(foo) # Fetches the parent > children = parent.children # Fetches all children > children.first.parent # No longer fetches the parent again
Cela devrait fonctionner sans l'option inverse_of
. Il s'agit du comportement par défaut de Rails 5.
L'association appartient_to
a une option appelée inverse_of
, qui, si elle est utilisée, établit une association bidirectionnelle entre les modèles, explicitement, comme décrit dans le Documentation de l'API pour appartient_to . Le fonctionnement des associations bidirectionnelles peut être exploré plus avant dans ce document sur l'API .
Fondamentalement, si le modèle Child
avait l’association écrite comme appartient_to: parent, inverse_of:: parent
, la requête supplémentaire n’aurait pas été effectuée.
Consultez ce blog a> pour plus de détails sur le fonctionnement de inverse_of
. Il fournit une très bonne explication avec des exemples.
Cela devrait fonctionner sans l'option inverse_of
. Il s'agit du comportement par défaut de Rails 5.
À partir de la version 4.1, Rails détecte automatiquement l'inverse d'une association.
Voir les notes de version https : //guides.rubyonrails.org/4_1_release_notes.html
Dans votre cas, ce n'est pas le parent pour lequel la dernière ligne a atteint la base de données. La requête SQL réelle a récupéré les enfants car lorsque vous accédez à l'association dans la console Rails et que vous n'utilisez pas réellement le résultat, elle n'est pas mise en cache.
parent = Parent.find(foo) # Fetches the parent children = parent.children # Fetches all children children.first.parent # Fetches all children again, does not fetch parent as it is automatically inversed child = children.first # will not fetch the parent child.parent
Donc, dans votre cas, ce sont les enfants pour lesquels la requête DB a été effectuée.
parent = Parent.find(foo) # Fetches the parent: # Parent Load (0.3ms) SELECT "parents".* FROM "parents" WHERE ... children = parent.children # Fetches all children: # Child Load (1.0ms) SELECT "children".* FROM "children" WHERE ... # the association has not been cached children.loaded? # => false # Will fetch children again and again ... children # Fetches all children: # Child Load (1.0ms) SELECT "children".* FROM "children" WHERE ... children # Fetches all children: # Child Load (1.0ms) SELECT "children".* FROM "children" WHERE ... # until you really use them: children.to_a children.loaded? # => true children # Does not hit the database now
Merci pour cela! Malheureusement, je ne le vois pas dans ma console Rails 5.2. J'ai mis à jour ma question avec les résultats et je me suis assuré que la collection est chargée.
Je pense que c'est ainsi que cela fonctionne. Vous pouvez essayer
inverse_of
et voir si cela vous aide.@MarlinPierce Je pensais que je me souvenais d'un moyen de faire ça! Merci!
Etes-vous sûr que dans votre cas, c'est le parent pour lequel la dernière ligne atteint la base de données? Jetez un œil au SQL. Va-t-il chercher le parent ou récupère-t-il les enfants? Ce que vous décrivez ne ressemble pas au comportement par défaut de Rails. Le parent doit être mis en cache dans votre cas.
Il atteint définitivement la base de données sur children.first.parent. L'objet enfant ne connaît pas le parent sans ce paramètre inverse_of.
@TomRossi Dans votre cas, l'enfant doit connaître le parent sans aucun paramètre
inverse_of
. C'est le comportement par défaut. Je comprends quechildren.first.parent
frappe la base de données. Ma question était: était-ce vraiment le parent pour lequel la DB a été frappée? Veuillez vérifier la requête SQL réelle