Как избежать n + 1 has_many дети должны помнить родителей - PullRequest
1 голос
/ 20 мая 2019

Если у вас есть отношения родитель-потомок:

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

Эта последняя строчка убивает меня. Почему это попадает в базу данных для родителя? Похоже, это следует помнить, так как ребенок был загружен через родителя?

Ответы [ 3 ]

2 голосов
/ 20 мая 2019

Решением является использование 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
1 голос
/ 20 мая 2019

Ассоциация belongs_to имеет параметр, называемый inverse_of, который, если используется, явно устанавливает двунаправленную ассоциацию между моделями, как описано в документе API для assign_to .Как работают двунаправленные ассоциации, можно подробнее изучить в этом документе API .
В принципе, если бы у модели Child была ассоциация, записанная как belongs_to :parent, inverse_of: :parent, дополнительный запрос не был бы выполнен.
Обязательно этот блог для получения дополнительной информации о том, как inverse_of работает.Это дает очень хорошее объяснение с примерами.

0 голосов
/ 21 мая 2019

Начиная с 4.1, Rails автоматически обнаруживает обратную ассоциацию.

См. Примечания к выпуску https://guides.rubyonrails.org/4_1_release_notes.html

В вашем случае последняя родительская строка попадает в базу данных.за.Фактический SQL-запрос извлек дочерние элементы, поскольку, когда вы обращаетесь к ассоциации в консоли Rails и фактически не используете результат, он не кэшируется.

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

Таким образом, в вашем случае это был запрос к базе данных для детей.

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 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...