Handling Money in Rails – Part III

This is Part III in a series of posts addressing the problems and solutions that I have encountered while building an eCommerce application. Part I and Part II are available here.

For everyone who’s been following along, we’ve already built a Payment class and explored various ways of “monetizing” the class. We’ve built our own accessor methods, and used ActiveRecord::Base‘s composed_of method.

We’re now at the point where we may have multiple classes that will all be dealing with money, and we’re looking for something a little less cumbersome.

Rails Magic

I still distinctly remember the first time I used Rails and I saw how easy it was to define relationships between models. The first time I used has_many, I was hooked; even if I didn’t know how it worked.

So, today, we’re going to define our own method monetize that will do all of the work that we did in our first two posts.

Our end goal will look like this:


# app/models/payment.rb

class Payment < ActiveRecord::Base
  monetize :amount
end

Very succinct, but I also believe very expressive. I think that most people dipping into this code will understand that the amount method will return some sort of “monetized” result. This is much cleaner than defining our own getters and setters, or even using the composed_of method which can be a little confusing.

They’re Just Class Methods!

The day that I realized that has_many, belongs_to, and acts_as_list were just class methods, was when I realized the power of being able to define my own.

So, let’s go ahead and define self.monetize that we can call on our payment class. As you’ll quickly see, I’ve made the decision to define my “monetized” methods using our original approach of having getters and setters. I could just as easily dynamically added composed_of blocks of code, but I’d like this to work on any Ruby object, not just those that inherit from ActiveRecord.


# app/models/payment.rb

monetize :amount

def self.monetize(field)
  define_method field do
    amount = send "#{field}_cents"
    currency = send "#{field}_currency"

    Money.new amount, currency
  end

  define_method "#{field}=" do |value|
    raise ArgumentError unless value.respond_to? :to_money
    money = value.to_money
    send "#{field}_cents=", money.cents
    send "#{field}_currency=", money.currency_as_string
    money
  end
end

This object should still pass all of our original tests with no issues.

How is This Any Different?!

I know, I know, this really didn’t do anything for us. We’re still defining the getters and setters on the class, but now we just have a new method that wraps it. But, we can now extract this method into its own Module and use it anywhere!

So, let’s start by pulling this block of code into its own file in our lib directory.


# lib/monetizable.rb

module Monetizeable
  def self.included(base)
    base.send :extend, ClassMethods
  end

  module ClassMethods
    def monetize(field)
      define_method field do
        amount = send "#{field}_cents"
        currency = send "#{field}_currency"

        Money.new amount, currency
      end

      define_method "#{field}=" do |value|
        raise ArgumentError unless value.respond_to? :to_money
        money = value.to_money
        send "#{field}_cents=", money.cents
        send "#{field}_currency=", money.currency_as_string
        money
      end
    end
  end
end


# app/models/payment.rb

class Payment < ActiveRecord::Base
  include Monetizable
  monetize :amount
end

Wrapping Up

We’ve now created a module that can be used in any class in our application that can “monetize” mutliple attributes. You could also hook into ActiveRecord with an initializer to include the module automatically.

If you’re curious, I’ve also put this into a small gem. Be sure to check out the lint test to easily ensure that your classes are properly monetized.

This is officially the end of our series about handling money in Rails. I’ll continue to post on a regular basis about all of the things I’ve learned along my journey.

Handling Money in Rails – Part II

This is Part II in a series of posts that will address the problems and solutions that I have encountered while building an eCommerce application. Part I

If you’re following our series, we’ve built a Payment class that has methods to set and get amount which is a Money type. Now we’ve reached the point of needing to persist data.

To review, our current implementation of Payment is:


# app/models/payment.rb

class Payment
  attr_reader :amount_cents, :amount_currency

  def initialize
    @amount_cents = 0
    @amount_currency = 'USD'
  end

  def amount=(amount_value)
    raise ArgumentError unless amount_value.respond_to? :to_money
    money = amount_value.to_money
    @amount_cents = money.cents
    @amount_currency = money.currency_as_string
    money
  end

  def amount
    Money.new amount_cents, amount_currency
  end
