Rails: Observar un modelo desde un plugin == problemas
November 16th, 2008 — 11:52 pmRails 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 : )
Rails: Carga dinamica de constantes
November 14th, 2008 — 09:09 pmAntes 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.
Rails: Orden de carga de los plugins
October 19th, 2008 — 04:57 pmmadtrick:~/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
Rails: Errores al instalar login_generator (1.2.2) en Rails 2.1.0
October 9th, 2008 — 05:07 pmSi 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 : )

