Category: rails


Rails: Observar un modelo desde un plugin == problemas

November 16th, 2008 — 11:52 pm

Rails implementa el patron observador para permitir observar un modelo y responder así los diferentes estados que puede atravesar un objeto de dicho modelo.

Actualmente estoy trabajando en un proyecto donde es necesario que dos aplicaciones realizadas en Rails se comuniquen entre si mediante mediante Rest.Para conseguir esto, una de las aplicaciones incluye un plugin (proxy) en la otra.En este plugin se encarga de lo siguiente:

  • Se definen los datos necesarios para esta comunicacion entre aplicaciones como así requiere ActiveResource::Base
  • Se registra un observador que observara (valga la redundancia) uno de los modelos en la aplicación receptora el plugin

Antes de continuar con este post, tengo que confesar una cosilla, observar un modelo desde un plugin es un problema, si y solo si se hace en modo desarrollo.¿Y por qué?

Si no te interesa saber el porque de los problemas y has llegado hasta aqui buscando una solucion, con indicar a Rails que recargue los plugins en cada petición tendrás el asunto resuelto.

Para ello sigue los siguientes pasos:

1º. Indicar a Rails que quieres recargar los plugins

En config/environment.rb añade esta linea:

config.reload_plugins = true

Con esto lo único que hacemos en realidad, es evitar que el path /lib de cada plugin sea incluido en Dependencies.load_once_paths

Pero esto puede que no nos arregle nuestro problema y para muestra un ejemplo:

Arrancare el webrick en modo debug con un breakpoint en el método dispatch del fichero dispatcher.rb, método invocado para cada petición.

def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout)
    debugger
    new(output).dispatch_cgi(cgi, session_options)
end

Arrancamos y enviamos una peticion:

=> Booting WEBrick...
=> Debugger enabled
=> Rails 2.1.1 application started on http://events.trabenet.local:3001
=> Ctrl-C to shutdown server; call with --help for options
[2008-11-16 21:37:24] INFO  WEBrick 1.3.1
[2008-11-16 21:37:24] INFO  ruby 1.8.7 (2008-08-08) [i686-darwin8]
[2008-11-16 21:37:25] INFO  WEBrick::HTTPServer#start: pid=1394 port=3001
/opt/local/lib/ruby/gems/1.8/gems/actionpack-2.1.1/lib/action_controller/dispatcher.rb:36
new(output).dispatch_cgi(cgi, session_options)
(rdb:2)

El plugin del que tratamos declara una constante, EventObserver. Veamos si existe:

(rdb:2) EventObserver
EventObserver
(rdb:2) EventObserver.object_id
25840630

Perfecto.

Como en mi config/environment.rb indico que quiere recargar mis plugins, tenemos que:

(rdb:2) Dependencies.load_once_paths
[]

Y los load_paths son:

(rdb:2) pp Dependencies.load_paths
["/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/app/controllers/",
 "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/app",
 "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/app/models",
 "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/app/controllers",
 "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/app/helpers",
 "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/config",
 "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/vendor",
 "/opt/local/lib/ruby/gems/1.8/gems/rails-2.1.1/lib/../builtin/rails_info/",
 "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/vendor/plugins/background/lib",
 "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/vendor/plugins/localization-with-gettext/lib",
 "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/vendor/plugins/localized_url_helpers/lib",
 "/opt/local/lib/ruby/gems/1.8/gems/mbleigh-acts-as-taggable-on-1.0.2/lib",
 "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/vendor/plugins/thinking-sphinx/lib",
 "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/vendor/plugins/trabenet-auth/lib",
 "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/vendor/plugins/trabenet-core/lib",
 "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/vendor/plugins/trabenet-people-proxy/lib",
 "/Users/madtrick/programacion/trabesoluciones/trabenet-sources/trabenet-events/vendor/plugins/trabenet_subscriptions_proxy/lib",
 "/opt/local/lib/ruby/gems/1.8/gems/will_paginate-2.2.2/lib"]

Por tanto en cada nueva petición los plugins serán candidatos a ser recargados.

Veamos si esto es así con otra petición:

127.0.0.1 - - [16/Nov/2008:22:00:21 CET] "POST /events.xml HTTP/1.1" 201 1
- -> /events.xml
/opt/local/lib/ruby/gems/1.8/gems/actionpack-2.1.1/lib/action_controller/dispatcher.rb:36
new(output).dispatch_cgi(cgi, session_options)
(rdb:11) EventObserver.object_id
25840630

