Filtros en Ruby on Rails

4
marzo
2006

El otro día en el seminario hubo una pregunta que demostró que yo no debería haber estado allí arriba, porque no tenía respuestas para ese tipo de preguntas.

La pregunta en cuestión era sobre cómo Ruby y Rails ejecutan los filtros antes y después de los métodos. La respuesta es bastante sencilla, los métodos que nosotros escribimos en los controladores no son invocados “directamente”, la llamada HTTP pasa a lo largo de varios objetos y varios métodos antes de llegar a nuestro objeto y a nuestro método. Sería bastante sencillo deducir que cualquiera de esos métodos podría ponerse a ejecutar los filtros. La realidad no es tan sencilla.

El camino de una petición del usuario es más o menos el siguiente: dependiendo de como ejecutemos nuestra aplicación la petición va dirigida a public/dispatch.rb (en el caso de utilizar WEBrick), public/dispatch.cgi (con Apache+CGI, por ejemplo) o a public/dispatch.fcgi (utilizando lighttpd+FastCGI, por ejemplo). Los dos primeros archivos cargan el archivo dispatcher.rb de Rails (en mi ordenador está en /usr/lib/ruby/gems/1.8/gems/rails-1.0.0/lib) y llaman a su método dispatch. Este método hace algunas cosas, pero la que nos interesa es que obtiene del sistema de rutas el controlador asociado a la petición y llama a su método process (el código del controlador se encuentra en (...) /actionpack-1.11.2/lib/action_controller/base.rb.

Entre algunas cosas el método process llama a otro método del controlador llamado perform_action (esto no es del todo exacto, llama a este método por defecto, si se le especifica otro como parámetro utiliza el método que le proporcionemos).

El método perform_action comprueba que el nombre de la acción que queremos invocar existe en nuestro controlador (un poco de introspección) y en caso afirmativo utiliza el método send que todo los objetos de Ruby tienen para enviarse un mensaje a si mismo, y como parámetro el nombre de la acción. De esta forma nuestra acción es invocada.

Pero en todo ese camino no hay ninguna línea de código que sea apply_filters o similar. Para ver que está pasando cuando añadimos los filtros hay que ir al archivo filters.rb, situado en el mismo directorio que el anterior. En este archivo se define un módulo Filters que mediante el método append_features (invocado por Ruby cuando se incluye un módulo en una clase) hace que el controlador base cree un alias para el método perform_action (llamado perform_action_without_filters) y otro alias llamado perform_action (con lo que el original queda ocultado) que invoca en realidad a perform_action_with_filters, que es parte del módulo Filters (el primer alias es necesario ya que los módulos ocultan a los métodos de la clase que los incluye y no se permite acceder al método original de la clase utilizando construcciones del lenguaje).

Este método es que realiza la llamada a los filtros antes y después de invocar al método (llamando a perform_action_without_filters, y de hecho sólo desde dentro del módulo Filters se podría realizar esto, ya que sólo dentro del módulo Filters se conocen los filtros que el usuario ha añadido.

Y como yo al menos no he comprendido lo que pongo arriba, me aclararé con un mini-ejemplo:

class MiniControladorBase
  def perform_action(action_name)
    send(action_name)
  end
  
  include Filters
end

module Filters
  def self.append_features(base)
    super
    base.class_eval {
      alias_method :perform_action_without_filters, :perform_action
      alias_method :perform_action, :perform_action_with_filters
    }
  end
  
  def perform_action_with_filters
    # prefiltros
    perform_action_without_filters
    # postfiltros
  end
end

Es un poco complicado de ver, pero ¿cuantos lenguajes de programación permiten añadir código ejecutable antes y después de un método y que las clases que lo utilizan no se de cuenta de este cambio? (sin modificar el código original, se supone). Yo ahora se de uno, con posibilidades de que otros dos más puedan hacer algo parecido.


Los comentarios están cerrados.

Tu servidor sin límites: 20GB de espacio, 1TB de transferencia, 1 dominio gratuito. Por 1.5€ al mes utilizando el código "RUIDOBLANCO" en DreamHost. Más información.