DSL block without argument in ruby

2019-04-23 05:03发布

问题:

I'm writing a simple dsl in ruby. Few weeks ago I stumbled upon some blog post, which show how to transform code like:

some_method argument do |book|
  book.some_method_on_book
  book.some_other_method_on_book :with => argument
end

into cleaner code:

some_method argument do
   some_method_on_book
   some_other_method_on_book :with => argument
end

I can't remember how to do this and I'm not sure about downsides but cleaner syntax is tempting. Does anyone have a clue about this transformation?

回答1:

def some_method argument, &blk
  #...
  book.instance_eval &blk
  #...
end

UPDATE: However, that omits book but don't let you use the argument. To use it transparently you must transport it someway. I suggest to do it on book itself:

class Book
  attr_accessor :argument
end

def some_method argument, &blk
  #...
  book.argument = argument
  book.instance_eval &blk
  #...
end

some_method 'argument' do
   some_method_on_book
   some_other_method_on_book argument
end


回答2:

Take a look at this article http://www.dan-manges.com/blog/ruby-dsls-instance-eval-with-delegation — there is an overview of the method (specifically stated in the context of its downsides and possible solution to them), plus there're several useful links for further reading.

Basically, it's about using instance_eval to execute the block in the desirable context.

Speaking about downside of this technique:

So what's the problem with it? Well, the problem is that blocks are generally closures. And you expect them to actually be full closures. And it's not obvious from the point where you write the block that that block might not be a full closure. That's what happens when you use instance_eval: you reset the self of that block into something else - this means that the block is still a closure over all local variables outside the block, but NOT for method calls. I don't even know if constant lookup is changed or not.

Using instance_eval changes the rules for the language in a way that is not obvious when reading a block. You need to think an extra step to figure out exactly why a method call that you can lexically see around the block can actually not be called from inside of the block.



回答3:

Check out the docile gem. It takes care of all the sharp edges, making this very easy for you.



标签: ruby block dsl