ruby - rails - haml to html
Clases de carga automática en Ruby sin su `autoload` (3)
Esto se planteó en un boleto de Rails hace algún tiempo y cuando lo investigué no parecía haber forma de evitarlo. El problema es que Ruby buscará los ancestros antes de llamar a const_missing
y, como todas las clases tienen a Object
como ancestro, siempre se encontrarán las constantes de nivel superior. Si puede limitarse a usar solo módulos para el espacio de nombres, entonces funcionará ya que no tienen un Object
como antecesor, 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]
Me encanta la funcionalidad de carga automática de Ruby ; sin embargo, desaparecerá en futuras versiones de Ruby ya que nunca fue seguro para subprocesos.
Así que ahora mismo me gustaría fingir que ya no está y escribir mi código sin él, implementando el mecanismo de carga lenta . Me gustaría implementarlo de la manera más simple posible (no me importa la seguridad de los hilos en este momento). Ruby debería permitirnos hacer esto.
Comencemos por aumentar 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 intentemos hacer referencia a una constante debajo de "Dummy" que falta, por ejemplo, si intentamos hacer referencia a "Dummy :: Hello", llamará const_missing
con el Símbolo :Hello
. Esto es exactamente lo que necesitamos, así que vamos 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, si hacemos referencia a "Dummy :: OAuth", requerirá el archivo "dummy / oauth.rb" que se espera que defina la constante "Dummy :: OAuth". Existe la posibilidad de un bucle sin fin cuando llamamos a const_get
(ya que puede llamar a const_missing
internamente), pero protegerse contra eso está fuera del alcance de esta pregunta.
El gran problema es que toda esta solución se descompone si existe un módulo llamado "OAuth" en el espacio de nombres de nivel superior. La referencia a "Dummy :: OAuth" omitirá su const_missing
y simplemente devolverá "OAuth" desde el nivel superior. La mayoría de las implementaciones de Ruby también harán una advertencia sobre esto:
warning: toplevel constant OAuth referenced by Dummy::OAuth
Esto fue reportado como un problema en el año 2003, pero no pude encontrar evidencia de que el equipo central de Ruby haya estado 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 omite silenciosamente a favor de una constante en el espacio de nombres de nivel superior. Esto no sucedería si "Dummy :: OAuth" se declarara con la funcionalidad de autoload
de Ruby. ¿Alguna idea de cómo trabajar alrededor de esto?
La carga perezosa es un patrón de diseño muy común, puede implementarlo de muchas maneras. me gusta :
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
Entonces tú puedes
bind :json do
require ''json''
end
begin
JSON.parse("[1,2]")
rescue
trigger :json
retry
end
Obtengo su problema en ree 1.8.7 (no menciona una versión específica) si uso const_get
dentro de const_missing
, pero no si uso ::
. No me encanta 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 que el Module
tuviera un ::
método para que pudieras hacer self.send :"::", const
.