Words and Code

One writer’s journey from words to code.

Money Makes the World Go Round: Using Money-Rails and BigDecimal

#technicaltuesdays, rails, ruby

Mo money, mo problems. This is especially the case when you’re a new developer trying to onboard onto a huge eCommerce Rails application.

No, but really - money is such a pain in the ass to deal with as a programmer. So much logic and detail goes into accepting a payment, processing a transaction, checking an order’s status…and don’t even get me started on shipping – seriously, I’m saving the entire concept of shipping for another blog post.

But, at some point or another, you have to deal with other people’s money. And you have to try and not screw it up, because apparently people really don’t like that. So, how do you handle all those dolla dolla bills? Yup, you guessed it: with the money-rails library and Ruby’s BigDecimal object.

Get Dat Dough: Implementing Money-Rails

Including the money-rails library is fairly simple: add gem 'money-rails' to your Gemfile and then bundle in your console.

But what exactly is this library, and how do you effectively use it? Well, that’s another thing entirely.

The most important thing to note is that the money-rails library provides integration of the money gem for Rails applications. So, before you go around throwing about your dough every which way, here are a few things about the money gem to keep in mind:

  1. It gives you a Money class, and instances of this class (Money objects) contain all the information about a certain amount of money. Provides a Money class which encapsulates all information about an certain amount of money, such as its value and its currency.
  2. It provides you with a Money::Currency class, which contains all the information about a certain monetary unit. Instances of this class are how we will represent different currencies.
  3. It includes APIs to exchange one kind of currency into another.
  4. It represents money in Integer values, not Float, in order to avoid rounding errors.

This last one is important, so try to remember it – we’re going to come back to this in a bit.

Make It Rain!

Ok, time to get rich: let’s make ourselves some money. Instantiating a Money object isn’t too hard; simply create a new instance and pass it an amount and a currency:

1
2
3
five_bucks = Money.new(500, "USD")  #=> $5.00 USD
five_bucks.cents     #=> 500
five_bucks.currency  #=> Currency.new("USD")

Notice that the amount must be passed in as cents, while the currency must be passed in as either a String or a Money::Currency object.

Money usually means math, so let’s do some:

1
2
3
4
my_money = Money.new(3000, "USD")
your_money = Money.new(1500, "USD")

my_money + your_money == Money.new(4500, "USD")

You can also use the parse method, which takes in a symbol and number in a string format, and returns a Money object with the correct currency type:

1
Money.parse("£60") == Money.new(60, "GBP")

In addition to comparing currencies, you can create an access them as objects:

1
2
3
currency = Money.new(60, "GBP").currency #=> "£60.00 GBP"
currency.iso_code #=> "GBP"
currency.name     #=> "British Pound"

You can access any information of a currency (which is, again, just a Money object), including its name, iso_code, symbol, and delimiter. And if you want to create a new currency, you’d simply need to pass in the values as a hash into the register class method. There’s also the default_currency class method, which does exactly what you think it does, and an exchange_to method, which can be super handy if you ever want to sell your products…well, basically anywhere.

So. You can make money now. Like, actually create it. Do you feel like a god yet? Okay good. Hold onto that feeling, because it’s about to get a little more complicated.

BigDecimal: Not Just Any Ol’ Number

When I was first playing around with this gem, I was much like you: young, naive, thoroughly amused by the fact that I could play with money in my console with (mostly) no consequences. And then I saw this:

1
#<BigDecimal:7fbd6eab87f8,'0.8E1',9(36)>

Um. What? Is that an association? An object? Is it even Ruby?

The answer to my questions were no, yes, and absolutely. Actually, that little guy up there is my new friend, BigDecimal. And by the time you finish reading this post, I think you’re going to like him, too.

Okay, time for a quick exercise! Open up irb and type in: 1.01 - 1.00

1
2
3
4
5
6
2.1.2 :001 > 1.01 - 1.00
 => 0.010000000000000009
2.1.2 :002 > n = _
 => 0.010000000000000009
2.1.2 :003 > n > 0.01
 => true

Wait…Ruby thinks that the difference between 1.01 and 1.00 is greater than 0.01? OH SHIT. DID WE BREAK RUBY?!