Pues no, aquí no se recarga nada!! porque como podemos apreciar , el object_id de la constante es el mismo que el de la primera petición, es decir son el mismo objeto.

2º.Segundo paso de la solución,descargar las constantes del entorno

La explicación a que el objeto representado por EventObserver sea el mismo esto es la siguiente:

Para empezar hay que tener en cuanta que para que una constante definida en un plugin tenga validez en Rails es necesario que ocurra una de las siguientes posibilidades:

  • Cuando se inicializa el entorno de Rails, para cada plugin se evalúa su fichero init.rb de forma que podemos utilizar llamadas a require para incluir el código contenido en el directorio /lib del plugin (y por tanto las constantes de este), en Rails.
  • Que las constantes sean cargadas de forma dinámica como ya explique en este post, de forma que cada una de estas constantes incluidas de esta forma pasa a formar parte del array Dependencies.autoloaded_constants.Esto ultimo es importante.

La importancia de la forma en la que se carga una constante en Rails, reside en que es lo que se recarga en cada petición y que es lo que se descarga tras cada peticion.Tras cada petición (si estamos en modo de desarrollo) se enviara un mensaje a Dependencies.clear.

def clear
      log_call
      loaded.clear
      remove_unloadable_constants!
end

La parte interesante de Dependencies.clear es la llamada al método remove_unloadable_constants!

def remove_unloadable_constants!
      autoloaded_constants.each { |const| remove_constant const }
      autoloaded_constants.clear
      explicitly_unloadable_constants.each { |const| remove_constant const }
end

En este método se borra la constante indicada del modulo o clase correspondiente. Como se puede apreciar se borraran tanto las constante “autocargadas” como las indicadas explicitaménte en Dependencies.explicitly_unloadable_constants.

A pesar de haber indicado en nuestro environment.rb que queríamos recargar los plugins (config.reload_plugins=true); para cada petición no se volverá a evaluar el fichero init.rb de cada plugin y por tanto, si nuestras constantes se cargan en Rails mediante llamadas a require (no existirán en el array Dependencies.autoloaded_constants) ; estas no volverán a ser cargadas debido a que no habrá necesidad de ello al no haber sido descargadas en Dependencies.explicitly_unloadable_constants.Para solucionar esto podemos optar por una de estas opciones:

  • No utilizar requires y dejar la carga de las constantes en manos de los mecanismos de Rails
  • Hacer uso de Depedencies.explicitly_unloadable_constants, indicando en dicho array las constantes que queremos que se descarguen tras cada petición

Volviendo a nuestro ejemplo, probemos una de estas opciones.Utilizaremos Depedencies.explicitly_unloadable_constants.

En mi caso, defino las constantes que quiero que se descargen en el init.rb del plugin (por eso de tener todo juntito : ) ).

Dependencies.explicitly_unloadable_constants = 'EventObserver'

Y lo probamos de nuevo con webrick:

=> Booting WEBrick...
=> Debugger enabled
=> Rails 2.1.1 application started on http://events.trabenet.local:3001
=> Ctrl-C to shutdown server; call with --help for options
[2008-11-17 00:01:15] INFO  WEBrick 1.3.1
[2008-11-17 00:01:15] INFO  ruby 1.8.7 (2008-08-08) [i686-darwin8]
[2008-11-17 00:01:15] INFO  WEBrick::HTTPServer#start: pid=1442 port=3001
/opt/local/lib/ruby/gems/1.8/gems/actionpack-2.1.1/lib/action_controller/dispatcher.rb:36
new(output).dispatch_cgi(cgi, session_options)
(rdb:2) EventObserver.object_id
25840560

Lanzamos otra petición.

127.0.0.1 - - [17/Nov/2008:00:01:17 CET] "POST /events.xml HTTP/1.1" 201 1
- -> /events.xml
/opt/local/lib/ruby/gems/1.8/gems/actionpack-2.1.1/lib/action_controller/dispatcher.rb:36
new(output).dispatch_cgi(cgi, session_options)
(rdb:11) EventObserver.object_id
27418300

Como se puede apreciar en esta ocasión son objetos distintos y por tanto realmente se recargado la constante.

Tras hacer esto, ya podemos observar con tranquilidad a nuestros modelos desde el plugin.

A partir de aquí voy a explicar el porque de la necesidad de recargar el contenido de nuestros plugins para poder observar a un modelo en modo de desarrollo.

