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.