2011-07-18 39 views
7

我一直試圖根據我的rails3應用程序中的某些範圍急切加載關聯,但是找不到任何解決方案。Eagerloading在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 = 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)]) 

這樣只有符合條件的條目纔會被加載。

我們不能在急切加載時使用範圍界定嗎? 請幫助我一起使用預先確定的範圍。

回答

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 

或略偏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 

,然後鏈中的方法您預先加載:

@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有這樣一段話:

即使活動記錄可以讓你在渴望 裝載協會只是想加入,推薦的方法是使用 加入,而不是指定的條件。