Como explique antes, si no se recarga el contenido de nuestros plugins y en concreto la clase donde definimos el observador del modelo, tendremos un problema.¿Y por qué?

Primero , algunas cosillas a tener en consideración:

  • Los Observadores en Rails utilizan el patrón Singleton
  • Donde se instancian los observadores

Empezaremos por el segundo punto.

En Rails, existen dos puntos prefijados donde se instancian los observadores correspondientes a cada modelo.El primero es en el fichero initializer.rb en el metodo de clase process donde se invoca el metodo load_observers

def process
...
load_observers
end

Con load_observers como sigue,

def load_observers
      if gems_dependencies_loaded && configuration.frameworks.include?(:active_record)
        ActiveRecord::Base.instantiate_observers
      end
end

En ActiveRecord::Base.instantiate_observers crearemos para cada modelo sus observadores y será aquí donde comienzen nuestros quebraderos de cabeza.

def instantiate_observers
        return if @observers.blank?
        @observers.each do |observer|
          if observer.respond_to?(:to_sym) # Symbol or String
            temp = observer.to_s.camelize.constantize
            temp.instance
          elsif observer.respond_to?(:instance)
            observer.instance
          else
            raise ArgumentError, "#{observer} must be a lowercase, underscored class name (or an instance of the class itself) responding to the instance method. Example: Person.observers = :big_brother # calls BigBrother.instance"
          end
        end
end

POR TERMINAR, lo hare en otro post : )

Comment » | programacion, rails, ruby

Rails: Orden de carga de los plugins

October 19th, 2008 — 04:57 pm
madtrick:~/programacion/ror/diary madtrick$ ruby script/server webrick
=> Booting WEBrick...
/opt/local/lib/ruby/gems/1.8/gems/activerecord-2.1.0/lib/active_record/base.rb:1667:in `method_missing': undefined local variable or method `acts_as_authorizable' for #<Class:0x336e194> (NameError)
        from /Users/madtrick/programacion/ror/diary/app/models/user.rb:21
        from /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:215:in `load_without_new_constant_marking'
        from /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:215:in `load_file'
        from /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:354:in `new_constants_in'
        from /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:214:in `load_file'
        from /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:95:in `require_or_load'
        from /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:60:in `depend_on'
        from /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:456:in `require_dependency'
        from /Users/madtrick/programacion/ror/diary/vendor/plugins/authentication_system/lib/authentication_system.rb:1
        from /opt/local/lib/ruby/vendor_ruby/rubygems/custom_require.rb:27:in `gem_original_require'
        from /opt/local/lib/ruby/vendor_ruby/rubygems/custom_require.rb:27:in `require'
        from /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:509:in `require'
...

Eso fue lo que me encontre hoy al ir a probar unas cosillas en Rails.

El problema deriva del orden en el que se cargan los plugins residentes en $RAILS_ROOT/vendor/plugins, que a parecer es lexicografica, con lo cual yo me encontraba en la situacion de que el plugin A hacia uso del modelo M pero a su vez este modelo hacia uso de un metodo “mixeado” en ActiveRecord::Base por el plugin B.

Por lo tanto, como A se cargaba antes que B ya teniamos montado el belen.

Tras preguntarle al señor google que como arreglabamos este desaguisado, este primero me dijo que ya habia mas gente con este problema allá por el 2006 (1) y (2) y que finalmente la solucion por la que se opto fue la siguiente :

En environment.rb ponemos config.plugins = {:plugin1,:plugin2,:all} pudiendo controlar asi, el orden en el que se carga dichos plugins,cuales se cargan, etc.El simbolo :all, evidentemente se utiliza para cargar todos los demas plugins.

Actualizacion:

Soy algo cortito, si me hubiera molestado en leerme lo que ponia en environment.rb no habria tenido ningun problema.Extraido de environment.rb:

# Only load the plugins named here, in the order given. By default, all plugins
# in vendor/plugins are loaded in alphabetical order.
# :all can be used as a placeholder for all plugins not explicitly named

1 comment » | programacion, rails

Rails: Errores al instalar login_generator (1.2.2) en Rails 2.1.0

October 9th, 2008 — 05:07 pm

Si queremos utilizar el gem (o es “la” gem, no tengo muy claro el genero) en rails 2.1.0 nos vamos a encontrar con lo siguientes problemas tras haberlo instalado:

Primer problema,cuando vamos a generar nuestro sistema de login.La extension de las plantillas nos causa el primer problemilla:

madtrick$ ruby script/generate login account
      create  lib/login_system.rb
      create  app/controllers/account_controller.rb
      create  test/functional/account_controller_test.rb
      create  app/helpers/account_helper.rb
      create  app/views/layouts/scaffold.rhtml
No such file or directory - /opt/local/lib/ruby/gems/1.8/gems/rails-2.1.0/ 
lib/rails_generator/generators/components/scaffold/templates/layout.rhtml

El error deriva de que el script que genera el login sigue considerando que las plantillas tienen extension .rhtml, cuando eso ya no es asi.Ahora las plantillas utilizan la extension .html.erb

Por lo tanto para solucionar este problema lo que debemos de hacer es modificar la extension del fichero solicitado en el generador , login_generator.rb en este caso.

En mi caso el fichero login_generator.rb se encuentra en /opt/local/lib/ruby/gems/1.8/gems/login_generator-1.2.2 en otros sistemas puede que sea /usr/local/lib/ruby/gems/1.8/gems/login_generator-1.2.2 o /ruby/local/lib/ruby/gems/1.8/gems/login_generator-1.2.2

Pasaremos de:

17
m.template "scaffold:layout.rhtml", "app/views/layouts/scaffold.rhtml"

A:

17
m.template "scaffold:layout.html.erb", "app/views/layouts/scaffold.rhtml"

Si ahora volvemos a intentar generar el sistema de login no tendremos ningun problema:

madtrick$ ruby script/generate login account
   identical  lib/login_system.rb
   identical  app/controllers/account_controller.rb
   identical  test/functional/account_controller_test.rb
   identical  app/helpers/account_helper.rb
overwrite app/views/layouts/scaffold.rhtml? \ 
 (enter "h" for help) [Ynaqdh] Y
       force  app/views/layouts/scaffold.rhtml
      create  public/stylesheets/scaffold.css
      create  app/views/account
      create  app/views/account/welcome.rhtml
      create  app/views/account/login.rhtml
      create  app/views/account/logout.rhtml
      create  app/views/account/signup.rhtml
      create  README_LOGIN

Segundo problema.Tras solventar lo anterior, estamos ansiosos por probar nuestro sistema de login asi que vamos a nuestro navegador y tecleamos: http://localhost:3000/account/login

En la anterior direccion asumimos lo siguiente:

  • Que el controlador que hemos creado para gestionar las funciones de login tiene por nombre AccountController
  • Que tenemos una ruta en routes.rb que funciona para esta peticion

Si probamos esto una bonita pantalla de error nos dara la bienvenida:

ArgumentError in AccountController#login
 
wrong number of arguments (0 for 1)

Bien, para solucionar esto debemos de modificar todos los @request.method que nos encontremos en nuestro controlador por @request.request_method para evitar colisiones con la palabra reservada de ruby method

Tras solucionar esto volvemos a probar nuestro sistema de login, pero un vez mas nos la dan con queso:

Segundo problema,

 NoMethodError in AccountController#login
 
You have a nil object when you didn't expect it!
The error occurred while evaluating nil.request_method

Bien, bien.Veamos que pasa aqui.Como podemos observar el mensaje de error nos dice que el error ocurrio al tratar de evaluar nil.request_method.Pero oiga, como que nil!! Si yo estoy viendo @request.request_method.

Lo que pasa aqui es que en esta version de Rails, @request (al igual que @params, @session, @flash, @cookies, @headers, @response) ha quedado en desuso en favor de los “accessor methods” que tienen el mismo nombre que las variables pero sin la arroba.Es decir donde tenemos @request.request_method pasamos a tener request.request_method y asi sucesivamente.

Probamos los cambios y nos encontramos con el tercer problema:

 NoMethodError in Account#login
 
Showing account/login.rhtml where line #1 raised:
 
undefined method `start_form_tag' for #<ActionView::Base:0x35a61dc>

login.rhtml luce de la siguiente forma:

<%%= start_form_tag :action=> "login" %>
...
<%%= end_form_tag %>

start_form_tag, tambien esta en desuso, en su lugar debemos de utilizar form_tag.De esta forma login.rhtml nos queda de la siguiente forma:

<% form_tag '/account/login' do  %>
...
<% end %>

Ahora por lo menos podemos probar nuestro login.Lo probamos y tras hacer click en el boton de login,nos encontramos con el cuarto error,

 NoMethodError in AccountController#login
 
undefined method `find_first' for #<Class:0x365c8b0>

