Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

Creating user-friendly validation messages with the Money gem

Most of the apps that I work on involve dealing with money in some form. I’m a big fan of the Money gem, which allows you to
store currency values in cents in an integer column in the database, then turn it into a easy-to-user Money object.
One problem I have with the Money gem is that when it converts strings to a Money object it doesn’t store the original
value, which makes it hard to show friendly validation messages to users.

In this post I’ll explain how you can make the Money gem a bit friendlier to use. The story goes something like this:

As a customer
When I enter "$21.045" in to a money form field
I want to see a validation error saying it's an invalid amount
And I want to see "$21.045" in the form field
Because my credit card cannot be charged fractional cents
And I most likely made an error

Extend money

First, extend money and add a new field to store the original value:

# config/initializers/money_ext.rb

Money.class_eval do
  attr_accessor :original_value

Configure the ActiveRecord objects

Then, craft a composed_of declaration that stores the original value when it’s being set, which can be used by ActiveRecord objects:

class Product < ActiveRecord::Base
  composed_of :price,
              :class_name => "Money",
              :mapping => ["price_in_cents", "cents"],
              :converter => proc { |value|
                money = value.to_money
                money.original_value = value

Now when you assign a price to a Product, you can access its original value: => "$21.567").price.original_value # => "$21.567"

If you need this in multiple models, you can easily extract it to a module:

module SmartMoney
  def smart_money(column)
    composed_of column,
                :class_name => "Money",
                :mapping => ["#{column}_in_cents", "cents"],
                :converter => proc { |value|
                  money = value.to_money
                  money.original_value = value

class Product < ActiveRecord::Base
  extend SmartMoney
  smart_money :price

Expose the original value in forms

To show the users the original value in their forms, you can create a custom form builder for your app, and add a new
money_field method that will do the right thing, like so:

# app/helpers/my_custom_form_builder.rb

class MyCustomFormBuilder < ActionView::Helpers::FormBuilder
  def money_field(method, options = {})
    value = @object.send(method)
    formatted_value = value.original_value.presence || value.format
    text_field method, options.merge(:value => (formatted_value))

# config/initializers/default_form_builder.rb

ActionView::Base.default_form_builder = MyCustomFormBuilder

The money_field method first checks for the presence of an original_value and shows it if it’s there, then
defaults to the format method if original_value is not present. You can now use this money_field like any other
form helper:

# in any view

<%= form_for @product do |f| %>
  <%= f.money_field :price %>
<% end %>

Add validations

Now that the Money object, the model and the view are configured properly, you can add custom validations that can access the
original value that the user entered. This Rails 3 validator is an example of one that only allows user input with up to 2 decimal places:

# app/validators/whole_cent_validator.rb

class WholeCentValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    _, cents = value.original_value.to_s.gsub(/[^0-9.]/, '').split(".")
    if cents && (cents.length > 2)
      record.errors[attribute] << (options[:message] || "must be a valid dollar value")

You can add this to the Product class like so:

class Product < ActiveRecord::Base
    validates :price,
              :whole_cent => {
                :message => "must be a valid dollar amount between $1.00 and $10,000.00"


Even though it involves extending Money, adding custom form builder methods, creating custom validations and crafting
a non-standard composed_of declaration, it’s relatively simple to add user-friendly validations to Money fields in such
a way that it’s easy to use for all of your money fields app-wide.

  1. Brennan Falkner says:

    “One problem I have with the Money gem is that when it converts strings to a Money object it doesn’t store the original value, which makes it hard to show friendly validation messages to users.”

    I think you’re supposed to assign price_before_type_cast the original string value. Then the form helpers will display the original value and that variable can be accessed from validations.

  2. Jeff Dean says:

    If `price` were a field in the database, you would be correct. But since I used `composed_of`, Rails does not store the the value before type cast (before conversion):

    product = => “$21.045″)
    product.price_in_cents_before_type_cast # => 2105
    product.price_before_type_cast # => undefined method

    I didn’t think of it before, but I could have created a custom `composed_of` (or patched Rails) to stored the value before conversion, which would have been a bit more general-purpose. Sounds like a great addition to Rails!

  3. Rajan Agaskar says:

    Always nice to google a problem and find a Pivot on the other end with a solution.

    I am also using the money gem and was hoping I could trick the model into letting me store off the user-entered value without having to patch it, but without alias-method-chaining, I think this looks pretty gnarly. I’ll just go ahead and implement the monkey patch you’ve got for the money gem.

    FWIW, I’m using the following regex format validation on my user-entered string:


    Thanks Jeff!

    I’m adding the following as helpful search keywords:
    validation composed_of rails validate

  4. Rajan Agaskar says:

    Oh, also, I agree that composed_of should *definitely* be storing this off as a before_type_cast. I’ll +1 any patch you write for this in a heartbeat.

  5. Rajan Agaskar says:

    Oops, one last note: that regex needs an end of string to prevent wayward matches:


Post a Comment

Your Information (Name required. Email address will not be displayed with comment.)

* Copy This Password *

* Type Or Paste Password Here *