Ruby Money & BigDecimal

The problem

In my current job, we faced calculation errors when operating with float for Money.
After some investigation, we found this article

Our first approach was to find every usage of money attributes and parse them with BigDecimal.
T…


This content originally appeared on DEV Community and was authored by M Bellucci

The problem

In my current job, we faced calculation errors when operating with float for Money.
After some investigation, we found this article

Our first approach was to find every usage of money attributes and parse them with BigDecimal.

This solution has some drawbacks. First, we would need to replace it in many places. Second, it doesn't prevent future developers to use float.

In order to overcome those issues, I wanted to enforce a validation over every money attribute.
Then if a future execution accidentally does money_attr = 233.0 (float) we could detect that error and report it.
After thinking for a moment I thought that would be preferable to do a conversion (float->BigDecimal) rather than raising an error.

So I'd like to write Ruby code to say: "hey if someone tries to assign a float to a money attribute then convert it to BigDecimal"

The Solution

In order to do that I came up with this solution:

module BigDecimalCheck
  def self.included(klass)
    klass.extend(ClassMethods)
  end

  module ClassMethods
    def enforce_big_decimal(*attrs)
      attrs.each do |attr|
        define_method :"#{attr}=" do |value|
          # try to convert argument to BigDecimal
          instance_variable_set(:"@#{attr}", BigDecimal(value.to_s))
        end
      end
    end
  end
end

class Rate
  attr_accessor :money

  include BigDecimalCheck
  enforce_big_decimal :money
end

With that code in place a consumer code would work like this

r = Rate.new
r.money = 33            # works
r.money = 33.0293       # works
r.money = "33"          # works
r.money = "33.0293"     # works
r.money = "no numeric"  # Argument Error

How this solution work?

  • self.included it is a hook that Ruby modules provide. It is called when the module is included and receives the class that included it.

  • klass.extend(ClassMethod) Let's say that klass = Foo, then this would be the same as doing:

class Foo
  extend ClassMethod
  # Now I'm able to call methods in ClassMethod form here
end

which will inject methods from ClassMethod into Foo object at class scope.

  • enforce_big_decimal
    def enforce_big_decimal(*attrs)
      attrs.each do |attr|
        define_method :"#{attr}=" do |value|
          # try to convert argument to BigDecimal
          instance_variable_set(:"@#{attr}", BigDecimal(value.to_s))
        end
      end
    end

If I call enforce_big_decimal :unit_price, total_price
It will define two methods:

def unit_price=(value)
  parsed_value = BigDecimal(value.to_s) # raise error if cannot parse
  instance_variable_set(:@unit_price, parsed_value)
end
def total_price=(value)
  parsed_value = BigDecimal(value.to_s) # raise error if cannot parse
  instance_variable_set(:@total_price, parsed_value)
end

Conslusion

I've shown an example of how to generalize the solution of a problem by using ruby meta-programming techniques.

I hope it can help you solve similar problems.

Feel free to ask questions or suggest improvements.

Thanks for reading!


This content originally appeared on DEV Community and was authored by M Bellucci


Print Share Comment Cite Upload Translate Updates
APA

M Bellucci | Sciencx (2021-05-28T20:26:54+00:00) Ruby Money & BigDecimal. Retrieved from https://www.scien.cx/2021/05/28/ruby-money-bigdecimal/

MLA
" » Ruby Money & BigDecimal." M Bellucci | Sciencx - Friday May 28, 2021, https://www.scien.cx/2021/05/28/ruby-money-bigdecimal/
HARVARD
M Bellucci | Sciencx Friday May 28, 2021 » Ruby Money & BigDecimal., viewed ,<https://www.scien.cx/2021/05/28/ruby-money-bigdecimal/>
VANCOUVER
M Bellucci | Sciencx - » Ruby Money & BigDecimal. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/05/28/ruby-money-bigdecimal/
CHICAGO
" » Ruby Money & BigDecimal." M Bellucci | Sciencx - Accessed . https://www.scien.cx/2021/05/28/ruby-money-bigdecimal/
IEEE
" » Ruby Money & BigDecimal." M Bellucci | Sciencx [Online]. Available: https://www.scien.cx/2021/05/28/ruby-money-bigdecimal/. [Accessed: ]
rf:citation
» Ruby Money & BigDecimal | M Bellucci | Sciencx | https://www.scien.cx/2021/05/28/ruby-money-bigdecimal/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.