2011-07-18 14 views
7

He estado intentando asociaciones de carga ansiosas basadas en algún ámbito en mi aplicación rails3, pero no he podido encontrar ninguna solución.Eagerloading con scoping en rails3

Mi aplicación tiene siguientes modelos:

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

Y en proyectos índice # Tengo siguiente código para obtener todos los proyectos de un usuario:

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

Recupera todos los proyectos del usuario, junto con ganas de cargar las asociaciones. Entonces, cuando realizo el siguiente ciclo para obtener todas las entradas dentro del proyecto, no se activa ninguna nueva consulta.

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 

Como esta carga ansiosa recupera todas las entradas, es demasiada información de la que realmente necesitaba. Así que traté de aplicar algunas condiciones a las entradas ansiosas cargadas, pero no pude encontrar ninguna solución. Estaba buscando algo como:

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

De modo que solo se carguen las entradas que cumplan algunas condiciones.

¿No podemos usar el alcance con la carga ansiosa? Por favor, ayúdenme a usar eagerloading junto con el alcance.

Respuesta

1

Hasta donde yo sé, los ámbitos no se pueden aplicar a asociaciones incluidas como esta. Sin embargo, puede especificar condiciones que solo deberían aplicarse a las consultas de carga ansiosas. Así que con un poco de refactorización, usted podría tener un método que sólo creó las condiciones que se definen actualmente en su ámbito de aplicación:

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 

o un poco más 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 

y luego cadena que método para su carga ansiosa:

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

y reutilizarla en su alcance, si lo necesita de forma independiente:

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

Descargo de responsabilidad: nada de esto se prueba con las definiciones reales de su modelo, pero funciona bien con algunos equivalentes bastante bonitos que tenía.

También tenga en cuenta que si las opciones de filtro provienen del lado del cliente, su condición es vulnerable a la inyección de SQL.

Detrás de escena, Rails usa un JOIN para cargar los datos relevantes, por lo que es algo a tener en cuenta. Puede ser algo bueno (unas pocas consultas menos) o algo malo (si su indexación no es óptima). Esa es probablemente la razón por la guide tiene esto que decir:

Aunque Active Record le permite especificar las condiciones en las ansiosos asociaciones cargadas al igual que se une, el método recomendado es utilizar se une en su lugar.