Ruby on Rails
HowToUseIntegerFieldsAsMoney

The project I’m working on will be using quite a few currency values. Because the framework maps decimal columns to (imprecise) floats, the Rails book recommends storing money as integers. I’ve been trying to work out a way to do this and use the fields directly in forms. Another reason for using integers is that I’m trying to represent the database schema entirely in migrations.

For a background on aggregation see UnderstandingAggregation. Also, TobiasLuetke has released a Money class for just such a purpose.

The closest example I could find was for LocalizingDates, so I’ve based my solution on that. I’ve been hacking about with the Depot application as an experiment, and this is what I came up with.

First of all, in the products table, I replace the price decimal(10,2) field with price_cents integer.

I add a Money class, inside lib/money.rb. This also includes an extension to the String class, similar to the LocalizingDates example:


class Money

  attr_reader :cents

  def initialize(cents)
    @cents = cents
  end
  
  def to_s
    sprintf("%0.2f", @cents / 100.0)
  end
end

class String
  def cents
    Integer(sprintf("%0.0f", self.to_f * 100.0))
  end
end

Then the Product model (model/product.rb) can be adjusted to work with this shadow-class.


class Product < ActiveRecord::Base

  require 'money'

  validates_presence_of :title, :description, :image_url
  validates_numericality_of :price
  
  composed_of :price, :class_name => Money,
      :mapping => [:price_cents, :cents]
  
  private #Need this for v_n_o to work!
  def price_before_type_cast
    self.price
  end
end

Now, the price attribute can be used as normal in templates like this …


<td><%=h product.price %></td>

… or this …


<%= text_field 'product', 'price' %>

I have no doubt this can be massively improved upon, but I’ve only been experimenting with RoR for the last week or so and couldn’t find any other examples. I think this especially needs some methods/operators on the Money class and some range checking.

Another way: http://blog.codahale.com/2006/05/18/dollars_and_cents-a-rails-plugin/ it’s a plugin that does the same thing