【问题标题】:How to eager load associations with the current_user?如何急切加载与 current_user 的关联?
【发布时间】:2011-08-01 18:11:08
【问题描述】:

我在我的 Rails 应用程序中使用 Devise 进行身份验证。我想在我的一些控制器中急切地加载一些与用户相关的模型。像这样的:

class TeamsController < ApplicationController

  def show
    @team = Team.includes(:members).find params[:id]
    current_user.includes(:saved_listings)

    # normal controller stuff
  end
end

我怎样才能做到这一点?

【问题讨论】:

  • 覆盖 current_user 方法

标签: ruby-on-rails ruby-on-rails-3 activerecord devise eager-loading


【解决方案1】:

我遇到了同样的问题,虽然每个人都一直说没有必要这样做,但我发现有,就像你一样。所以这对我有用:

# in application_controller.rb:
def current_user
  @current_user ||= super && User.includes(:saved_listings).find(@current_user.id)
end

请注意,这将加载 所有 控制器中的关联。对于我的用例,这正是我所需要的。如果您真的只希望在 一些 控制器中使用它,那么您将不得不对此进行更多调整。

这也将调用User.find 两次,但查询缓存不应该是一个问题,并且由于它可以防止一些额外的数据库命中,它仍然是一个性能提升。

【讨论】:

  • 我还没有测试过,但它看起来不错。很容易让它接受一个表示要包含的表名数组的参数。
  • 请注意,从 Rails 4 开始,这个使用 #find 的答案确实会在 users 上触发两个单独的数据库查询。 #first(由 OrmAdapter::ActiveRecord#get 使用,Devise 调用它来序列化其current_user 对象)在 Rails 4+ 中的实现添加了一个 ORDER BY users.id ASC 子句,而 #find 没有,因此两个查询在查询缓存。 User.includes(:saved_listings).where(id: @current_user.id).first 将匹配 Rails 版本。
  • +1 用于上述数据库查询。这可能是一个昂贵的解决方案。使用ActiveRecord::Associations::Preloader在单独的答案中找到更好的替代我和我的队友@
【解决方案2】:

在您的 User 模型中覆盖 serialize_from_session

class User
  devise :database_authenticatable

  def self.serialize_from_session key, salt
    record = where(id: key).eager_load(:saved_listings, roles: :accounts).first
    record if record && record.authenticatable_salt == salt
  end
end

然而,这将急切加载所有个请求。

【讨论】:

  • Thilo 上面当前选择的答案会导致更多查询。如果意图是在 current_user 中急切加载关联模型以减少查询数量,那么覆盖类方法(如本答案所示)是可行的方法。
【解决方案3】:

我想添加我认为更好的解决方案。如 cmets 中所述,现有解决方案可能会使用 find 请求两次访问您的数据库。相反,我们可以使用 ActiveRecord::Associations::Preloader 来利用 Rails 在加载关联方面的工作:

def current_user
  @current_user ||= super.tap do |user|
    ::ActiveRecord::Associations::Preloader.new.preload(user, :saved_listings)
  end
end

这将重用内存中的现有模型,而不是再次连接和查询整个表。

【讨论】:

  • 这是迄今为止最好的答案,谢谢。您还可以创建一个方便的方法来使其对被调用者显式。 def current_user_with(includes) @current_user_with ||= current_user.tap do |user| ::ActiveRecord::Associations::Preloader.new.preload(user, includes) end end
【解决方案4】:

为什么不在模型上使用 default_scope 呢?

像这样:

Class User  < ActiveRecord::Base
  ...
  default_scope includes(:saved_listings)
  ...
end

【讨论】:

  • 那么User 上的每个查询都会急切加载保存的列表(包括测试)。如果您只想预先加载一两个操作,这将是多余的,并且会对应用程序的响应时间和测试套件的速度产生负面影响。
  • 只是一个想法...如果您的current_user 属于一家公司以及表中的所有其他用户,并且如果您不断引用返回用户或 current_user 的公司属性,不会有这个有意义吗?此外,Rails 4.2.1(不知道以前的版本)不允许 default_scope 没有块,所以你必须调用 default_scope { includes(:company) }
猜你喜欢
  • 1970-01-01
  • 2015-02-24
  • 1970-01-01
  • 1970-01-01
  • 2011-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多