在使用after_initialize回调时,如何修复ActiveRecord(Rails 3)中的n + 1查询问题?

时间:2011-06-30 13:52:09

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

型号:

class Project < ActiveRecord::Base
  has_many :user_roles 
  after_initialize :add_user_roles

  def add_user_roles
    UserRoles.all.each do |ur|
      self.user_roles << ur unless self.user_roles.include?(ur)
    end
  end
end

查找项目的声明:

@projects = Project.includes(:user_roles)

所以你可以看到,我告诉它在查询中包含用户角色关联。但是,我仍然看到n + 1查询问题:它为每个项目找到一次角色。

如果我从回调中删除self.user_roles的用法并查看日志,我可以看到它在2个查询中找到项目及其用户角色 - 一个用于项目,一个用于角色使用{ {1}}。

有办法解决这个问题吗?

让我澄清一点:虽然我愿意在需要时解决我的具体情况,但我更喜欢那些专注于如何解决问题的答案。我能够编写一个kludge来使数据处于我想要的状态而不使用after_initialize回调,因此不会进入n + 1查询问题。但是,我宁愿不这样做,所以我更喜欢一般问题的答案而不是我的具体例子。

3 个答案:

答案 0 :(得分:6)

看看rails eager loading http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations

您可以使用includes

加载对象时加载关联
User.find(2).includes(:assets)#will load all assets with user

或者您可以在模型中指定急切加载关联

应用程序/模型/ user.rb

class User< AR::Base
   has_many :posts,:include=>:comments
end

class Post < AR::Base
  has_many :comments
  belongs_to :user
end

现在u.posts将为每个帖子加载评论

答案 1 :(得分:1)

这可能是由after_initialize回调引起的,每次初始化每个对象时都会运行该回调。如果回调点是自动为每个用户分配每个角色(除非已经分配),那么您可以通过before_save过滤器来完成此操作。这样,在执行Project.includes(:user_roles)查找程序时,代码将无法运行。

答案 2 :(得分:1)

即使是在after_initialize中也没有加载加载的关联(在初始化记录后,它们会被加载)。有关讨论,请参阅此Rails问题:

https://github.com/rails/rails/issues/13156

与原始问题相关:看起来每个项目都有相同的UserRole个对象集。我猜测有一个has_many :through被消毒了,但即便如此,Project怎么会没有全套?我没有看到ProjectUserRole在这里实际连接的方式 - 从示例中可见的内容来看:

class Project < ActiveRecord::Base
  def user_roles
    UserRole.all
  end
end

会完成与after_initialize ...

相同的事情