ActiveRecord: delegación de asociaciones

Visita este artí­culo en http://www.estadobeta.com/2007/06/25/activerecord-delegacion-de-asociaciones/

Por Ismael en Desarrollo, Documentación, Ruby & Rails, artículos, tips

Gracias a la magia de ActiveRecord, en Rails es fácil definir las asociaciones entre clases:

Code (ruby)
  1. class Blog < ActiveRecord::Base
  2.   has_many :posts, :order => ‘published_on DESC’
  3. end

Esto nos permite bellezas como

Code (ruby)
  1. mi_blog = Blog.find(:first)
  2. articulos = mi_blog.posts

Pero supongamos que queremos en Blog un método que nos retorne sólo los 10 artículos más recientes Una solución es definir otra asociación para la misma clase:

Code (ruby)
  1. class Blog < ActiveRecord::Base
  2.   has_many :posts, :order => ‘published_on DESC’
  3.   has_many :recent_posts, :class_name => ‘Post’, :order => ‘published_on DESC’, :limit => 10
  4. end


De esta forma podemos disponer de estos 10 artículos fácilmente:

Code (ruby)
  1. recientes = mi_blog.recent_posts

Pero hay un acercamiento más poderoso que me ha resultado muy útil en el último tiempo: la Delegación de Asociaciones (”Association Proxies”).

Code (ruby)
  1. class Blog < ActiveRecord::Base
  2.   has_many( :posts, :order=>’published_on’ ) do
  3.       def recent(limit = 10)
  4.           find :all, :limit => limit
  5.       end
  6.   end
  7. end

Así es. Las asociaciones de Activerecord aceptan un bloque de Ruby como argumento opcional. Si en ese bloque definimos métodos, éstos son agregados como métodos de instancia a la colección de objetos asociados. El ejemplo se usa así:

Code (ruby)
  1. todos = mi_blog.posts
  2. 10_recientes = mi_blog.posts.recent #retorna 10 posts por defecto
  3. 5_recientes = mi_blog.posts.recent(5)

Los métodos definidos en el bloque aceptan los mismos parámetros que ActiveRecord::Base#find, pero las condiciones serán aplicadas sólo a los objetos definidos en la asociación ( en este caso, Post). Por ejemplo, si quisieramos distinguir entre artículos publicados y no publicados:

Code (ruby)
  1. class Blog < ActiveRecord::Base
  2.   has_many( :posts, :order=>’published_on’ ) do
  3.       def published
  4.           find :all, :conditions => "posts.is_published = 1"
  5.       end
  6.       def unpublished
  7.           find :all, :conditions => "posts.is_published = 0"
  8.       end
  9.   end
  10. end

Todo muy bonito, pero hay un problema. Cuando usamos las asociaciones de modo normal, ActiveRecord tiene la delicadeza de cachear los resultados; es decir, la primera vez que invoquemos a mi_blog.posts, ActiveRecord generará el SQL necesario, lo ejecutará en la base de datos y guardará los resultados en un atributo. La segunda vez que hagamos el mismo llamado, ActiveRecord nos entregará los resultados almacenados en lugar de ir de nuevo a la base de datos. Nada de esto funciona cuando definimos métodos extra en las asociaciones.

La solución es implementar nosotros mismos un simple cache usando atributos de instancia en Ruby.

Code (ruby)
  1. class Blog < ActiveRecord::Base
  2.   has_many( :posts, :order=>’published_on’ ) do
  3.       def published
  4.           @publicados ||= find( :all, :conditions => "posts.is_published = 1" )
  5.       end
  6.       def unpublished
  7.           @no_publicados ||= find( :all, :conditions => "posts.is_published = 0" )
  8.       end
  9.   end
  10. end

Un comentario para “ActiveRecord: delegación de asociaciones”

  1. GravatarBeto Dice:

    Muy buen artículo. Este ruby (o este Rails?) es una caja de sorpresas. Eso del bloque me dejo plop!. Como cuando estas llamando un metodo puedas asignarle a ese misma “cuestion” mas métodos?? Crazy!. Todavía no puedo abrir mi mente xD.

Deja un comentario

XHTML: puedes usar estas etiquetas: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>