JZ.
JZ.

Reputation: 21877

How does Ruby chaining work?

Why can you chain this:

"Test".upcase.reverse.next.swapcase

but not this:

x = My_Class.new 
x.a.b.c

where

class My_Class 

  def a 
    @b = 1 
  end 

  def b
    @b = @b + 2 
  end 

  def c 
    @b = @b -72 
  end

end

Upvotes: 5

Views: 2838

Answers (3)

Phrogz
Phrogz

Reputation: 303168

As support for other answers, this code:

"Test".upcase.reverse.next.swapcase

...is almost exactly the same as...

a = "Test"
b = a.upcase
c = b.reverse
d = c.next
e = d.swapcase

....except that my code above has extra variables left over pointing to the intermediary results, whereas the original leaves no extra references around. If we do this with your code:

x = MyClass.new   # x is an instance of MyClass
y = x.a           # y is 1, the last expression in the a method
z = y.b           # Error: Fixnums have no method named 'b'

Using Ruby 1.9's tap method, we can even make this more explicit:

irb> "Test".upcase.tap{|o| p o}.reverse.tap{|o| p o}.next.tap{|o| p o}.swapcase
#=> "TEST"
#=> "TSET"
#=> "TSEU"
=> "tseu"

irb> class MyClass
irb>   def a
irb>     @b = 1
irb>   end
irb>   def b
irb>     @b += 2
irb>   end
irb> end
=> nil

irb(main):011:0> x = MyClass.new
=> #<MyClass:0x000001010202e0>

irb> x.a.tap{|o| p o}.b.tap{|o| p o}.c
#=> 1
NoMethodError: undefined method `b' for 1:Fixnum
from (irb):12
from /usr/local/bin/irb:12:in `<main>'

Upvotes: 6

Ryan Bigg
Ryan Bigg

Reputation: 107718

The upcase, reverse, next and swapcase methods all return String objects and all those methods are for... you guessed it, String objects!

When you call a method (more often than not, like 99.9999% of the time) it returns an object. This object has methods defined on it which can then be called which explains why you can do this:

"Test".upcase.reverse.next.swapcase

You can even call reverse as many times as you like:

"Test".reverse.reverse.reverse.reverse.reverse.reverse.reverse.reverse

All because it returns the same kind of object, a String object!

But you can't do this with your MyClass:

x = My_Class.new

x.a.b.c

For that to work, the a method would have to return an object which has the b method defined on it. Right now, that seems like only instances of MyClass would have that. To get this to work you could make the return value of a the object itself, like this:

def a
  @b += 2
  self
end

Extrapolating this, the b method would also need to return self as the c method is available only on instances of the MyClass class. It's not important what c returns in this example, because it's the end of the chain. It could return self, it could not. Schrödinger's cat method. Nobody knows until we open the box.

Upvotes: 7

cam
cam

Reputation: 14222

The last expression in a function is its implicit return value. You need to return self if you want to chain methods like that.

For example, your a method is currently returning 1. b is not a method for numbers. You'll want to modify it like so:

def a 
  @b = 1 
  self
end 

Upvotes: 5

Related Questions