我有一个名为Notifier
的模块。
module Notifier
def self.prepended(host_class)
host_class.extend(ClassMethods)
end
module ClassMethods
def emit_after(*methods)
methods.each do |method|
define_method(method) do |thing, block|
r = super(thing)
block.call
r
end
end
end
end
end
它公开了一个类方法emit_after
。我这样使用它:
class Player
prepend Notifier
attr_reader :inventory
emit_after :take
def take(thing)
# ...
end
end
目的是通过调用emit_after :take
,模块使用其自己的方法覆盖#take
。
但是实例方法没有被覆盖。
我可以,但不使用ClassMethods
module Notifier
def self.prepended(host_class)
define_method(:take) do |thing, block|
r = super(thing)
block.call
r
end
end
class Player
prepend Notifier
attr_reader :inventory
def take(thing)
# ...
end
end
#> @player.take @apple, -> { puts "Taking apple" }
#Taking apple
#=> #<Inventory:0x00007fe35f608a98...
我知道ClassMethods#emit_after
被调用,所以我假设该方法已定义,但从未被调用。
我想动态创建方法。如何确保generate方法覆盖我的实例方法?
答案 0 :(得分:2)
该解决方案如何?
module Notifier
def self.[](*methods)
Module.new do
methods.each do |method|
define_method(method) do |thing, &block|
super(thing)
block.call if block
end
end
end
end
end
class Player
prepend Notifier[:take]
def take(thing)
puts "I'm explicitly defined"
end
end
Player.new.take(:foo) { puts "I'm magically prepended" }
# => I'm explicitly defined
# => I'm magically prepended
这与Aleksei Matiushkin的解决方案非常相似,但是祖先的链条比较干净(那里没有“无用的”通知程序)
答案 1 :(得分:1)
进入当前打开的课程:
module Notifier
def self.prepended(host_class)
host_class.extend(ClassMethods)
end
module ClassMethods
def emit_after(*methods)
# ⇓⇓⇓⇓⇓⇓⇓ HERE
prepend(Module.new do
methods.each do |method|
define_method(method) do |thing, block = nil|
super(thing).tap { block.() if block }
end
end
end)
end
end
end
class Player
prepend Notifier
attr_reader :inventory
emit_after :take
def take(thing)
puts "foo"
end
end
Player.new.take :foo, -> { puts "Taking apple" }
#⇒ foo
# Taking apple
答案 2 :(得分:1)
@Konstantin Strukov
的解决方案不错,但可能有些混乱。因此,我建议另一种解决方案,该解决方案更像原始解决方案。
您的首要目标是向您的班级添加 class方法(emit_after
)。为此,您应该使用extend
方法,不要使用任何钩子,例如self.prepended()
,self.included()
或self.extended()
。
prepend
和include
用于添加或覆盖实例方法。但这是您的第二个目标,当您致电emit_after
时就会发生。因此,在扩展类时,不应使用prepend
或include
。
module Notifier
def emit_after(*methods)
prepend(Module.new do
methods.each do |method|
define_method(method) do |thing, &block|
super(thing)
block.call if block
end
end
end)
end
end
class Player
extend Notifier
emit_after :take
def take(thing)
puts thing
end
end
Player.new.take("foo") { puts "bar" }
# foo
# bar
# => nil
现在很明显,您调用extend Notifier
来添加emit_after
类方法,并且所有魔术都隐藏在该方法中。