2012-01-29 8 views
21

Me encanta the autoload functionality of Ruby; sin embargo, es going away in future versions of Ruby ya que nunca fue seguro para subprocesos.Clases de carga automática en Ruby sin su `autoload`

Así que en este momento me gustaría fingir que ya se ha ido y escribir mi código sin él, por implementando el mecanismo de carga lenta yo. Me gustaría implementarlo de la manera más sencilla posible (no me importa la seguridad de hilos en este momento). Ruby debería permitirnos hacer esto. inicio

Vamos mediante el aumento de una clase const_missing:

class Dummy 
    def self.const_missing(const) 
    puts "const_missing(#{const.inspect})" 
    super(const) 
    end 
end 

Ruby llamar a este método especial cuando tratamos de hacer referencia a una constante bajo 'dummy' que le falta, por ejemplo, si tratamos de hacer referencia a "Dummy: : Hola ", llamará al const_missing con el símbolo :Hello. Esto es exactamente lo que necesitamos, así que vamos a ir más lejos:

class Dummy 
    def self.const_missing(const) 
    if :OAuth == const 
     require 'dummy/oauth' 
     const_get(const)  # warning: possible endless loop! 
    else 
     super(const) 
    end 
    end 
end 

Ahora bien, si hacemos referencia "Dummy :: OAuth", se requerirá el archivo "maniquí/oauth.rb" que se espera para definir el " Dummy :: OAuth "constante. Existe la posibilidad de un ciclo infinito cuando llamamos al const_get (ya que puede llamar al const_missing internamente), pero evitar eso está fuera del alcance de esta pregunta.

El gran problema es que toda esta solución se rompe si existe un módulo llamado "OAuth" en el espacio de nombres de nivel superior. Al hacer referencia a "Dummy :: OAuth" se saltará el const_missing y simplemente devolverá el "OAuth" del nivel superior. La mayoría de las implementaciones de Ruby también harán una advertencia acerca de esto:

warning: toplevel constant OAuth referenced by Dummy::OAuth 

This was reported as a problem way back in 2003 pero no pudieron encontrar evidencia de que el equipo central Ruby era siempre preocupado por esto. Hoy en día, las implementaciones más populares de Ruby tienen el mismo comportamiento.

El problema es que const_missing se salta silenciosamente a favor de una constante en el espacio de nombres de nivel superior. Esto no sucedería si se declarara "Dummy :: OAuth" con la funcionalidad de Ruby autoload. ¿Alguna idea de cómo solucionar esto?

+0

Esto parece como una sugerencia tonta, pero se puede mirar en la fuente de C 'autoload'? Estoy seguro de que puedes encontrarlo en alguna parte de la fuente de Ruby. Si no puede hacerlo en Ruby, existe la opción de crear una extensión C (que tiene acceso a la parte inferior del intérprete). – Linuxios

+0

Es una opción. – mislav

+0

suena como algo de fuerza bruta, pero ¿no podrías 'eliminar_contar' en la clase de nivel superior? – phoet

Respuesta

5

Esto se planteó en un boleto de Rails hace un tiempo y cuando lo investigué parecía no haber forma de evitarlo. El problema es que Ruby buscará en los antepasados ​​antes de llamar al const_missing y dado que todas las clases tienen Object como antecesor, siempre se encontrarán las constantes de nivel superior. Si puede restringirse sólo a través de módulos de namespacing entonces todo funcionará, ya que no tienen Object como un antepasado, por ejemplo:

>> class A; end 
>> class B; end 
>> B::A 
(irb):3: warning: toplevel constant A referenced by B::A 

>> B.ancestors 
=> [B, Object, Kernel, BasicObject] 

>> module C; end 
>> module D; end 
>> D::C 
NameError: uninitialized constant D::C 

>> D.ancestors 
=> [D] 
+0

Sí, es lamentable que no podamos hacer que funcione con las clases, sin embargo, es bueno saber que al menos esto funciona con módulos. ¡Gracias! – mislav

0

que consigo su problema en ree 1.8.7 (usted no menciona una versión específica) si uso const_get dentro const_missing, pero no si uso ::. No me gusta usar eval, pero funciona aquí:

class Dummy 
    def self.const_missing(const) 
    if :OAuth == const 
     require 'dummy/oauth' 
     eval "self::#{const}" 
    else 
     super(const) 
    end 
    end 
end 

module Hello 
end 

Dummy.const_get :Hello # => ::Hello 
Dummy::Hello   # => Dummy::Hello 

Me gustaría Module tenían un método :: por lo que podría hacer self.send :"::", const.

+0

Perdón por no mencionar versiones específicas. Mi problema está presente en 1.8.7, 1.9.2, 1.9.3, Rubinius (tanto estable como de borde) y otros. Voy a intentar tu sugerencia, pero por lo que obtuve en mis pruebas, el método 'const_missing' no se llamó en absoluto. Entonces no importa cómo lo implemente. – mislav

+0

Eso depende de si usa 'Dummy.const_get: Hello' o' Dummy :: Hello' * fuera * del método 'const_missing'. Solo ':: Hello' activará' const_missing' - o al menos eso es cierto en la única implementación de Ruby que probé. –

0

La carga diferida es un patrón de diseño muy común, puede lleve a la práctica de muchas maneras .como:

class Object 
    def bind(key, &block) 
    @hooks ||= Hash.new{|h,k|h[k]=[]} 
    @hooks[key.to_sym] << [self,block] 
    end 

    def trigger(key) 
    @hooks[key.to_sym].each { |context,block| block.call(context) } 
    end 
end 

A continuación, puede

bind :json do 
    require 'json' 
end 

begin 
    JSON.parse("[1,2]") 
rescue 
    trigger :json 
    retry 
end 
Cuestiones relacionadas