format internet:

…please wait (42% completed)…

includes anidados / encadenados en rails (eager loading)

Posted by javier ramirez on December 10, 2006

Ya sabemos que mediante el uso de Associations podemos conseguir eliminar el problema de las n+1 selects cuando queremos cargar datos de una tabla y su tabla asociada. Esto es lo que se conoce como eager loading.

Siguiendo la documentación, está claro que se puede hacer include de n tablas de golpe haciendo :include =>[tabla1,tabla2], y obtendremos datos tanto de nuestra tabla original como de las tablas tabla1 y tabla2. Un punto a tener en cuenta es que el tipo de joins que se hacen son LEFT OUTER, por lo que obtendremos todos los registros de la tabla original y solamente los que tengan datos de las tablas relacionadas.

Hasta aquí, todo perfecto cuando quieres hacer una select con tablas que están relacionadas con tu modelo directamente; pero si quieres hacer de una tacada una query que incluya las tablas relacionadas y a su vez las tablas relacionadas de éstas, no parece que sea posible. No al menos mirando solamente la documentación.

Sin embargo, una de las cosas interesantes del open source es que le podemos echar un vistazo al código fuente y ver qué es lo que hace exactamente, teniendo de esta forma la mejor documentación posible.

Tras una investigación alrededor de ActiveRecord(*ver más abajo), encontramos que, cuando se definen asociaciones, a la hora de montar la query se diferencia entre tres casos diferentes: si la asociación es un Symbol/String, si es un Array o si es una Hash. Aquí es donde está la gracia. En la documentación sólo nos explican el caso de Symbol/String y de Array, pero… y si le paso una Hash?.

Si le paso una Hash, consigo justamente lo que quería, poder incluir en la query las tablas relacionadas y las relacionadas con ellas. La sintáxis ,no tan obvia, para esto es la siguiente:

:include=>[{:tabla1=>[:tabla1_A,:tabla1_B]}]

Esto me carga las relaciones de mi modelo con tabla1, y a su vez las de tabla1 con tabla1_A y con tabla1_B, ahorrándonos bastante carga contra la base de datos.

* A continuación muestro el extracto de la clase JoinDependency, definida en Associations.rb donde se lleva a cabo la magia de las asociaciones

def construct(parent, associations, joins, row)
  case associations
  when Symbol, String
    while (join = joins.shift).reflection.name.to_s != associations.to_s
    raise ConfigurationError, “Not Enough Associations” if joins.empty?
    end
    construct_association(parent, join, row)
  when Array
    associations.each do |association|
    construct(parent, association, joins, row)
    end
  when Hash
    associations.keys.sort{|a,b|a.to_sb.to_s}.each do |name|
    association = construct_association(parent, joins.shift, row)
    construct(association, associations[name], joins, row) if association
    end
  else
    raise ConfigurationError, associations.inspect
  end
end



def construct_association(record, join, row)
  case join.reflection.macro
  when :has_many, :has_and_belongs_to_many
    collection = record.send(join.reflection.name)
    collection.loaded
    return nil if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
    association = join.instantiate(row)
    collection.target.push(association) unless collection.target.include?(association)
  when :has_one, :belongs_to
    return if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
    association = join.instantiate(row)
    record.send(“set_#{join.reflection.name}_target”, association)
  else
    raise ConfigurationError, “unknown macro: #{join.reflection.macro}”
  end
  return association
end

searchwords: include, nested include, chained include, eager loading, associations, JoinDependency

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: