开发者

How to provide class/object methods in a block in Ruby?

开发者 https://www.devze.com 2023-01-11 04:09 出处:网络
Sometimes yo开发者_StackOverflowu can see: do_this do available_method1 \"arg1\" available_method2 \"arg1\"

Sometimes yo开发者_StackOverflowu can see:

do_this do
   available_method1 "arg1"
   available_method2 "arg1"
end

When I use the block from do_this method then I get some methods I could use inside that block.

I wonder how this is accomplished? How does the code look like behind the scenes?

I want to be able to provide some methods through a block.


It's called a Domain-Specific Language (DSL). Here's (Last archived version) some great info on various forms of Ruby DSL blocks.

There are really two ways to go about doing this, with different syntaxes:

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

The first is more explicit and less likely to fail, while the second is more concise.

The first can be implemented like so, by simply passing a new instance as the block parameter with 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

And the second, which runs the block in the context of the new object, giving it access to self, instance variables, and methods:

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

And if you really want to do it without calling MyThing.create, just:

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


This is usually done using instance_eval to change the value of self inside the block to be some different object, which then handles those method calls.

As a quick example:

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

Though this is a powerful language feature, and has been used before to great effect, there is still some debate on whether it's a good idea or not. If you don't know what's going on, you might be surprised that the value of @some_instance_variable changes inside and outside the block, since it's relative to the current value of self.

See Daniel Azuma's excellent article for more discussion and details.

0

精彩评论

暂无评论...
验证码 换一张
取 消