Tuesday, July 01, 2008

Don't lose my Money.new, or why 1 / 3 = ?

If you're using the Money object in rails, you'll be storing your money as cent in your DB. Then, you use composed_of to read this into a whole separate object (of type Money).

This works great, but, I don't like losing Money.

The problem is, Money objects can't go into fractions below a cent. So when you divide 1 by 3, you can't have .33333 reoccurring... you get .33.

We tackled this issue by modifying division to round to the nearest cent, but auto-calculate the total error. This only makes sense when you have a way of dealing with the rounding...


class Money
alias_method :lossy_divide, :/
def /(*params)
raise "Do not divide - losing Money!"
end

def split_between(x)
result = self.lossy_divide(x)
rounding_error = self - (result * x)

result, rounding_error
end
end


In this way, you 'split' money rather than exactly divide.

Our use-case is that we want to split the money between x people, or amongst x days. So, we generally have to assign the split money out to different instances.

This works beautifully if we return an array representing all the splits, and add the rounding error to the first entry:


class Money
alias_method :lossy_divide, :/
def /(*params)
raise "Do not divide - losing Money!"
end

def split_between(x)
result = self.lossy_divide(x)
rounding_error = self - (result * x)

[result + rounding_error] + (2..x).map{result}
end
end

No comments: