19N

November 19th, 2008 — 11:25 pm

IMG_1264

Comment » | protesta

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: Carga dinamica de constantes

November 14th, 2008 — 09:09 pm

Antes de empezar y por si alguien quiere, aquí tiene una brevísima intruducción a las constantes en Ruby

Vamos entonces con un ejemplo sencillo en una consola de Ruby (sin el entorno de Rails cargado):

madtrick::ruby$ irb
irb(main):001:0> Constante
NameError: uninitialized constant Constante
        from (irb):1
irb(main):002:0>

Como la constante no esta definida en el contexto donde la reclamamos, se genera una excepción.

Pero si queremos, podemos capturar esta “referencia a una constante indefinida” dentro del contexto en el que nos encontramos, redefiniéndo el método const_missing de Object:

irb(main):010:0> class << Module
irb(main):011:1> def const_missing(name)
irb(main):012:2> p "Constante infenida: #{name}"
irb(main):013:2> end
irb(main):014:1> end
=> nil
irb(main):015:0> Constante
"Constante infenida: Constante"
=> nil

Si queremos que la excepción continúe debemos de indicarlo con un raise al final del método:

irb(main):016:0> class << Module
irb(main):017:1> def const_missing(name)
irb(main):018:2> p "Constante infenida: #{name}"
irb(main):019:2> raise
irb(main):020:2> end
irb(main):021:1> end
=> nil
irb(main):022:0> Constante
"Constante infenida: Constante"
RuntimeError: 
        from (irb):19:in `const_missing'
        from (irb):22
        from :0

Rails , se aprovecha de esta funcionalidad para cargar dinámicamente constantes.¿Cómo?.Éso es lo que explicaremos a continuación:

Para seguir esta explicación de una manera mas cómoda, recomiendo tener a mano el fichero dependencies.rb incluido en active-support.

1º.HOYGA NO HENCUENTRO LA CONSTANTE

Rails , al igual que hicimos nosotros antes , abre las clases Module y Class para redefinir el método const_missing.

class Module #:nodoc:
  ...
  # Use const_missing to autoload associations so we don't have to
  # require_association when using single-table inheritance.
  def const_missing(class_id)
    ActiveSupport::Dependencies.load_missing_constant self, class_id
  end
...
end
 
class Class
  def const_missing(const_name)
    if [Object, Kernel].include?(self) || parent ==
      super
    else
      begin
        begin
          ActiveSupport::Dependencies.load_missing_constant self, const_name
        rescue NameError
          parent.send :const_missing, const_name
        end
      rescue NameError => e
        # Make sure that the name we are missing is the one that caused the error
        parent_qualified_name = ActiveSupport::Dependencies.qualified_name_for parent, const_name
        raise unless e.missing_name? parent_qualified_name
        qualified_name = ActiveSupport::Dependencies.qualified_name_for self, const_name
        raise NameError.new("uninitialized constant #{qualified_name}").copy_blame!(e)
      end
    end
  end
end

No entiendo porque en el método const_missing redefinido en Class comprobamos si self es Kernel ya que Kernel es un Module y por tanto nunca invocaria al const_missing de una clase.
Pero ambas “redefiniciones” nos llevan al mismo punto: ActiveSupport::Dependencies.load_missing_constant

2º.Carguémos la constante

Una vez dentro de load_missing_constant lo primero que se lleva a cabo es una comprobacion para determinar si el modulo o clase que desea cargar la constante es Kernel para en caso afirmativo intercambiar este por Object, si Object no tiene definida la constante deseada o devolver el valor de la constante en caso contrario.Esto tiene sentido ya que Kernel es un mixin de Object y por tanto sus constantes lo serán a la vez de Object.

if from_mod == Kernel
        if ::Object.const_defined?(const_name)
          log "Returning Object::#{const_name} for Kernel::#{const_name}"
          return ::Object.const_get(const_name)
        else
          log "Substituting Object for Kernel"
          from_mod = Object
        end
      end

En caso de no encontrarse dicha constante en Object pasamos a lo siguiente:

from_mod = Object if from_mod.name.blank?
 
      unless qualified_const_defined?(from_mod.name) && from_mod.name.constantize.object_id == from_mod.object_id
        raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!"
      end

Con lo que comprobamos:

  • Si estamos ante un modulo anónimo
  • La segunda parte no me queda muy claro cuando se puede dar

En la siguiente linea de código, se me plantea otra nueva duda

raise ArgumentError, "#{from_mod} is not missing constant #{const_name}!" if uninherited_const_defined?(from_mod, const_name)

Si estamos buscando una constante (const_name) no definida en from_mod, ¿Por qué uninherited_const_defined?(from_mod,const_name) habría de devolver true? Uninherited_const_defined? es como sigue:

if Module.method(:const_defined?).arity == 1
      # Does this module define this constant?
      # Wrapper to accomodate changing Module#const_defined? in Ruby 1.9
      def uninherited_const_defined?(mod, const)
        mod.const_defined?(const)
      end
    else
      def uninherited_const_defined?(mod, const) #:nodoc:
        mod.const_defined?(const, false)
      end
    end

Dependiendo de la versión de Ruby ( 1.9 o inferior) utilizara un método un otro con mismo resultado: invocar const_defined? sobre el módulo.Y esto es lo que no entiendo, si const_name genero un const_missing en dicho modulo, porque ahora no habría de hacerlo….

En fin, sigamos.

qualified_name = qualified_name_for from_mod, const_name
path_suffix = qualified_name.underscore
name_error = NameError.new("uninitialized constant #{qualified_name}")

qualified_name_for devuelve el nombre cualificado para el modulo y constante dados

# Return the constant path for the provided parent and constant name.
    def qualified_name_for(mod, name)
      mod_name = to_constant_name mod
      (%w(Object Kernel).include? mod_name) ? name.to_s : "#{mod_name}::#{name}"
    end

Ejemplos:

  • Para mod A y name C , tendriamos A::C
  • Para mod A::C y name D, tendriamos A::C::D
  • Para mod Object (o Kernel) y name C, tendriamos C


path_suffix = qualified_name.underscore
.De esta linea obtendremos lo siguiente, ejemplos:

  • Para qualified_name A::C , tendremos a/c
  • Para qualified_name A::B::C, tendremos a/b/c
  • Para qualified_name A, tendremos a

Y por ultimo name_error = NameError.new prepara un objeto de clase NameError para el caso de que no podamos hacer nada por cargar la constante.

3º.¿Pero cargamos la constante o no?

Hasta ahora no hemos realizado ningún intento por cargar la constante perdida (buen titulo para una peli…Las aventuras de la constante perdida), pero a partir de ahora es cuando empieza lo divertido.

Para empezar intentamos buscar un directorio en nuestro load_path (definido en Dependencies.load_paths) que albergue una ruta igual a path_suffix (indicamos como se determina dicho path_suffix, lineas mas arriba) con terminación “.rb”.

file_path = search_for_file(path_suffix)

Es decir, si nuestro path_suffix es “a/b/c.rb”, buscamos algún path de entre los que tenemos disponibles para cargar ficheros que contenga la ruta “a/b/c.rb”.Ejemplo:

Si nuestro load_path esta formado por:

  • /home/lib/funny_lib, la ruta completa seria /home/lib/funny_lib/a/b/c.rb
  • /home/lib/dll_hell, la ruta completa seria /home/lib/dll_hell/a/b/c.rb
  • /home/lib/act_as_pr0n, la ruta completa seria /home/lib/act_as_pr0n/a/b/c.rb

El primero de los anteriores que exista será el valor devuelto por search_for_file o nil en caso que ninguno de ellos exista.

Lo siguiente que nos encontramos es una gran estructura condicional

      if file_path && ! loaded.include?(File.expand_path(file_path)) # We found a matching file to load
        require_or_load file_path
        raise LoadError, "Expected #{file_path} to define #{qualified_name}" unless uninherited_const_defined?(from_mod, const_name)
        return from_mod.const_get(const_name)
      elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix)
        return mod
      elsif (parent = from_mod.parent) && parent != from_mod &&
            ! from_mod.parents.any? { |p| uninherited_const_defined?(p, const_name) }
        # If our parents do not have a constant named +const_name+ then we are free
        # to attempt to load upwards. If they do have such a constant, then this
        # const_missing must be due to from_mod::const_name, which should not
        # return constants from from_mod's parents.
        begin
          return parent.const_missing(const_name)
        rescue NameError => e
          raise unless e.missing_name? qualified_name_for(parent, const_name)
          raise name_error
        end
      else
        raise name_error
      end

Vayamos por partes

3.1 Cargando la constante desde un fichero

if file_path && ! loaded.include?(File.expand_path(file_path)) # We found a matching file to load
        require_or_load file_path
        raise LoadError, "Expected #{file_path} to define #{qualified_name}" unless uninherited_const_defined?(from_mod, const_name)
        return from_mod.const_get(const_name)

Si se da que tenemos un file_path, y este no esta cargado actualmente, lo cargamos mediante require_or_load.Este método necesita para el solo otra entrada en el blog de lo amplio que es.

Aquí podemos apreciar por tanto otra convención de Rails.Es decir, si por ejemplo tenemos un plugin que añade una constante a ActiveRecord (sea la constante Constant) y queremos que Rails cargue automáticamente esta constante sin tener que indicarlo nosotros explícitamente mediante un require, debemos de:

  • En el directorio /lib de nuestro plugin crear otro que se llame active_record
  • Dentro de este ultimo directorio crear un fichero que se llame constant.rb

Si no cumplimos dicha convencion, tras cargar el fichero podemos obtener el siguiente error

raise LoadError, "Expected #{file_path} to define #{qualified_name}" unless uninherited_const_defined?(from_mod, const_name)

Hay que tener cuidado con este método ya que como se indico anteriormente, el file_path elegido sera el primero que exista, es decir que si hay dos plugins con la misma estructura y en ninguno de ellos requerimos explícitamente el fichero donde se define la constante el primero en ser elegido ocultara la presencia del otro.

3.2

elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix)
        return mod

con autoload_module! como sigue,

# Attempt to autoload the provided module name by searching for a directory
# matching the expect path suffix. If found, the module is created and assigned
# to +into+'s constants with the name +const_name+. Provided that the directory
# was loaded from a reloadable base path, it is added to the set of constants
# that are to be unloaded.
    def autoload_module!(into, const_name, qualified_name, path_suffix)
      return nil unless base_path = autoloadable_module?(path_suffix)
      mod = Module.new
      into.const_set const_name, mod
      autoloaded_constants << qualified_name unless load_once_paths.include?(base_path)
      return mod
    end

Como bien indica el comentario, autoload_module! primero determina mediante autoloadable_module? si existe un path de entre los disponibles en nuestro load_path al cual añadiéndole el path_suffix, exista como directorio.Si es así, continuamos, sino devolvemos nil.

Si existe dicho path, creamos un modulo anónimo y añadimos la constante que falta a from_mod con el valor de este modulo anónimo.
Para terminar en autoload_module!, incluimos la constante cualificada en Dependencies.autoloaded_constants a menos que el directorio sea un directorio que solo se puede cargar una vez durante la ejecución de Rails (definidos en Dependencies.load_once_paths).

3.3 Ni fichero, ni directorio.¿Dónde coj***s esta la constante?

Llegados a este punto es porque ningún load_path de los disponibles alberga o bien un directorio o bien un fichero que siga la estructura designada por path_suffix.

elsif (parent = from_mod.parent) && parent != from_mod &&
            ! from_mod.parents.any? { |p| uninherited_const_defined?(p, const_name) }
        # If our parents do not have a constant named +const_name+ then we are free
        # to attempt to load upwards. If they do have such a constant, then this
        # const_missing must be due to from_mod::const_name, which should not
        # return constants from from_mod's parents.
        begin
          return parent.const_missing(const_name)
        rescue NameError => e
          raise unless e.missing_name? qualified_name_for(parent, const_name)
          raise name_error
        end

Resumiendo que esto se alarga demasiado….en este ultimo bloque, buscaremos la constante en los padres de from_mod.La explicacion a como se obtienen los padres para un path dado es la siguiente:

En introspection.rb:

# Returns all the parents of this module according to its name, ordered from
# nested outwards. The receiver is not contained within the result.
#
# module M
# module N
# end
# end
# X = M::N
#
# p M.parents # => [Object]
# p M::N.parents # => [M, Object]
# p X.parents # => [M, Object]
#

y si tampoco esta definida en ninguno de sus padres, invocaremos denuevo todo el proceso desde el principio sobre el padre de from_mod

return parent.const_missing(const_name)

Mencionar que si no encontramos la constante en ningun sitio se lanzara la excepcion de clase NameError creada anteriormente.

Comment » | Uncategorized

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

Nach oben

« Previous Entries     Next Entries »