« Birt

Usando bloques en lugar de métodos en Rails, parte I

Visita este artí­culo en http://www.estadobeta.com/2007/12/27/bloques-ruby-en-lugar-de-metodos-1/

Por Ismael en Desarrollo, Ruby & Rails, artículos

Hace un tiempo contaba sobre un proyecto cuya complejidad justificaba la creación de un simple DSL para estructurar de mejor forma la lógica de negocios.

Dado el <amargo_sarcasmo>exito rotundo</amargo_sarcasmo> del artículo, expongo un par de ideas que desarrollé para el proyecto en cuestión.

La aplicación gira en torno a la definición de distintas “vistas” sobre los contenidos del sitio, según los privilegios del usuario. Las reglas que definían estas vistas podían cambiar en el futuro y por eso diseñé un sencillo mini-lenguaje (DSL) que luego transformaba internamente en queries SQL. Podía usar este mini-lenguaje facilmente en distintos métodos de mis objetos.

Code (ruby)
  1.  
  2. class Usuario < ActiveRecord::Base
  3.   def mis_articulos
  4.     prepara_articulos do |articulos|
  5.       articulos.add :from=>self, :to=>:all, :status => :all
  6.       articulos.add :from=>:all, :to=>:self, :status => :public
  7.       articulos.add :from=>friends, :to=>:all, :status => :all
  8.     end
  9.   end
  10. end
  11.  

Ahora. Con el tiempo y el humor cambiante del cliente, fue quedando claro que no solamente estas reglas podían cambiar sino que habría que crear una cantidad indeterminada de vistas alternativas para la misma información.

La solución rápida era simplemente crear nuevos métodos para cada vista requerida, y posiblemente invocarlos desde una acción en el controlador.

Code (ruby)
  1.  
  2. # config/Routes.rb
  3. map.vista ‘articulos/:id_usuario/:vista’, :controller=>:articulos, :action=>:muestra_vista
  4.  
  5. # app/controllers/articulos_controller.rb
  6. def muestra_vista
  7.   @usuario = Usuario.find( params[:id] )
  8.   @articulos = @usuario.send( params[:vista] )
  9. end
  10.  

Así, basta con definir un nuevo método en la clase Usuario y llamarlo directamente desde las URLs. El controlador se encarga de invocar el método en el usuario dinámicamente por medio de send.

Code (ruby)
  1.  
  2. class Usuario < ActiveRecord::Base
  3.  
  4.   # GET /articulos/ismael/mis_articulos
  5.   def mis_articulos
  6.     # Define reglas
  7.   end
  8.  
  9.   # GET /articulos/ismael/enviados
  10.   def enviados
  11.     # define otras reglas
  12.   end
  13. end
  14.  


La solución es bastante elegante pero tiene sus inconvenientes. Primero, deja a disponibilidad del visitante todos los métodos públicos del objeto @usuario, que ahora pueden ser invocados directamente desde la URL. Aunque puedo validar los parámetros antes de usarlos, el proceso sería engorroso y susceptible a omisiones.

El segundo problema es algo más dogmático: con cada uno de estos nuevos métodos ensuciamos el espacio de nuestras clases con métodos que cumplen prácticamente la misma función.

Ir a hacerse un café y pensar un poco.

El requerimiento es simple. La clase Usuario debe ser capaz de definir múltiples “vistas” (o reglas) usando el DSL creado para ese efecto, sin tener que recurrir a la creación de métodos arbitrarios.

Partiendo por la API (como debe ser), lo que necesito es esto:

Code (ruby)
  1.  
  2. class Usuario < ActiveRecord::Base
  3.  
  4.   define_vista_de_articulos(:mis_articulos) do |usuario,articulos|
  5.     articulos.add :from=>usuario, :to=>:all, :status => :all
  6.     articulos.add :from=>:all, :to=>:self, :status => :public
  7.     articulos.add :from=>usuario.friends, :to=>:all, :status => :all
  8.   end
  9.  
  10.   define_vista_de_articulos(:enviados) do |usuario,articulos|
  11.     articulos.add :from => usuario, :to => :all
  12.   end
  13. end
  14.  

El método de clase define_vista_de_articulos define las reglas usadas por las instancias de Usuario a nivel de la clase. El método recibe un nombre para la vista y un bloque con la definición de las reglas. Estas vistas deben ser accesibles en URLs como /articulos/ismael/mis_articulos, /articulos/ismael/enviados, etc, donde el último fragmento es el nombre de la vista, sea lo que sea.

Luego, en el controlador, tendríamos algo como esto:

Code (ruby)
  1.  
  2. # app/controllers/articulos_controller.rb
  3. def muestra_vista
  4.   @usuario = Usuario.find( params[:id] )
  5.   @articulos = @usuario.carga_vista( params[:vista] )
  6. end
  7.  

El método carga_vista sería el único método de instancia involucrado en la clase Usuario y se encargaría de rescatar la vista definida para el parámetro entrante en la URL (”mis_articulos”, “enviados”).

El truco aquí es almacenar los bloques de configuración de cada vista a nivel de la clase, e invocarlos luego desde la instancia. Si la sintaxis se ve familiar a los callbacks de ActiveRecord, es porque la metodología es la misma.

Code (ruby)
  1.  
  2. # Este bloque es definido en la clase pero ejecutado en la instancia
  3. # sin necesidad de crear nuevos m&eacute;todos de instancia.
  4. after_save do |usuario|
  5.   logger.debug(usuario.nombre)
  6. end
  7.  

Si todavía estas leyendo y te interesa saber cómo lo hice, espera la segunda entrega de este artículo, pronto en su blog amigo EstadoBeta.

Un comentario para “Usando bloques en lugar de métodos en Rails, parte I”

  1. Gravatar59aea0a91aec Dice:

    59aea0a91aec…

    59aea0a91aec0589f315…

Deja un comentario

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