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:
- It gives you a
Moneyclass, and instances of this class (
Moneyobjects) 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.
- It provides you with a
Money::Currencyclass, which contains all the information about a certain monetary unit. Instances of this class are how we will represent different currencies.
- It includes APIs to exchange one kind of currency into another.
- It represents money in
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
Notice that the amount must be passed in as cents, while the currency must be passed in as either a
String or a
Money usually means math, so let’s do some:
1 2 3 4
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:
In addition to comparing currencies, you can create an access them as objects:
1 2 3
You can access any information of a currency (which is, again, just a
Money object), including its
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:
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
Wait…Ruby thinks that the difference between
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
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
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
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
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:
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.
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:
money-railslibrary allows you to create
Moneyobjects, including different currencies. Ruby
BigDecimalobjects round more accurately and are more precise than
Floatobjects, making them more preferable when working with money.
- Read more about the methods available to
BigDecimalobjects in the Ruby documentation, and check out all the cool stuff that the
moneygem can do over on their website.
- Want to know more about what makes floats weird? Read this blog post.