This question几乎总结了在Ruby中动态扩展类层次结构的简单案例。
我遇到的问题是我想用DSL定义这个子类,我想我是我自己复杂范围的受害者。
我有使用基类的工作代码:
module Command
class Command
...
end
end
然后每个命令都作为子类实现:
module Command
class Command_quit < Command
def initialize
name = "quit"
exec do
@user.client.should_terminate = true
end
end
end
end
这里有很多死记硬背和重复,我设想了一个可以大大清除它的DSL:
module Command
define :quit do
exec do # this is global.rb:7 from the error below
@user.client.should_terminate = true
end
end
end
正如你所看到的,我想干掉样板,因为我只关心#initialize
的内容,它设置了一些元数据(例如name
)并定义了{{1}阻止(这是重要的部分)。
我遇到了以下模块方法:
exec
此代码产生module Command
def self.define(cmd_name, &init_block)
class_name = "Command_#{cmd_name.to_s}"
class_definition = Class.new(Command)
class_initializer = Proc.new do
name = cmd_name
init_block.call
end
::Command.const_set class_name, class_definition
::Command.const_get(class_name).send(:define_method, :initialize, class_initializer)
end
end
假设我有一些元数据(lib/commands/global.rb:7:in 'exec': wrong number of arguments (0 for 1+) (ArgumentError)
),我想在我的DSL中设置:
foo
我看到module Command
define :quit do
foo "bar" # this becomes global.rb:7
exec do
@user.client.should_terminate = true
end
end
end
我想我的Proc / block / lambda-fu错了,但是我很难找到困惑的底线。我该如何写lib/commands/global.rb:7:in block in <module:Command>': undefined method 'foo' for Command:Module (NoMethodError)
来获得理想的结果?看起来虽然Ruby创建Command::define
作为Command::Command_help
的子类,但它实际上并没有继承任何属性。
答案 0 :(得分:1)
你的问题是块保留了self的值(除其他外) - 当你调用init_block.call
并且执行跳转到传递给define
的块时,self就是模块Command
而不是Command_quit
如果将初始化方法更改为
,则应该没问题class_initializer = Proc.new do
self.name = cmd_name # I assume you didn't just want to set a local variable
instance_eval(&init_block)
end
instance_eval
执行块,但是使用接收器(在这种情况下,您的Command_quit
实例作为子类。
&#34;块的例外保留了自我&#34;行为是define_method
:在这种情况下,self始终是调用方法的对象,就像使用普通方法一样。
答案 1 :(得分:1)
当您在Ruby中引用something
时,它首先在本地绑定中查找something
,如果失败,则会查找self.something
。 self
表示评估的上下文,此上下文在类定义class C; self; end
,方法定义class C; def m; self; end; end
上更改,但是,它不会在块定义上更改。该块在块定义点捕获当前self
。
module Command
define :quit do
foo "bar" # self is Command, calls Command.foo by default
end
end
如果您想修改区块内的self
上下文,可以使用BasicObject.instance_eval
(或instance_exec
,class_eval
,class_exec
)。
对于您的示例,传递给define
的块应在具体命令实例的self
上下文中进行评估。
这是一个例子。我在类Command::Command
中添加了一些模拟方法定义:
module Command
class Command
# remove this accessor if you want to make `name` readonly
attr_accessor :name
def exec(&block)
@exec = block
end
def foo(msg)
puts "FOO => #{msg}"
end
def run
@exec.call if @exec
end
end
def self.define(name, &block)
klass = Class.new(Command) do
define_method(:initialize) do
method(:name=).call(name) # it would be better to make it readonly
instance_eval(&block)
end
# readonly
# define_method(:name) { name }
end
::Command.const_set("Command_#{name}", klass)
end
define :quit do
foo "bar"
exec do
puts "EXEC => #{name}"
end
end
end
quit = Command::Command_quit.new #=> FOO => bar
quit.run #=> EXEC => quit
puts quit.class #=> Command::Command_quit