【问题标题】:Chaining multiple scopes that contain joins() and select()链接包含 joins() 和 select() 的多个范围
【发布时间】:2013-12-03 17:04:57
【问题描述】:

我阅读的所有内容都建议人们将“复杂”查询排除在控制器之外,并将它们放入模型的范围内。但是,您如何建议在使用joins 时使用需要来自三个模型的数据的查询来执行此操作。例如,

class User < ActiveRecord::Base
  belongs_to :company
  belongs_to :profile
end

如果我们需要来自Usercompanies.nameprofiles.city,我们可以将查询填充到控制器操作中,这非常有效。

User.joins(:company, :profile).select('users.*, companies.name as company_name, profiles.city as city').find(1)

为了将其排除在控制器之外,我们可以定义一个命名范围,例如

scope :include_company_name_and_profile_city, -> { joins(:company, :profile).select('users.*, companies.name as company_name, profiles.city as city') }

在控制器中使用User.include_company_name_and_profile_city.find(1)

但是如果在不同的时间我们需要公司名称或个人资料城市呢?我们可以定义两个作用域并将它们链接起来吗?

scope :include_company_name, -> { joins(:company).select('users.*, companies.name as company_name') }
scope :include_profile_city, -> { joins(:profile).select('users.*, profiles.city as city') }

调用User.include_company_name.include_profile_city.find(1) 将产生一个在SELECT 子句中包含两个users.* 的查询。

SELECT users.*, companies.name as company_name, profiles.city as city, users.*, ...

推荐的处理方法是什么?

  1. 将所有joins()select() 调用放入控制器中?
  2. 创建一个加载公共关联数据的作用域,并且不关心如果该数据从未使用过可能产生的开销? (这是有缺陷的 imo,见下文)
  3. 创建多个不包含select('users.*') 的命名作用域并在控制器或默认作用域中附加select('users.*')
  4. 在 select 子句中有多个 users.* 是否重要?

如果人们推荐选项 2,那么在其他模板中我们需要更多(不那么常见)关联数据的情况呢? (即User.include_common_association_data.include_non_common_association_data.find(1))。这将遇到上面提到的相同问题(在 select 子句中有多个 users.*)。

【问题讨论】:

    标签: ruby-on-rails rails-activerecord


    【解决方案1】:

    我不确定我是否理解实际问题,但听起来您正在编写联接和选择,何时 ActiveRecord 会为您完成所有这些工作?

    这些是你的模特吗?

    class User < ActiveRecord::Base
      belongs_to :company
      belongs_to :profile
    end
    ...
    class Company 
      has_many :users
    end
    
    class Profile
      has_many :users
    end
    

    所以如果你想这样做:

    如果我们需要用户的 Companies.name 和 profiles.city,我们可以 将查询填充到控制器操作中,效果很好。

    您可以通过 Active Record 关系获得这些:

    user = User.find(whatever).includes(:company, :profile)
    user.company.name 
    user.profile.city
    

    已编辑以显示使用包含的即时加载 您不需要范围来找出这些属性中的任何一个,除非还有更多。范围基本上用于从属性中自动过滤。

    scope :latest, order("created_at desc").limit(3)
    scope :city_present, where("city is not null")
    

    编辑:如果您以前没有花时间在上面,activerecord query interface 指南非常有帮助。 此外,ActiveRecord 在优化查询方面非常聪明,无需您告诉它该做什么,如果您想查看它呈现的 SQL,请使用 rails 控制台查看。

    【讨论】:

    • user = User.find(1) 将执行一个查询,访问user.company.name 将执行第二个 (SELECT * FROM companies ...),user.profile.city 将执行第三个 (SELECT * FROM profiles ...)。不过,您可以使用User.joins(:company, :profile).select('users.*, companies.name as company_name, profiles.city as city').find(1)joins()select() 进行优化,这将在用户实例上添加company_namecity 属性,同时只执行一个查询。
    • 是的,但是如果您急切加载连接的对象,它将对所有三个对象进行一次查询,并且您拥有完全加载的对象,而不仅仅是在选择中提取细节。 “Active Record 允许您通过使用数组、散列或数组/散列的嵌套散列和 include 方法,通过单个 Model.find 调用预先加载任意数量的关联。”
    • 不。我假设人们专门为他们需要的数据编写每个范围,但这会引入我不喜欢的重复。
    • 您是否明白我关于使用包含一次查询多个对象的观点(在一个查询中)?
    猜你喜欢
    • 1970-01-01
    • 2020-08-26
    • 2016-12-08
    • 2014-01-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-05
    相关资源
    最近更新 更多