I am trying to do a chaining method for the following two methods. After running this code, I kept getting the following output:
#<SimpleMath:0x007fc85898ab70>%
My question is: what is the proper way of chaining methods in Ruby
?
Here is my codes:
class SimpleMath
def add(a,b=0)
a + b
return self
end
def subtract(a,b=0)
a - b
return self
end
end
newNumber = SimpleMath.new()
print newNumber.add(2,3).add(2)
Are you trying to do something like this?
class SimpleMath
def initialize
@result = 0
end
#1 add function
def add(val)
@result += val
self
end
#2 Subtract function
def subtract(val)
@result -= val
self
end
def to_s
@result
end
end
newNumber = SimpleMath.new
p newNumber.add(2).add(2).subtract(1)
For any number of arguments
class SimpleMath
def initialize
@result = 0
end
#1 add function
def add(*val)
@result += val.inject(&:+)
self
end
#2 Subtract function
def subtract(*val)
@result -= val.inject(&:+)
self
end
def to_s
@result
end
end
newNumber = SimpleMath.new
p newNumber.add(1, 1).add(1, 1, 1, 1).subtract(1)
Let's define an instance of your class SimpleMath
:
sm = SimpleMath.new #=> #<SimpleMath:0x000001020ca820>
Three things to note here:
sm
is a variable. In Ruby, variables are represented by lower case letters, optionally separated with underscores (e.g., my_var
).
- while it's OK to add
()
after new
, when new
has no arguments (aka "parameters"), that's optional and not usually done.
- if the keyword
return
is not present, Ruby returns the last calculation performed by the method. Here you would generally write the last line as simply self
, and that would be returned. Alas, that matters not, as returning self
, with or without the keyword return
, is not what you want.
Try the following in IRB:
sm.add(2) #=> #<SimpleMath:0x000001020ca820>
You no doubt were expecting this to return 2+0 #=> 2
, but instead it returned self
, which, as you can see above, is in fact sm
(#<SimpleMath:0x000001020ca820>
).
You can fix this by simply removing the line:
return self
from add
and subtract
:
class SimpleMath
def add(a,b=0)
a + b
end
def subtract(a,b=0)
a - b
end
end
Now
sm = SimpleMath.new
sm.add(2) #=> 2
However, if we try to chain another add
, we have another problem:
sm.add(2).add(2,3) #=> NoMethodError: undefined method `add' for 2:Fixnum
This message is very clear: the class Fixnum
, of which 2
is an instance, has no instance method named add
. That's because you defined it for the class SimpleMath
, not for Fixnum
.
When Ruby executes sm.add(2).add(3,4)
, it first computes sm.add(2) #=> 2
, which reduces the expression to 2.add(3,4)
. It then attempts to send the method add
(with its two parameters) to 2
, but finds the class 2.class #=> Fixnum
has no instance method add
; hence the exception.
We can correct that error by defining these methods for class Fixnum
instead:
class Fixnum
def add(a,b=0)
a + b
end
def subtract(a,b=0)
a - b
end
end
You can confirm that these methods have been added to the Fixnum
class by running:
Fixnum.instance_methods.sort
Now, another problem:
sm = Fixnum.new #=> NoMethodError: undefined method `new' for Fixnum:Class
Oh, my, the class Fixnum
has no new
method! That's because the instances of Fixnum
are integers, which cannot be created. You can easily confirm that integers are instances of Fixnum
:
72.class #=> Fixnum
-3.class #=> Fixnum
So we can invoke the add
method by sending it to any Fixnum
instance:
72.add(2) #=> 2
-3.add(2) #=> 2
Now let's try to chain add
operations:
72.add(2).add(3,4) #=> 7
72.add(2000000).add(3,4) #=> 7
No exception, but no chaining. The way to fix this is to change the methods yet again:
class Fixnum
def add(b=0)
puts "in add, self = #{self}, b = #{b}"
self + b
end
def subtract(b=0)
puts "in subtract, self = #{self}, b = #{b}"
self - b
end
end
I've added a puts
statement in each method in case more debugging is needed. We'll remove these when the code works properly. Let's test:
2.add(3) #=> 5
in add, self = 2, b = 3
5.add #=> 5
in add, self = 5, b = 0
5.add(7) #=> 12
in add, self = 5, b = 7
2.add(3).add.add(7) #=> 12
in add, self = 2, b = 3
in add, self = 5, b = 0
in add, self = 5, b = 7
2.subtract(5) #=> -3
in subtract, self = 2, b = 5
-3.subtract #=> -3
in subtract, self = -3, b = 0
2.subtract(5).subtract #=> -3
in subtract, self = 2, b = 5
in subtract, self = -3, b = 0
2.add(3).subtract(5).add(7) #=> 7
in add, self = 2, b = 3
in subtract, self = 5, b = 5
in add, self = 0, b = 7
Success! Get it?
This fellow (tjackiw.tumblr.com) uses this as an interview question and gives a very clean walkthough of how & why the correct answer is similar to the following:
class Interpreter
def initialize(&block)
instance_eval(&block)
end
def at(time)
@time = time
self
end
def when(date)
@date = date
self
end
def we(*people)
@people = people
self
end
def going(where)
@where = where
self
end
def output
[@people.join(' and '), "are going", @where, @date, "at", @time].join(' ')
end
end
Another way is to build pipeline via chainable_methods gem.
Described in the article
require 'chainable_methods'
module SimpleMath
include ChainableMethods
def add(a, b=0)
a + b
end
def subtract(a, b=0)
a - b
end
end
SimpleMath.
chain_from(5).
add(5).
add(5).
subtract(3).
unwrap