Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Ruby chaining work?

Tags:

ruby

chaining

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
like image 256
JZ. Avatar asked Feb 03 '11 01:02

JZ.


2 Answers

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.

like image 116
Ryan Bigg Avatar answered Sep 20 '22 17:09

Ryan Bigg


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>'
like image 42
Phrogz Avatar answered Sep 17 '22 17:09

Phrogz