在 rails3 中使用范围进行 Eagerloading [英] Eagerloading with scoping in rails3

查看:38
本文介绍了在 rails3 中使用范围进行 Eagerloading的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在尝试基于 Rails3 应用程序中的某个范围急切加载关联,但找不到任何解决方案.

I have been trying to eager load associations based on some scope in my rails3 app, but could not find any solution.

我的应用有以下模型:

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 我有以下代码来获取用户的所有项目:

And in projects#index i have following code to get all projects of an user:

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

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

It fetches all projects of the user, along with eager loading the associations. So when i perform following loop to get all the entries within the project, no new query gets fired.

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

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

As this eager loading fetches all entries, it is way too much data than what I actually needed. So I tried to apply some conditions to the entries eager loaded, but could not find any solution. I was looking for something like:

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

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

So that only the entries satisfying some conditions get loaded.

我们不能在急切加载中使用作用域吗?请帮助我在确定范围的同时使用 Eagerloading.

Can't we use scoping with eager loading? Please help me out use eagerloading alongside scoping.

推荐答案

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

As far as I know, scopes cannot be applied to included associations like this. However, you can specify conditions that should only be applied to the eager loading queries. So with a bit of refactoring, you could have a method that only created the conditions you currently define in your scope:

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

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

and then chain that method to your eager loading:

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

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

and also reuse it in your scope if you need it independently:

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

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

Disclaimer: None of this is tested with your actual model definitions, but it works fine with some pretty equivalent ones that I had lying around.

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

Also note that if the filter options ultimately come from the client side, your condition is vulnerable to SQL injection.

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

Behind the scenes, Rails uses a JOIN to load the relevant data, so that is something to be aware of. It might be a good thing (a few less queries) or a bad thing (if your indexing is suboptimal). That's probably why the guide has this to say:

即使 Active Record 允许你在eager上指定条件加载关联就像连接一样,推荐的方法是使用改为加入.

Even though Active Record lets you specify conditions on the eager loaded associations just like joins, the recommended way is to use joins instead.

这篇关于在 rails3 中使用范围进行 Eagerloading的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