Calling methods from within a block

(Daniel Schierbeck) #1

Hi lads!

I'm looking to do something like this:

Doc.new do
   foo do
     bar { "value of doc -> foo -> bar" }
     buz { "another text node" }
   end

   baz do
     biz { "some text" }
   end
end

So that the methods called (biz, bar, buz and baz) are called on the same objects as the method to which they belong.

foo should be called on the Doc object, as should baz. bar and buz should be called on an object returned by foo, etc. etc.

What I'm looking for is a way to avoid having to pass "self" to the blocks all the time.

Daniel Schierbeck

(James Edward Gray II) #2

Instead of yielding to the blocks, instance_eval() them. That will set self up for the call.

James Edward Gray II

···

On Aug 22, 2005, at 8:31 AM, Daniel Schierbeck wrote:

Hi lads!

I'm looking to do something like this:

Doc.new do
  foo do
    bar { "value of doc -> foo -> bar" }
    buz { "another text node" }
  end

  baz do
    biz { "some text" }
  end
end

So that the methods called (biz, bar, buz and baz) are called on the same objects as the method to which they belong.

foo should be called on the Doc object, as should baz. bar and buz should be called on an object returned by foo, etc. etc.

What I'm looking for is a way to avoid having to pass "self" to the blocks all the time.

(Malte Milatz) #3

Daniel Schierbeck schrieb:

I'm looking to do something like this:

Doc.new do
   foo do
     bar { "value of doc -> foo -> bar" }
    buz { "another text node" }
  end
end

The syntax you used could be achieved via instance_eval, which, however,
isn't the prefered way to do it. Another API trick (not such a nice one
either, however):

Doc.new do |x|
  x.foo
end

Malte

(David Holroyd) #4

Something I've done which parallels that is to use an instance method
instead of a block, and to include the methods (of Doc, in your example)
from a module,

----8<----
class MyDocBuilder
  include Doc

  def build
    foo do
      bar { "value of doc -> foo -> bar" }
      buz { "another text node" }
    end
  
    baz do
      biz { "some text" }
    end
  end
end

MyDocBuilder.new.build
---->8----

dave

···

On Mon, Aug 22, 2005 at 10:31:18PM +0900, Daniel Schierbeck wrote:

Hi lads!

I'm looking to do something like this:

Doc.new do
  foo do
    bar { "value of doc -> foo -> bar" }
    buz { "another text node" }
  end

  baz do
    biz { "some text" }
  end
end

--
http://david.holroyd.me.uk/

(Daniel Schierbeck) #5

Malte Milatz wrote:

Doc.new do |x|
  x.foo
end

That was what I was hoping to avoid :frowning:

Daniel

(James Edward Gray II) #6

Hey, if it works, it works. instance_eval() is a great tool, though there are some minuses to it. Mainly the following does not parse as intended:

MyObject.new do
     a_method_ending_in_equals_on_my_object = :whatever
end

Ruby will see that as a local variable assignment, not a method call. You must add a self. prefix, or avoid methods ending in =s.

Also, you lose the context of where the call is made from. Instance variables and method calls will all target the new self, not the calling location.

These minuses mean instance_eval() isn't perfect in all cases, but if you are aware of the gotchas you can use it to do some very clever interface work, in my opinion.

In summary, preferred or not, I like it! :wink:

James Edward Gray II

···

On Aug 22, 2005, at 8:41 AM, Malte Milatz wrote:

Daniel Schierbeck schrieb:

I'm looking to do something like this:

Doc.new do
   foo do
     bar { "value of doc -> foo -> bar" }
    buz { "another text node" }
  end
end

The syntax you used could be achieved via instance_eval, which, however,
isn't the prefered way to do it.

(Jim Weirich) #7

James Edward Gray II said:

Instead of yielding to the blocks, instance_eval() them. That will
set self up for the call.S

instance_eval will work, but be careful. It changes the values of self
within the block making the code inside the block act differently than
code outside the block. This is OK for small snippets of code, but I have
found it confusing when the block is larger and tends to contain arbitrary
logic. It particularly confusing when you try to call methods that are
defined in the object containing block (for they get directed to a
different object by default).

See http://onestepback.org/index.cgi/Tech/Ruby/StayingSimple.rdoc for
details where I switched back from an instance_eval to a normal yield.

···

--
-- Jim Weirich jim@weirichhouse.org http://onestepback.org
-----------------------------------------------------------------
"Beware of bugs in the above code; I have only proved it correct,
not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)

(Malte Milatz) #8

James Edward Gray II schrieb:

These minuses mean instance_eval() isn't perfect in all cases, but if you
are aware of the gotchas you can use it to do some very clever interface
work, in my opinion.

In summary, preferred or not, I like it! :wink:

(One more gotcha/feature is that instance_eval doesn't prevent you from
calling private methods.)

Well, in my opinion it's nice when used on a literal block, however I think
it isn't great API design if the API user has to know about the fact that
the block will be evaluated using instance_eval. For example, if I happen to
read foreign code with something like this:

  anObject.aMethod { anotherMethod }

Then I may be searching a long time for the definition of anotherMethod,
because I didn't have the idea that the block will be instance_eval'ed from
anObject.

However, readers of this be aware that I'm not really involved in Ruby
philosophy, so my opinion might not be relevant in any way.

Malte

(James Edward Gray II) #9

I actually consider that a feature. Sometimes I'll define some private initialization only methods, then instance_eval() a block in the constructor to provide access to them at the right time.

It's just another tool in the toolbox. Beware the limitations, but don't be afraid to use it, I say!

James Edward Gray II

···

On Aug 22, 2005, at 9:46 AM, Malte Milatz wrote:

(One more gotcha/feature is that instance_eval doesn't prevent you from
calling private methods.)