ActiveRecord的意外行为包括

时间:2016-05-23 21:07:47

标签: sql ruby-on-rails ruby ruby-on-rails-4 rails-activerecord

我使用AR includes方法在对象用户构建之间执行LEFT OUTER JOIN,其中用户可能会也可能不会有一个建筑协会:

users = User.includes(:building).references(:buildings)

由于我使用references,因此任何关联的建筑物对象都会被急切加载。

我的期望是,我可以遍历用户列表,并检查用户是否有与之关联的建筑物而不会触发其他查询,但我发现实际上每当我尝试访问建筑物属性时如果没有一个用户,AR会进行另一次SQL调用以尝试检索该建筑物(尽管在后续尝试中它只会返回nil)。

这些查询显然是多余的,因为在初始加入期间会加载关联,并且似乎打败了包含/引用的热切加载的整个目的,因为现在我查看了N次查询的数量等于空关联的数量。

users.each do | user |

  # This will trigger a new query when building is not present: 
  # SELECT  "buildings".* FROM "buildings" WHERE "buildings"."address" = $1 LIMIT 1  [["address", "123 my street"]]
  if user.building
    puts 'User has building'
  else
    puts 'User has no building' 
  end

end

用户类:

class User < ActiveRecord::Base
  belongs_to :building, foreign_key: 'residence_id'
end

有没有办法检查用户的存在&#39;建立关联而不会触发额外的查询?

ON RAILS 4.2.0 / POSTGRES

更新

感谢@BoraMa汇总了这个test。看起来我们在最近的Rails版本中获得了不同的行为:

OUTPUT(RAILS 4.2.0):

User 1 has building
User 2 has building
User 3 has no building
D, [2016-05-26T11:48:38.147316 #11910] DEBUG -- :   Building Load (0.2ms)  SELECT  "buildings".* FROM "buildings" WHERE "buildings"."id" = $1 LIMIT 1  [["id", 123]]
User 4 has no building

OUTPUT(RAILS 4.2.6)

User 1 has building
User 2 has building
User 3 has no building
User 4 has no building

OUTPUT(RAILS 5.0.0)

User 1 has building
User 2 has building
User 3 has no building
User 4 has no building

带走:

  • 此问题仅限于&#34;悬挂外键(即residence_id 列不是零,但没有相应的构建对象)&#34; (感谢@FrederickCheung)
  • 自Rails 4.2.6
  • 起,该问题已得到解决

3 个答案:

答案 0 :(得分:3)

听起来好像你在Active Record中得到了bug,这已经在rails 4.2.3中修复了。

在列为零的情况下,Active Record已经知道它甚至不需要尝试加载关联的对象。剩下的案例是受此漏洞影响的案例

答案 1 :(得分:0)

有点像拼写错误,请注意building而不是buildingsUser.includes(:building).references(:buildings)

这应该触发对每个关联和表使用AS tX_rY格式的大查询。

答案 2 :(得分:0)

似乎由于rails 4.1存在潜在的冲突,如何隐含#includes应该是多少,请参阅以下open issue

这段代码的语法都未经过测试,但我会尝试两种方法:

1 /急切加载隐含

users = User.eager_load(:building).preload(:buildings)

2 /分离出两种类型的用户,即建筑物附属的用户,这意味着你甚至不会尝试预先加载建筑物,从而消除了无效率。

users = User.includes(:building).where.not(residence_id: nil).references(:buildings)

users.each do | user|
     puts "User has building: #{user} #{user.building}"
end

# No additional references needed to be eager-loaded.
users = User.where(residence_id: nil)
users.each do | user |
     puts "#{User} has no building."
end