end

A Migration

The first thing we’ll need is a quick migration to actually be able to persist our payments.


class CreatePayments < ActiveRecord::Migration
  def up
    create_table :payments do |t|
      t.integer :amount_cents,    null: false, default: 0
      t.string  :amount_currency, null: false

      t.timestamps
    end
  end

  def down
    drop_table :payments
  end
end

Composition with composed_of

Ok, now we just need to inherit from ActiveRecord::Base and get to saving and retriving this data from our database. We’re also going to make use of composed_of to eliminate our setters and getters. the full documentation is also available at api.rubyonrails.org.

In essense the method is passed 2 parameters:

  1. The name of the attribute, as a symbol
  2. An options hash

The attribute param should be obvious in our case (:amount), and we’ll look at the options in a little more detail.

The first option we’ll look at is the :class_name, which again should fairly obviously be “Money” since we always want our amount to be an instance of Money.


# app/models/payment.rb

composed_of :amount, class_name: 'Money'

Mapping the Attributes

Next, we’ll look at the :mapping option. This one is a little less apparent from the start, but makes a bit of sense as we examine it more closely.

The :mapping key takes an array of arrays. Each array is a one-to-one mapping with the first item being the attribute of the model class, and the second item being the attribute of the target class, in our case: Money.

The 2 attributes we need to set on our Payment class are amount_cents and amount_currency. These match-up with the cents and currency_as_string attributes on the Money class, respectively.

Attribute in Payment Attribute in Money
amount_cents cents
amount_currency currency_as_string

So, about halfway through, our method call should look like this:


# app/models/payment.rb

composed_of :amount,
  class_name: 'Money',
  mapping: [ %w(amount_cents cents), %w(amount_currency currency_as_string) ]

Getter = Constructor

The next option that we need to set is, :constructor. This key takes either a method name or a Proc that is called when getting our amount. The method or Proc is passed arguments for each of the attributes set in our :mapping arrays (amount_cents, and amount_currency).

Notice that the constructor is essentially the same implementation as the original amount method definition.


# app/models/payment.rb

composed_of :amount,
  class_name: 'Money',
  mapping: [ %w(amount_cents cents), %w(amount_currency currency_as_string) ],
  constructor: Proc.new { |cents, currency| Money.new(cents, currency) }

Setter = Converter

The final option that we need to set is, :converter. This key takes either the name of a method on the class that was set in our earlier :class_name option, or a Proc that is called when setting the amount attribute. This Proc will be called with one argument (the new value to assign to amount), and only if the argument passed is not an instance of :class_name (Money).

Ok, we’re finally ready to finish off our method call and have a fully monetized amount attribute for Payment. Again, notice that this is the same implementation as our original amount= method.


# app/models/payment.rb

composed_of :amount,
  class_name: 'Money',
  mapping: [ %w(amount_cents cents), %w(amount_currency currency_as_string) ],
  constructor: Proc.new { |cents, currency| Money.new(cents, currency) },
  converter: Proc.new { |value| value.respond_to?(:to_money) ? value.to_money : raise(
    ArgumentError, "Can't convert #{value.class} to `Money`"
  ) }

Success! We have a Payment class that knows how to represent an amount as actual money, but can save to any database that only knows about integers and strings.

All of the tests from our earlier suite should still be green, and we’ve pushed all of the converting to ActiveRecord.

Hopefully, you’ve learned a little about the not commonly discussed composed_of method, and are already envisioning ways to compose your models of multiple different types of classes without worrying about serialzing data into the database.

While this is can come in very handily, it can also be cumbersome to use in multiple models. For example, we would most definitely be violating DRY if we had classes representing Payment, Product, LineItem, Order that all had essentially the same composed_of method call. This doesn’t even mention the possibility of having multiple attributes on a model that need to monetized.

So, next time we’ll look into creating our own monetize class method to take care of all of this for us.

Handling Money in Rails – Part I

This is Part I in a series of posts that will address benefits of using an actual Money type in your Ruby or Rails projects.

So, you have a working eCommerce application with customers, products, shopping carts, and line items, now you need to start collecting and managing money in droves and droves.

