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 BigDecimal
s 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
integer
column 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_s
displays a decimal formatted number ("1234.00")product.price.formatted
displays 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