避免n + 1个has_many孩子应该记住父母

时间:2019-05-20 15:52:04

标签: ruby-on-rails activerecord ruby-on-rails-5

当您有亲子关系时

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 个答案:

答案 0 :(得分:2)

解决方案是使用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 :(得分:1)

belongs_to关联具有一个名为inverse_of的选项,如使用该选项,则会明确建立模型之间的双向关联,如API doc for belongs_to中所述。双向关联如何工作in this API doc
基本上,如果Child模型的关联写为belongs_to :parent, inverse_of: :parent,则不会进行额外的查询。
请查看this blog以获得有关inverse_of的工作方式的更多详细信息。它通过示例提供了很好的解释。

答案 2 :(得分:0)

从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