Nope, nope we did not. But, we did just discover the one true flaw of Ruby Float, which is that they can’t store decimals very precisely. Apparently, the reason for this is that floats are stored in a binary number format, which means that there’s a lot of conversion from binary to decimal going on under the hood.

This probably doesn’t seem all that important, but here’s the thing: people really don’t like it when you take their money. And when you use floats, you may very well be rounding up in cases when you really shouldn’t be, which would result in some sort of numerical error.

Enter our new buddy, BigDecimal. We can do the exact same calculation with this object, but without the arbitrary rounding! If we try it out in irb, we’ll get the number that we were originally expecting:

1
2
3
4
5
6
2.1.2 :005 > BigDecimal.new("1.01") - BigDecimal.new("1.00")
 => #<BigDecimal:7fee2add1560,'0.1E-1',9(27)> 
2.1.2 :006 > n = _
 => #<BigDecimal:7fee2add1560,'0.1E-1',9(27)> 
2.1.2 :007 > n.to_f
 => 0.01

If you remember scientific notation from middle school, then you’ll notice what’s going on in 0.1E-1, and how that converts to 0.01 when we called the to_f method on it. This takes more time, but it is far more accurate – something that’s pretty important when it comes to the monies.

Remember earlier in the post when I told you to remember that the money gem uses Integer but not Float? Well, it also uses BigDecimal objects, for the same exact reason that we just discovered on our own.

What Is Standard Cannot Be Core

If your first reaction to reading this post was opening up irb and trying to make your own BigDecimal object, then you most certainly encountered this error:

1
2
3
4
 irb
2.1.2 :001 > BigDecimal.new
NameError: uninitialized constant BigDecimal
  from (irb):1

Before you freak out, let me assure you that I did not lie to you – BigDecimal most surely is a real thing. But it’s not a core thing. What I mean by that is, BigDecimal is not part of the Ruby Core Library; it’s actually part of the Ruby Standard Library.

If you’re thinking to yourself, Whut?! Ruby has two different libraries?, you’d be right. But even though both deal with Ruby objects, there’s a fundamental difference between the two.

The Ruby Core Library is what you use every day, including basic objects such as String, Integer, Float, Array, Hash, and many others. On the other hand, the objects in the Ruby Standard Library may seem a little less familiar. Unlike the Core Library, objects from the Standard Library have to be explicitly required. BigDecimal objects are part of the Standard Library, as are Abbrev, Logger, and Matrix objects.

The Standard Library is an extension of the Ruby language. You will always have access to the Core Library, but if you want to use an object that exists outside of the Core Library, you’ll need to specifically require it. So, if you want to play with BigDecimal in the console or use it in your application, you’ll have to type this in to have access to the object: require 'bigdecimal'.

More Bang For Your Buck

When I first started reading about BigDecimal, I wondered why I had never run into this type of object before. How could I have missed it entirely? As a beginner, it may not sense to use this type of Ruby object at all, and it may very well slow you down. In fact, I think floats are the default object in use for a reason: they’re much cheaper to use in memory, and can convert, lookup, and do arithmetic much faster than BigDecimal. Calculations on BigDecimal objects are much slower because they are objects, which take up far more space and memory. However, if you’re actually dealing with currency values – building an ecommerce application, for example – BigDecimal still seems to be a clear winner to me.

The money gem and money-rails library provide a lot of flexibility when it comes to handling money in your application mostly because they create Money objects. I’m a huge fan of object-oriented programming, and these libraries allow you to manipulate and access your currency with far more ease than if they were stored as simple integers, or even floats.

However, these resources are only the tip of the iceberg when it comes to dealing with money in your Rails application. Another awesome library is monetize, which allows you to convert different types of Ruby objects into Money objects. Once you get the basic structure and namespacing of these libraries, learning about new ones will be a piece of cake. Pretty soon, you’ll be breaking the bank with all your knowledge about using money in Rails. Maybe you’ll even end up like this guy:

tl;dr?

  • The money-rails library allows you to create Money objects, including different currencies. Ruby BigDecimal objects round more accurately and are more precise than Float objects, making them more preferable when working with money.
  • Read more about the methods available to BigDecimal objects in the Ruby documentation, and check out all the cool stuff that the money gem can do over on their website.
  • Want to know more about what makes floats weird? Read this blog post.