find_first es otro de los metodos en desuso.Debemos de cambiarlo por find, con el simbolo :first como primer parametro y la opcion :conditions con la condicion que teniamos en find_first.

Es decir, pasamos de:

find_first(["login = ? AND password = ?", login, sha1(pass)])

A:

find(:first,:conditions=>["login = ? AND password = ?", login, sha1(pass)])

Si volvemos a probar, adivinad que pasa….si, otro error.Por el quinto vamos,otra vez por elementos en desuso.En este caso se trata de la variable @session residente en el fichero login_system.rb.Cambiamos esta y las demas variables existentes en dicho fichero a sus “accesor methods” , guardamos y volvemos a probar… e-voilà!!

Funciona!!

Espero que todo este rollo le sea de ayuda a alguien : )

1 comment » | programacion, rails

Rails: Claves foraneas en las migraciones

October 8th, 2008 — 10:37 pm

Hay varias formas para indicar la restriccion que implica una clave foranea en nuestras tablas cuando estamos utilizando las migraciones para definir dichas tablas.

La primera y mas “tosca” consiste en indicar a pelo dicha restriccion:

class CreateOrder < ActiveRecord::Migration
   def self.up
      t.string :name
      t.integer :user_id
 
      execute "alter table table add constraint fk_table_user \ 
         foreign key (user_id) references users (id)"
 
   end
 
   ...
 
end

Pero tambien podemos aprovechar algun plugin para facilitarnos el trabajo.Es aqui donde entra en juego este plugin que descubri hoy.

Con este plugin podemos rehacer lo anterior de la siguiente forma:

class CreateOrder < ActiveRecord::Migration
   def self.up
      t.string :name
      t.integer :user_id
 
      t.foreign_key :users,:user_id,:id
 
   end
 
   ...
 
end

Bastante mejor, no?.Este plugin tiene disponible adaptadores para mysql y postgresql.

Aqui teneis los otros plugins que tienen disponibles.

2 comments » | programacion, rails

Ruby,Rails: “metodos encadenados”

September 19th, 2008 — 04:13 pm

Una de las cosas que mas me gustan de Ruby es su dinamismo y la posibilidad de “abrir” las clases y modulos para añadir o redefinir metodos totalmente dinamicamente y en tiempo de ejecucion.

Esta funcionalidad junto con la posibilidad de saber cuando un modulo ha sido incluido en una clase nos permite añadir funcionalidad a las clases que incluyen el modulo sin que estan tengan que percibirlo.

Un ejemplo:

class Module
  def included(base)
    p "Included module " + name
 
    without = "test_method_#{name}_without_filters".to_sym
    with ="test_method_#{name}_with_filters".to_sym
    base.class_eval do
      alias_method without, :test_method
      alias_method :test_method, with
    end
 
  end
end

Lo que he hecho es redefinir el metodo included para que todos los modulos indiquen que ha sido incluidos y que hagan un alias sobre el metodo “test_method“.

Hay que tener en cuenta que alias_method no sobreescribe el metodo original, sino que hace una guarda una referencia a este, de forma que aun pueda ser invocado.

module A
 
  def test_method_A_with_filters
    p "Doing something really important in module A"
    test_method_A_without_filters
  end
 
end
 
module B
 
  def test_method_B_with_filters
    p "Doing something really important in module B"
    test_method_B_without_filters
  end
 
end
 
 
class Test
 
  def test_method
      p "Inside method of class A"
 
      p "Backtrace:"
      caller.each do |text| p text end
      return
  end
 
  include A
  include B
end

Creamos los modulos, la clase e incluimos los primeros en dicha clase.Hasta aqui nada nuevo bajo el sol.

Probemos entonces el codigo en irb,

madtrick:~/programacion/ruby madtrick$ irb
irb(main):001:0> require "test30"
"Included module A"
"Included module B"
=> true
 
irb(main):002:0> Test.new.test_method
"Doing something really important in module B"
"Doing something really important in module A"
"Inside method of class A"
 
"Backtrace:"
"./test30.rb:14:in `test_method_B_without_filters'"
"./test30.rb:32:in `test_method'"
"(irb):2:in `irb_binding'"
"/opt/local/lib/ruby/1.8/irb/workspace.rb:52:in `irb_binding'"
":0"
=> nil

Si hechamos un vistazo a la salida generada en irb,

irb(main):001:0> require "test30"
"Included module A"
"Included module B"
=> true