No problem, right? Just add a Payment class with an amount, and start solving the next problem! But what type of attribute should #amount be?

There are many ways to handle representing money in your system: Fixnum, Float, String, but which will work best?

Most tutorials and examples that I see regarding handling money suggest that you use a Fixnum integer to represent the amount in cents of a product price, a payment amount, or any other object that may need to represent money.

It’s also been suggested to use a Float so we get a closer representation of what money typically looks like. However, anytime this approach is used you’ll see extra code needed to handle precision, and to correct rounding errors.

In almost all cases, when displaying this information to a user, the Rails view helper number_to_currency is used. This should be our first hint that something is a bit off. We just asked Rails to display a number as currency… Shouldn’t there be a way to have a currency object that knows how it should display itself?

Also, when dealing with user input, we can expect to receive a wide range of values that all represent the same amount.

  • 15
  • $15
  • 15.00
  • $15.00

Instead of imposing strict input validations, and using something along the lines of validates_numericality_of our users should be able to enter any of the above and have it just work.

There just seem to be a mountain of issues and edge cases that come with using primitive Ruby constructs to represent a non-primitive object.

This is without even mentioning the possibility of needing to handle multiple currencies, or dealing with exchanging one currency for another.

Luckily, we do have access to Money objects in Ruby; there is a widely accepted library [Money][1]. Similar to how you’re already probably handling amounts, this object accepts an amount in cents, and an optional currency type.

A Failing Test

So, let’s get started by writing a quick test to describe how a future Payment class would handle money amounts. We’re going to test various forms of input that we listed earlier that may make their way through our application at some point.


# test/unit/payment_test.rb

require 'test_helper'

class PaymentTest < MiniTest::Unit::TestCase
  def setup
    @payment = Payment.new
    @fifteen_dollars = Money.new '1500', 'USD'
  end

  def test_string
    @payment.amount = '15'
    assert_equal @fifteen_dollars, @payment.amount
  end

  def test_string_with_symbol
    @payment.amount = '$15'
    assert_equal @fifteen_dollars, @payment.amount
  end

  def test_string_decimal
    @payment.amount = '15.00'
    assert_equal @fifteen_dollars, @payment.amount
  end

  def test_string_decimal_with_symbol
    @payment.amount = '$15.00'
    assert_equal @fifteen_dollars, @payment.amount
  end

  def test_integer
    @payment.amount = 15
    assert_equal @fifteen_dollars, @payment.amount
  end

  def test_decimal
    @payment.amount = 15.00
    assert_equal @fifteen_dollars, @payment.amount
  end
end

A fairly straight forward test that should most obviously fail. Our job is, just as obviously, to get it to pass.

Making It Pass

We're going to start with the most basic Ruby object to represent payments in our system and the behavior they need to encapsulate.


# app/models/payment.rb

class Payment attr_reader :amount_cents, :amount_currency

  def initialize
    @amount_cents = 0
    @amount_currency = 'USD'
  end

  def amount
  end

  def amount=(amount_value)
  end
end

This class should get us passed the first rule of TDD: make the test green or change the error message. We have changed the error message, but now it's time to make it green.

Our first step will be to make our application "money aware" by requiring the gem. As always, this is as simple as adding:


# Gemfile

gem 'money', '~> 5.1.0'

Now to implement #amount=. This needs to take an input value, and set the appropriate instance variables to be used when retrieving the amount.

Again the Money gem will handle this for us in most cases. They have very kindly provided #to_money to coerce any Numeric or String object into money. With this help, our method becomes trivial to implement.


# payment.rb

def amount=(amount_value)
  raise ArgumentError unless amount_value.respond_to? :to_money
  money = amount_value.to_money
  @amount_cents = money.cents
  @amount_currency = money.currency_as_string money
end

Since we also know that a Money object just needs an amount in cents and a currency type, again our amount method becomes simple to implement as well.


# payment.rb

def amount
  Money.new amount_cents, amount_currency
end

Just like that, we should have passing tests and an object that knows how to handle money!

That's it for Part I.