这个问题几乎总结了在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样板,因为我只关心#initialize的内容,它设置了一些元数据(例如name )并定义了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)

假设我有一些元数据( foo ),我想在我的DSL中设置:

module Command
  define :quit do
    foo "bar" # this becomes global.rb:7
    exec do
      @user.client.should_terminate = true
    end
  end
end

我看到lib/commands/global.rb:7:in block in <module:Command>': undefined method 'foo' for Command:Module (NoMethodError)

我想我的Proc / block / lambda-fu错了,但是我很难找到困惑的底线。 我应该如何编写Command::define来获得所需的结果? 看起来虽然Ruby创建了Command::Command_help作为Command::Command的子类,但它实际上并没有继承任何属性。

===============>>#1 票数:1

你的问题是块保留self的值(除其他外) - 当你调用init_block.call并执行跳转到传递给define的块时,self是模块Command而不是Command_quit的实例

如果将initialize方法更改为,则应该没问题

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实例作为子类)。

“blocks preserve self”行为的一个例外是define_method :在这种情况下,self将始终是调用该方法的对象,就像使用普通方法一样。

===============>>#2 票数:1 已采纳

当你在Ruby中引用something时,它首先在本地绑定中查找something ,如果它失败了,它会查找self.something self表示评估的上下文,此上下文在类定义class C; self; end上更改class C; self; end class C; self; end class C; self; end ,方法定义class C; def m; self; end; end class C; def m; self; end; 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_execclass_evalclass_exec )。

对于您的示例,应该在具体命令的实例的self上下文中评估传递给define的块。

这是一个例子。 我在类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

  ask by Chris Tonkinson translate from so

未解决问题?本站智能推荐: