在 Rails3 中使用范围进行预加载

发布于 2024-11-24 08:23:40 字数 1556 浏览 1 评论 0原文

我一直在尝试根据 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)])

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

我们不能将作用域与预加载一起使用吗? 请帮助我在范围界定的同时使用预加载。

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

My app has following models:

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)
}

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.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

掀纱窥君容 2024-12-01 08:23:40

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

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 来加载相关数据,因此需要注意这一点。这可能是一件好事(更少的查询),也可能是一件坏事(如果您的索引不是最优的)。这可能就是指南这样说的原因:

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

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

or a bit more rubyesque:

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.

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

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:

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文