Los modulos se incluyen en la clase en el orden deseado

irb(main):002:0> Test.new.test_method
"Doing something really important in module B"
"Doing something really important in module A"
"Inside method of class A"

Ahora creamos un objeto de tipo Test e invocamos sobre él el metodo “test_method”…!Pero que ha pasado aqui¡!De donde salen esa lineas¡No no pongamos nerviosos y averigüemos que pasa.

Cuando redefinimos el metodo included de la clase module, realizamos unos alias.
Para el modulo A:

  • Nuevo nombre : test_method_A_without_filters,nombre antiguo: test_method
  • Nuevo nombre : test_method, nombre_antiguo test_method_A_with_filters

Para el modulo B , lo mismo pero cambiando la A por una B.

El resultado de estos alias es el que vemos en la salida de irb, al invocar sobre un objeto de la clase Test
su metodo de instancia “test_method” lo primero que vemos no es “Inside method of class A” sino “Doing something really important in module B“,seguido de un “Doing something really important in module A” para finalmente ver nuestro esperado “Inside method of class A“,esto es asi porque:

  1. El ultimo modulo incluido fue el B, por tanto su alias “sobreescribioo” al creado por el modulo A.Es decir al hacer Test.new.test_method en realidad estamos invocando a test_method_B_with_filters
  2. Tras ejecutar este metodo, invocamos al metodo “original”, que en este caso no es el definido en la clase, sino el alias realizado por el modulo A
  3. Tras ejecutar este ultimo alias, volvemos a invocar al metodo original (alias de “test_method_A_without_filters”) quien finalmente si que es el metodo de la clase Test

Utilizando caller podemos saber la stacktrace de nuestro programa y asi saber cual fue la direccion de los mensajes.Esto lo podemos ver en este trozo de la salida de irb,

"Backtrace:"
"./test30.rb:19:in `test_method_B_without_filters'"
"./test30.rb:30:in `test_method'"
"(irb):2:in `irb_binding'"
"/opt/local/lib/ruby/1.8/irb/workspace.rb:52:in `irb_binding'"
":0"

Teniendo en cuenta que las primeras lineas indican los ultimos mensajes.Tenemos que:

En la linea 30 vamos enviar un mensaje tras recibir uno con destino a “test_method” alias de “test_method_B_with_filters“,

28
29
30
31
  def test_method_B_with_filters
    p "Doing something really important in module B"
    test_method_B_without_filters
  end

En la linea 19 vamos a enviar un mensaje tras recibir uno con destino “test_method_B_without_filters
el cual es un alias de “test_method“,

17
18
19
20
 def test_method_A_with_filters
    p "Doing something really important in module A"
    test_method_A_without_filters
  end

Como se puede apreciar es una forma bastante sencilla de añadir funcionalidad sin influir para nada en las clases.Y de aqui es de donde saque el titulo, porque literalmente estamos encadenando metodos.

Para terminar
Si alguien le extraña la coletilla _filters que utilize para hacer los alias , tiene su explicacion.Ultimamente estoy curioseando en el codigo de Rails, y para poder seguir un orden me voy guiando por el orden el que se procesan las peticiones ,gracias al debugger para webrick y otros servidores.Bueno la cosa es que llegue al fichero filters.rb donde me encontre con este codigo,

 module InstanceMethods # :nodoc:
      def self.included(base)
        base.class_eval do
          alias_method_chain :perform_action, :filters
          alias_method_chain :process, :filters
        end
      end
...

¿alias_method_chain?, no me sonaba para nada, asi que tras una consulta a google llegue a esto:

All over the internals of Rails you’ll find code like this in a module:

module Layout #:nodoc:
def self.included(base)
base.extend(ClassMethods)
base.class_eval do
alias_method :render_with_no_layout, :render
alias_method :render, :render_with_a_layout
# … etc
This makes it so that when the module is included into the base class, it adds behavior onto some method in that class without the method having to be aware of the fact that it’s being enhanced

En resumidas cuentas, que alias_method_chain hace lo que acabos de hacer nosotros pero un poquillo mejor ya que se puede aplicar a cualquier metodo, no solo a test_method como en nuestro caso.

Volviendo a lo de la coletilla _filters, la explicacion es que donde vi el primer ejemplo de alias_method_chain fue en el codigo indicado arriba y entonces fue lo primero que se me ocurrio.

1 comment » | programacion, rails, ruby

« Previous Entries