【问题标题】:Eagerloading with scoping in rails3在 Rails 3 中使用范围限定的 Eager Loading
【发布时间】:2011-07-18 05:11:06
【问题描述】:

我一直在尝试根据我的 rails3 应用程序中的某些范围急切加载关联,但找不到任何解决方案。

我的应用有以下型号:

class Project
 has_many :entries
 has_many :to_dos

class ToDo
 has_may :entries
 has_many :tasks
 belongs_to :project

class Task
 has_many :entries
 belongs_to :to_do

class Entry
belongs_to :project
belongs_to :to_do
belongs_to :task

# options format: {:from_date=>(Date.today-1.week), :to_date=>(Date.today+1.week), :user_id=>60}
scope :filtered_list, lambda { |options|
  condition = options[:user_id].nil? ? "true" : "user_id = #{options[:user_id]}"
  condition += options[:from_date].nil? ? "" : " AND entry_date >= '#{options[:from_date]}'"
  condition += options[:to_date].nil? ? "" : " AND entry_date <= '#{options[:to_date]}'"
  where(condition)
}

在 projects#index 中,我有以下代码来获取用户的所有项目:

@projects = current_user.projects.includes(:entries, :to_dos =>[:entries, :tasks => :entries])

它获取用户的所有项目,以及预先加载关联。因此,当我执行以下循环以获取项目中的所有条目时,不会触发新查询。

def all_entries(options)
  entries = self.entries
  self.to_dos.each do |d|
    entries += d.entries
    d.tasks.each do |t|
      entries += t.entries
    end
  end
end

由于这种急切加载会获取所有条目,因此数据量比我实际需要的要多。所以我尝试对急切加载的条目应用一些条件,但找不到任何解决方案。我正在寻找类似的东西:

@projects = current_user.projects.includes(:entries.filtered_list(options), :to_dos =>[:entries.filtered_list(options), :tasks => :entries.filtered_list(options)])

这样只有满足某些条件的条目才会被加载。

我们不能在急切加载中使用范围吗? 请帮助我将 eagerloading 与范围界定一起使用。

【问题讨论】:

    标签: ruby-on-rails-3 scoping


    【解决方案1】:

    据我所知,范围不能应用于像这样的包含关联。但是,您可以指定只应应用于预加载查询的条件。因此,通过一些重构,您可以拥有一个仅创建您当前在您的范围内定义的条件的方法:

    def self.filter_by(options)
      condition = options[:user_id].nil? ? "true" : "entries.user_id = #{options[:user_id]}"
      condition += options[:from_date].nil? ? "" : " AND entries.entry_date >= '#{options[:from_date]}'"
      condition += options[:to_date].nil? ? "" : " AND entries.entry_date <= '#{options[:to_date]}'
      condition
    end
    

    或者更红一点:

    def self.filter_by(options)
      conditions = []
      conditions << "entries.user_id = #{options[:user_id]}" unless options[:user_id].nil?
      conditions << "entries.entry_date >= '#{options[:from_date]}'" unless options[:from_date].nil?
      conditions << "entries.entry_date <= '#{options[:to_date]}'" unless options[:to_date].nil?
      conditions.join(" AND ")
    end
    

    然后将该方法链接到您的预加载:

    @projects = current_user.projects.includes(:entries, :to_dos =>[:entries, :tasks => :entries].where(Entry.filter_by(options))
    

    如果您独立需要它,也可以在您的范围内重用它:

    scope :filtered_list, lambda { |options| where(Entry.filter_by(options)) }
    

    免责声明:这些都没有使用您的实际模型定义进行测试,但它适用于我周围的一些非常等效的模型。

    另请注意,如果过滤器选项最终来自客户端,则您的条件很容易受到 SQL 注入的攻击。

    在幕后,Rails 使用 JOIN 来加载相关数据,因此需要注意这一点。这可能是一件好事(少一些查询)或一件坏事(如果您的索引不是最佳的)。这可能就是guide 这么说的原因:

    即使 Active Record 允许您在 Eager 上指定条件 加载关联就像连接一样,推荐的方法是使用 而是加入。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-09
      • 2017-09-17
      • 1970-01-01
      • 2011-10-27
      • 1970-01-01
      • 2014-10-22
      相关资源
      最近更新 更多