Topic: Storing Money as Cents (integers)

I've been reading around on how to store money in the database and decided on storing them as integers (thus changing the amount to cents).

This is what I came up with to check before creating a record if it is a float:

# transaction.rb
class Transaction < ActiveRecord::Base

        before_create :check_if cents

    def check_if_cents
        if self.amount.class.to_s == "Float"
            cents_amount = 100 * self.amount
            self.amount = cents_amount.to_i
        end
    end
end


is this what I'm looking for?  Or am I going about this the wrong way. 

Thanks.

Personal Site josephhsu.com
Working with Rails profile
Twitter jhsu

Re: Storing Money as Cents (integers)

Hmm..I'd just use a virtual accessor:

class Transaction < ActiveRecord::Base
  def amount_dollars; amount/100.0; end
  def amount_dollars=(a); amount = (a*100).to_i; end
end

This prevents the situation where you attempt to assign a Fixnum as an amount in dollars (not as cents). You just access it as "amount_dollars" instead of "amount" (if you wanted, you could rename the column to "amount_cents" or similar and make the virtual attribute "amount".

I've also heard good things about this plugin:

http://agilewebdevelopment.com/plugins/rails_money

Re: Storing Money as Cents (integers)

Ah, thanks.

After looking around, I found a railscast about virtual attributes:
http://railscasts.com/episodes/16

Personal Site josephhsu.com
Working with Rails profile
Twitter jhsu

Re: Storing Money as Cents (integers)

How would i create a transaction and set the amount_cents from a form?

from a form, would I set the amount value (and it would update the amount_cents in the database) or would i  set the amount_cents (then how would it convert it to cents, using this method)?

Personal Site josephhsu.com
Working with Rails profile
Twitter jhsu

Re: Storing Money as Cents (integers)

If you keep the database column as "amount" and make the virtual attribute "amount_dollars", you'd do this:

# in your form
<%= f.text_field :amount_dollars  %>

If you make the database column "amount_cents" and make the virtual attribute "amount", you'd do this:

# in your form
<%= f.text_field :amount %>

When the form is rendered, Rails calls amount (in the latter exampler), which in turn reads amount_cents and divides by 100.0 and displays that in the text field. When the form is submitted, ActiveRecord assigns amount to the amount in dollars (such as 4.13), which triggers your virtual attribute. The number is then multiplied by 100 (turning it into 413) and then assigned to the amount_cents column in the database.

Does that make sense?

Re: Storing Money as Cents (integers)

still can't seem to get this to work. take a look:

account has many transactions.

# form
<% form_for :transaction, :url => {:action => 'create', :id => account.id} do |f| -%>
<%= f.text_field :name %>
<%= f.text_field :amount %>
<%= submit_tag 'Create' %>
<% end -%>

# transaction.rb (model)

class Transaction < ActiveRecord::Base
    belongs_to :account
   
    def amount; amount_cents/100.0; end
    def amount=(number); amount_cents = (number*100).to_i; end
end


# Controller, create method
    def create
        @account = Account.find(params[:id])
        @account.balance += (params[:transaction][:amount]*100).to_i
        @transaction = @account.transactions.build(params[:transaction])
        if @account.save
            respond_to do |format|
                format.html { redirect_to :action => 'show', :id => @account.id}
                format.js
            end
        end
    end


and the error it gives me:

SQLite3::SQLException: SQL logic error or missing database: INSERT INTO transactions ("name", "account_id", "date", "amount_cents") VALUES('test5', 1, '2008-02-08 17:17:36', NULL)

It doesn't seem to set the amount_cents value as amount*100

any ideas?

Personal Site josephhsu.com
Working with Rails profile
Twitter jhsu

Re: Storing Money as Cents (integers)

Whoops, sorry. Try this:

def amount; amount_cents/100.0; end
def amount=(number); write_attribute(:amount_cents, (number*100).to_i); end

I think you may need to call write_attribute directly. See how that goes.

Re: Storing Money as Cents (integers)

I'm just wondering if storing money in cents is desirable and/or necessary in Rails. The Agile book has a sidebar on page 72 that notes that if you make your database column a "decimal" type, both the database and Ruby store the value as a "scaled integer", not a floating point number. I haven't tested this personally, but it would be handy if that number handling was automatic.

Re: Storing Money as Cents (integers)

Yes, the "decimal" type is normally used to store money (with 2 decimal points exactly). I assumed that onizuka had compatibility or specification reasons for using an integer, but you're correct: decimals are normally the preferred option.

Re: Storing Money as Cents (integers)

I've been playing with integer fields using the :scale and :precision hashes.. seems to work so far.

manitoba's solution is pretty interesting though

Re: Storing Money as Cents (integers)

manitoba98 wrote:

Whoops, sorry. Try this:

def amount; amount_cents/100.0; end
def amount=(number); write_attribute(:amount_cents, (number*100).to_i); end

I think you may need to call write_attribute directly. See how that goes.

Try using self.amount_cents =
From memory, it should work.

Also, Make sure you don't have it in the migration but thats pretty much a given

Re: Storing Money as Cents (integers)

Storing prices in cents as integers is a good idea because it helps avoid rounding errors. Here is a plugin that will help with the boilerplate functionality, http://github.com/jerrett/rails_money.