You already have a class that encapsulates wallet / cent pairs and the operations are also performed pair-wise. Why not take advantage of it and make +, - and * take a (single) MoneyBox instance as their argument and return the result as another MoneyBox instance, e.g.: (arithmetic operators shouldn't modify their operands)
class MoneyBox
attr_accessor :wallet, :cent
def initialize(wallet, cent)
@wallet = wallet
@cent = cent
end
def +(other)
MoneyBox.new(wallet + other.wallet, cent + other.cent)
end
def -(other)
MoneyBox.new(wallet - other.wallet, cent - other.cent)
end
def *(other)
MoneyBox.new(wallet * other.wallet, cent * other.cent)
end
def to_s
"#{wallet},#{cent}"
end
end
Example usage:
cash1 = MoneyBox.new(500, 30)
cash2 = MoneyBox.new(100, 15)
puts "#{cash1} + #{cash2} = #{cash1 + cash2}"
# 500,30 + 100,15 = 600,45
puts "#{cash1} - #{cash2} = #{cash1 - cash2}"
# 500,30 - 100,15 = 400,15
puts "#{cash1} * #{cash2} = #{cash1 * cash2}"
# 500,30 * 100,15 = 50000,45
To multiply both wallet and cents by 2 you'd use a MoneyBox.new(2, 2) instance:
puts "#{cash1} - #{cash2} * 2 = #{cash1 - cash2 * MoneyBox.new(2, 2)}"
# 500,30 - 100,15 * 2 = 300,0
Note that operator precedence still applies, so the result is evaluated as cash1 - (cash2 * Money.new(2, 2)).
If you want to multiply by integers directly, i.e. without explicitly creating that MoneyBox.new(2, 2) instance, you could move that logic into * by adding a conditional, e.g:
def *(other)
case other
when Integer
MoneyBox.new(wallet * other, cent * other)
when MoneyBox
MoneyBox.new(wallet * other.wallet, cent * other.cent)
else
raise ArgumentError, "expected Integer or MoneyBox, got #{other.class}"
end
end
Which gives you:
cash1 = MoneyBox.new(500, 30)
cash2 = MoneyBox.new(100, 15)
puts "#{cash1} - #{cash2} * 2 = #{cash1 - cash2 * 2}"
# 500,30 - 100,15 * 2 = 300,0
Note that this only defines MoneyBox#* and doesn't alter Integer#*, so 2 * cash2 does not work by default:
2 * cash2
# TypeError: MoneyBox can't be coerced into Integer
That's because you're calling * on 2 and Integer doesn't know how to deal with a MoneyBox instance. This could be fixed by implementing coerce in MoneyBox: (something that only works for numerics)
def coerce(other)
[self, other]
end
Which effectively turns 2 * cash2 into cash2 * 2.
BTW if you always call * with an integer and there's no need to actually multiply two MoneyBox instances, it would of course get much simpler:
def *(factor)
MoneyBox.new(wallet * factor, cent * factor)
end
+operator seems to deal with three operands (self, and the two argumentsobjandobj_cent), and this is not possible, since+, no matter how you redefine it, can syntactically have only two operands.