如何在Ruby中的块中提供类/对象方法?

时间:2010-08-08 19:30:42

标签: ruby

有时您可以看到:

do_this do
   available_method1 "arg1"
   available_method2 "arg1"
end

当我使用do_this方法中的块时,我得到了一些我可以在该块中使用的方法。

我想知道这是如何实现的?代码如何在幕后看起来像?

我希望能够通过块提供一些方法。

2 个答案:

答案 0 :(得分:4)

它被称为域特定语言(DSL)。 Here's (Last archived version)关于各种形式的Ruby DSL块的一些很好的信息。

实际上有两种方法可以使用不同的语法:

do_thing do |thing| # with a block parameter
  thing.foo :bar
  thing.baz :wibble
end

# versus

do_thing do # with block-specific methods
  foo :bar
  baz :wibble
end

第一个更明确,更不可能失败,而第二个更简洁。

第一个可以像这样实现,只需将新实例作为块参数传递给yield

class MyThing
  def self.create
    yield new
  end

  def foo(stuff)
    puts "doing foo with #{stuff}"
  end
end

MyThing.create do |thing|
  thing.foo :bar
end

第二个,它在新对象的上下文中运行块,使其可以访问self,实例变量和方法:

class MyThing
  def self.create(&block)
    new.instance_eval &block
  end

  def foo(stuff)
    puts "doing foo with #{stuff}"
  end
end

MyThing.create do
  foo :bar
end

如果您真的想在不致电MyThing.create的情况下这样做,只需:

def create_thing(&block)
  MyThing.create &block
end

答案 1 :(得分:2)

通常使用instance_eval将块中的self的值更改为某个不同的对象,然后处理这些方法调用。

作为一个简单的例子:

class ExampleReceiver
  def available_method1 arg ; p [:available_method1, arg] ; end
  def available_method2 arg ; p [:available_method2, arg] ; end
end
def do_this(&blk) ; ExampleReceiver.new.instance_eval(&blk) ; end

do_this do
  available_method1 "arg1" #=> [:available_method1, "arg1"]
  available_method2 "arg1" #=> [:available_method2, "arg1"]
end

虽然这是一个强大的语言功能,并且之前已经使用过很好的效果,但是对于它是否是一个好主意还存在一些争论。如果您不知道发生了什么,您可能会惊讶@some_instance_variable的值在块内外发生变化,因为它相对于self的当前值。

有关更多讨论和详细信息,请参阅Daniel Azuma's excellent article