ruby on rails - example - Ruby method_added callback not trigger incluyendo Módulos
rails after action (3)
Quería escribir un poco de lib de "Deprecate" y usé la devolución de llamada "method_added" mucho. Pero ahora noté que esta devolución de llamada no se desencadena, cuando se incluye un módulo.
¿Hay devoluciones de llamada o soluciones alternativas para informar a la clase "Foobar" cuando se incluye algo?
Pequeña demostración para demostrar:
# Including Moduls won''t trigger method_added callback
module InvisibleMethod
def invisible
"You won''t get a callback from me"
end
end
class Foobar
def self.method_added(m)
puts "InstanceMethod: ''#{m}'' added to ''#{self}''"
end
def visible
"You will get a callback from me"
end
include InvisibleMethod
end
[:invisible, :visible, :wont_exist].each do |meth|
puts "#{meth}: #{Foobar.public_method_defined? meth}"
end
Ese es el resultado:
InstanceMethod: ''visible'' added to ''Foobar''
invisible: true
visible: true
wont_exist: false
Información Adicional:
Realmente necesito usar un gancho como method_added.
ActiveModel está agregando métodos_instancia_pública a la clase durante el tiempo de ejecución mediante módulos anónimos.
Creo que deprecation
no es una gran idea para requerir una biblioteca. se implementa así en datamapper
. Acerca de hook method_added
; está funcionando como se esperaba porque los métodos ya se han agregado al module
no a la class
. Solo usted puede obtener su resultado esperado Parche de monos included
gancho.
# got from https://github.com/datamapper/dm-core/blob/master/lib/dm-core/support/deprecate.rb
module Deprecate
def deprecate(old_method, new_method)
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{old_method}(*args, &block)
warn "/#{self.class}##{old_method} is deprecated, use /#{self.class}##{new_method} instead (/#{caller.first})"
send(#{new_method.inspect}, *args, &block)
end
RUBY
end
end # module Deprecate
class MyClass
extend Deprecate
def old_method
p "I am old"
end
deprecate :old_method, :new_method
def new_method
p "I am new"
end
end
m = MyClass.new
m.old_method
# MyClass#old_method is deprecated, use MyClass#new_method instead (pinger.rb:27:in `<main>'')
# "I am new"
Tal como lo sugiere uno de los comentarios, puede usar algún otro enlace para obtener el comportamiento que desea. Por ejemplo, intente agregar esto al comienzo de su código:
class Module
def included(klass)
if klass.respond_to?(:method_added)
self.instance_methods.each do |method|
klass.method_added(method)
end
end
end
end
Cada vez que se incluye un módulo en una clase, todos los métodos de instancia de ese módulo se notificarán a la clase, siempre que defina el método method_added
. Al ejecutar su código con el cambio anterior obtengo este resultado:
InstanceMethod: ''visible'' added to ''Foobar''
InstanceMethod: ''invisible'' added to ''Foobar''
invisible: true
visible: true
wont_exist: false
Lo cual creo que es el comportamiento que quieres.
El problema es que incluir módulos no agrega métodos a las clases, solo cambia la cadena de llamadas del método. Esta cadena define qué clases / módulo se buscará para un método, que no está definido para la clase en cuestión. Lo que sucede cuando se incluye un módulo es una adición de una entrada en esa cadena.
Esto es exactamente lo mismo que cuando agrega un método en una superclase; esto no llama a method_added
ya que no está definido en la superclase. Sería muy extraño si una subclase pudiera cambiar el comportamiento de una superclase.
Podrías solucionarlo mediante el método de llamada manual agregado para un módulo incluido, al redefinir include
para tu clase:
class Foobar
def self.include(included_module)
included_module.instance_methods.each{|m| self.method_added(m)}
super
end
end
Y es mucho más seguro que redefinir el método included
en el Module
: el cambio se reduce solo a las clases que usted mismo definió.