I'm in the process of learning Ruby on Rails and I've set myself the task of putting together a very basic shopping cart system. I have a table items that costs of a price column currently set to integer. I have no problem with inputting the data in cent, but when it comes to displaying the price in th view, well I obviously want it to be in Euros & cent. Can anyone tell me what the best currency/money handling practice is with RoR? An example?
views:
7215answers:
4Common practice for handling currency is to use decimal type. Here is a simple example from "Agile Web Development with Rails"
add_column :products, :price, :decimal, :precision => 8, :scale => 2
This will allow you to handle prices from -999,999.99 to 999,999.99
You may also want to include a validation in your items like
def validate
errors.add(:price, "should be at least 0.01") if price.nil? || price < 0.01
end
to sanity-check your values.
You'll probably want to use a DECIMAL type in your database. In your migration, do something like this:
# precision is the total amount of digits
# scale is the number of digits right of the decimal point
add_column :items, :price, :decimal, :precision => 8, :scale => 2
In Rails, the :decimal type is returned as BigDecimal, which is great for price calculation.
If you insist on using integers, you will have to manually convert to and from BigDecimals everywhere, which is probably a pain in the ass.
As pointed out by mcl, to print the price, use:
number_to_currency(price, :unit => "€")
#=> €1,234.01
Just last week I dealt with this issue. I have a class FinancialDocument with an amount attribute which is an integer. I recommend storing money in cents in an integer column. It's trivially easy to multiply and divide by 100 and it prevents certain kinds of errors.
In my show view, I format the amount like this:
number_to_currency financial_document.amount.to_f / 100
In my add and edit views I offer dollars and cents inputs. That makes it really easy to ensure users only enter valid money amounts. I added these methods to the FinancialDocument model to help display preexisting values in those inputs:
def format_dollars
if amount && amount > 0
(amount / 100).floor()
else
''
end
end
def format_cents
if amount && amount > 0
amount % 100
else
'00'
end
end
As mentioned, the model attribute is amount but the user enters amount_dollars and amount_cents. How does that work?
I added this to the model to handle the incomming money data:
# Declare attributes to handle dollars and cents, even though
# they aren't DB columns.
attr_accessor :amount_dollars, :amount_cents
# Declare a Rails `before_save` callback method. ActiveRecord
# will call this method every time it saves an instance of the
# model.
before_save :set_amount_based_on_dollars_and_cents
def set_amount_based_on_dollars_and_cents
d = 0
c = 0
d = @amount_dollars.to_i unless @amount_dollars.blank?
c = @amount_cents.to_i unless @amount_cents.blank?
self.amount = (d * 100) + c
end
Here's a fine, simple approach that leverages composed_of (part of ActiveRecord, using the ValueObject pattern) and the Money gem
You'll need
- The Money gem (version 3.1.0)
- A model, for example
Product - An
integercolumn in your model (and database), for example:price
Write this in your product.rb file:
class Product < ActiveRecord::Base
composed_of :price,
:class_name => 'Money',
:mapping => %w(price cents),
:converter => Proc.new { |value| value.respond_to?(:to_money) ? value.to_money : Money.empty }
...
What you'll get:
- Without any extra changes, all of your forms will show dollars and cents, but the internal representation is still just cents. The forms will accept values like "$12,034.95" and convert it for you. There's no need to add extra handlers or attributes to your model, or helpers in your view.
product.price = "$12.00"automatically converts to the Money classproduct.price.to_sdisplays a decimal formatted number ("1234.00")product.price.formatteddisplays a properly formatted string for the currency- If you need to send cents (to a payment gateway that wants pennies),
product.price.cents.to_s - Currency conversion for free