Lets say I create an object a and give it a method .to_i, why can't this object be added to an Integer?
>> a = Object.new
=> #<Object:0x0000000006cfa9d0>
?> def a.to_int
?> 42
>> end
=> :to_int
>> 3 + a
(irb):5:in `+': Object can't be coerced into Integer (TypeError)
from (irb):5:in `<main>'
Thanks to Stefan it works!
irb(main):010:1* def a.coerce other
irb(main):011:1* [other, 42]
irb(main):012:0> end
=> :coerce
irb(main):013:0> 1 + a
=> 43
Adding an integer to an object can be achieved by implementing +, e.g.:
class Foo
def initialize(value)
@value = value
end
def to_i
@value
end
def +(other)
Foo.new(to_i + other.to_i)
end
end
Foo.new(5) + 4
#=> #<Foo:0x00007fbd22050640 @value=9>
In order to add an instance of Foo to an integer, you have to also implement coerce which takes the left-hand value as an argument and returns an array with both values converted to Foo instances, e.g.:
class Foo
# ...
def coerce(other)
[Foo.new(other.to_i), self]
end
end
This gives you:
4 + Foo.new(5)
#=> #<Foo:0x00007fba600e3e28 @value=9>
The docs for Numeric contain another example.
Internally, coerce is called by Integer#+ if the argument is not an integer: (C code)
VALUE
rb_int_plus(VALUE x, VALUE y)
{
if (FIXNUM_P(x)) {
return fix_plus(x, y);
}
else if (RB_TYPE_P(x, T_BIGNUM)) {
return rb_big_plus(x, y);
}
return rb_num_coerce_bin(x, y, '+');
}
rb_num_coerce_bin calls coerce and then invokes the binary operator + on the returned values.
In Ruby this would be: (simplified)
class Integer
def +(other)
if other.is_a?(Integer)
# ...
else
x, y = other.coerce(self)
x + y
end
end
end
You still need to call the to_int method how else does the interpreter know what you want to do?
>> 3 + a.to_int
Ruby doesn't do automatic conversion.
>> 3 + "5"
This will give the same error, even though "5" has a perfectly fine to_i method. Btw. to int methods in ruby are ususally called to_i in case you want to keep the consistency.