Detecting when a method is chained

Go To StackoverFlow.com

2

I am looking to do some method chaining. I have the following code:

class MyClass

  attr_accessor :handler

  def do_a
    puts 'i just did a'
    self.handler = 'a'
    self
  end

  def do_b_if_a
    puts 'i just did b' if handler == 'a'
  end

 end

So the following works:

irb > test = MyClass.new
=> #<MyClass:0x007fa44ced9a70 @handler=nil>
irb > test.do_a
'i just did a'
irb > test.do_a.do_b_if_a
'i just did a'
'i just did b'

What I DONT want to work is when I call do_a the first time it sets the handler, which means now do_b_if_a can be called at any time. But I only want it to be called when it is chained with do_a, how do I do that?

2012-04-03 22:07
by Jason Waldrip


0

I think you're tracking state in the wrong place. You'd be better off with something similar to ActiveRecord's query interface, for example:

class MyClass
  attr_accessor :handler

  def do_a
    puts 'i just did a'
    with_handler 'a'
  end

  def do_b_if_a
    puts 'i just did b' if handler == 'a'
  end

private

  def with_handler(h)
    o = dup
    o.handler = h
    o
  end

end

That way you always have an instance of MyClass but you have a throw-away copy that keeps track of its history. This is similar to cHao's approach but it doesn't need an extra decorator class as MyClass can decorate itself.

2012-04-03 22:45
by mu is too short


1

In general, you don't want to care (and in most cases, you don't even get to know) if your methods are called in a certain way. That way lies madness. Magic call sequences make for a hell of a time debugging and testing.

What you could do, though...instead of having do_a return self, wrap a decorator around it that defines do_b, and return the decorator. At that point, your original MyClass can't do_b, cause it doesn't know how. But the thingie returned from do_a can.

(Now, you can still say like a = test.do_a and then a.do_b, but you can't really get around that without parsing the Ruby code yourself.)

2012-04-03 22:17
by cHao


0

As cHao said, you shouldn't try to method call patterns. But I can also think of another way to do this:

def do_a
  puts "i just did a"
  do_b_if_a(true)
end

def do_b_if_a(did_a=false)
  puts "i just did b" if did_a
end

do_a       # prints "i just did a"
do_b_if_a  # does nothing

Sure, you can call do_b_if_a(true), but then it just makes it more flexible ;)

2012-04-03 22:27
by Jwosty
The problem is I still need to have access to the object - Jason Waldrip 2012-04-03 22:41
What do you mean, access to the object - Jwosty 2012-04-03 22:42


0

Doesn't get you all the way there, but you could define do_b_if_a as a singleton method that gets added when to_a is called:

class MyClass

  def do_a
    puts 'i just did a'
    self.tap{|s| def s.do_b_if_a; puts 'i just did b' end}
  end

 end

You could take a look at Kernel.caller if you want to take certain actions based on the call stack. However, you might get punched in the face by another developer if you don't have a good reason for doing so, lol.

2012-04-04 02:39
by Abe Voelker