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
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. - 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. - It includes APIs to exchange one kind of currency into another.
- It represents money in
Integer
values, notFloat
, 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::Currency
object.
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:
1
|
|
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 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
|
|
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.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 |
|
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 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 createMoney
objects, including different currencies. RubyBigDecimal
objects round more accurately and are more precise thanFloat
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 themoney
gem can do over on their website. - Want to know more about what makes floats weird? Read this blog post.