codahale.com٭blog

This is my old blog. My current writing is here: codahale.com

dollars_and_cents: a Rails plugin

Update: Use of this plugin is deprecated. If you’re using Rails 1.2 or higher, use the :decimal field type for your monetary values. This will provide you with fixed-point math on both the database and Rails sides of your application.


(Yes, another plugin. I’m full of them.)

One of the big problems with a database-agnostic framework like ActiveRecord is that it doesn’t have a decent data type for money. Yes, you can use a FLOAT, but then you end up charging someone $12.3000000000000001, which is just awkward. You can hard-code a database-specific type, but then your code takes a dive when you hit the bigtime and get bounced up to a DB2 backend or some nonsense. Or you could store prices as an integer value of the smallest unit possible–the cent–and do the math in the model, but then you have to write an attribute wrapper, which is boring and not very DRY. Or…

Aw snap, Coda’s hauled off and done it for you! Oh happy people! Oh frabjous day!

Installamacation

Love SVN?


./script/plugin install -x http://svn.codahale.com/dollars_and_cents/

Ambivalent about it?


./script/plugin install http://svn.codahale.com/dollars_and_cents/

Usamization

dollars_and_cents is an easy plugin to use.

Name your fields the right thing

All prices should end with _in_cents. That’s the convention I made up, and you all get to deal with it. I think it’s rational, but I think many things are rational with which other people violently disagree (e.g., my dad and I on the direction in which twisty ties should be fastened–he says clockwise, I say whatever).

In a migration or somesuch:


create_table :buyable_things do |t|
  t.column :name, :string
  t.column :price_in_cents, :integer
end

Profit

Once installed, dollars_and_cents adds a Float attribute price to the model BuyableThing. Use as you would any other attribute:


  @buyable_thing = BuyableThing.find(params[:id])
  old_price = @buyable_thing.price   # => 19.99
  @buyable_thing.price = 23.99
  new_price = @buyable_thing.price   # => 23.99

And now 2399 is stored in the database, in a nice, cross-platform format: the humble int.

(If you had a database field called tulpa_death_chant_cost_in_cents, it would add an attribute called tulpa_death_chant_cost. It’s not limited to the phrase “price”. FYI.)

All yours, under the MIT license © 2006. Woo hah.

50 Responses to “dollars_and_cents: a Rails plugin”

  1. Hampton Says:

    Excellent man! I love this thing.

  2. Sanjeev Says:

    Hmm, plugin doesn’t format prices as I’d expected. For a value in cent’s of 5300 I’d expect the output to be “53.00″ not “53.0″.

    However as I type this I realize that the plugin makes no pretense about formating the output. So take this comment as just an observation rather than a request to modify the plugin.

  3. Coda Says:

    Sanjeev–the reason it does so is because that\’s the way Ruby prints out Numeric objects.

    5.300000.to_s #=> \"5.3\"

    If you\’d like to print out numbers as currency, try this:

    < %= number_to_currency(@monkey.bribe) %>
  4. dollars_and_cents · mornography.de Says:

    [...] dollars_and_cents erleichtert das Speichern von Währungs-Werten in einer Rails-Anwendung, in dem es für Attribute mit Namen wie foo_as_cents, die die Cent-Werte (als Rundungs-sichere Integers) speichern, automatisch Float-Attribute namens foo bereit stellen. Nett. [...]

  5. Jakob S Says:

    Perfect timing, I was pretty much about to implement something like this myself. Thanks Coda.

  6. Sean Porter Says:

    xal has also written the ‘Money’ gem, which takes care of currency as well as the float-to-integer problem:

    http://dist.leetsoft.com/api/money/

  7. Marc Siegel Says:

    Awesome! I prefer this to the Money gem, especially appreaciate the svn integration

  8. Maykel Rodriguez Says:

    I’m getting the following error:

    NoMethodError in Admin/activitiesController#update
    undefined method `price_before_type_cast’ for #

    any idea about what could be the problem? I just followed the easy instructions and my models automatically displayed the prices as floats, but when I try to save I get that error.

    Thanks in advance
    Maykel

  9. Scott Burton Says:

    Maykel, you are probably getting that error from a validation. I was getting the same error. I changed it to validates_presence_of :price_in_cents

  10. Coda Says:

    Thanks, Scott!

    I’ve been Very Busy recently, Maykel, but I’ll see if I can make this act a bit more transparently, especially as Scott’s solution will display stuff like “Price in cents cannot be blank” for error messages, which may confuse the hell out of your users. It should just be a matter of also responding to price_before_type_cast.

  11. Victor Rosillo Says:

    I am getting rounding errors.
    If you input the following numbers:
    18.01 you get into the database field 18.02
    18.10 you get into the database field 18.11
    18.17 you get into the database field 18.18
    18.19 you get into the database field 18.2
    19.94 you get into the database field 19.95
    18.92 you get into the database field 18.93

    my database is called products and the field is called price_in_cents
    i am accessing via a _form:
    (RHTML eaten by WordPress)
    Why?

  12. Coda Says:

    Victor–I’m not sure. What version of Ruby are you running, and what database platform?

  13. Victor Rosillo Says:

    Ruby version 1.8.4 (powerpc-darwin8.6.0)
    RubyGems version 0.8.11
    Rails version 1.1.4
    Active Record version 1.14.3
    Action Pack version 1.12.3
    Action Web Service version 1.1.4
    Action Mailer version 1.2.3
    Active Support version 1.3.1
    Environment development
    Database adapter mysql version: 4.1.12-standard

  14. Coda Says:

    Sorry, Victor, that was a stupid bug of mine. Fixed now.

  15. Victor Rosillo Says:

    Coda;

    Thanks a lot for the fix !!!!

    After installing Revision 10 of the plugin
    AND restarting the server in this case lighttpd
    everything works fine.

  16. ed Says:

    guess I’m mising something. tested out the plugin in console and it keeps returning 0.0 for my max_bid price:

    >> k = KeywordLink.find(2)
    #>, @attributes={ “max_bid_in_cents”=>”2323″, “id”=>”2″}, @new_record_before_save=nil>
    >> k.max_bid
    => 0.0
    >> k.max_bid_in_cents
    => 2323
    >> k.max_bid = 9.99
    => 9.99
    >> k.save
    => true
    >> k
    => #>, @attributes={”max_bid_in_cents”=>999,”id”=>”2″}, @new_record_before_save=nil>
    >> k.max_bid
    => 0.0

    ideas? my db column was created by a migration. it’s a nullable INT(11) named max_bid_in_cents (as you can see by the dump above). running linux, ruby 1.8.4, rails 1.1.4 and the non-svn version of dollars_and_cents

  17. Coda Says:

    Ed, I have no idea. I can’t duplicate that problem in any of my applications, and it’s not something I’ve heard about before. I can’t think of any reason why it would do that–maybe a conflict with another plugin?

    Let me know if you find anything else.

  18. ed Says:

    you’re right about the conflicting plugin. i had given up on your plugin and was setting up to use the Money gem when, in my model, i noticed i had a directive for another plugin (default_values) set for the max_bid field. it was setting the default to 0.0. apparently it did that after dollars_and_cents so all my values were 0.0. i removed that plugin and your’s now works.

    thanks,
    ed

  19. ptorrsmith Says:

    Hi from New Zealand!

    I’m getting a “undefined method `flat_rate’ for #” error when I try to call it in a view.

    I’ve got a model “AssetRate” with a flat_rate_in_cents column in the database.

    When I play with the object in consoler, everything works fine (I can call/set asset_rate.flat_rate no probs), but no within a rhtml view.

    Any ideas?

    Thanks
    Peter

  20. ptorrsmith Says:

    The code snippet from my RHTML that should have been with my previous post/question (i’ve taken out the RHTML tags so it doesn’t get swallowed by WordPress) :

    for asset_rate in @asset_rates

    #this works
    =asset_rate.flat_rate_in_cents

    #this fails
    =asset_rate.flat_rate

    end

  21. Coda Says:

    Peter–Uh, none of the code actually made it into your comment–comments are filtered for HTML.

    Honestly, I don’t have a clue as to why your records wouldn’t work with your views. If it works anywhere, it should work everywhere.

  22. ptorrsmith Says:

  23. ptorrsmith Says:

    Got it sorted… I needed to restart my webserver (mongrel) for it to work in the views… since consoler is direct.

    Doh!

    Cheers
    Peter

  24. ptorrsmith Says:

    Hi Again Coda.

    In terms of usage, to avoid rounding errors, should I perform all calculations (eg x 27.3) on the price_in_cents and then divide by 100 before putting it back inot the price field?

    And if I just want to display (but not persist) a temporary value (price * 1.3314) should I do price_in_cents * 1.3314 / 100 or can I safely do price * 1.3314?

    Thanks.

  25. Coda Says:

    Honestly, you’re probably better off running Edge Rails and using the new :decimal type with BigDecimals.

    But yeah, do all your math in cents and set property_in_cents to the results. If you have a float value from a user, you can convert it to cents using the following:

    params[:dollar_value].to_cents

    That shouldn’t cause too much ruckus.

    For anything display-based, feel free to use the float methods.

  26. relaxdiego Says:

    This is a time saver! Thanks!

    I found the “_in_cents” suffix a bit limiting though and decided to rename it to “_in_hundredths” by changing the CENTS_SUFFIX constant in my application.

  27. Victor Rosillo Says:

    Hi Coda;
    How should I define a tax and how should I use it?

    I would like to define it in my model as a constant and then use it
    with the plugin.

    Say I need to define a sales tax that is 15%

    I was thinking on something like this on the model:


    TAX = [ [:iva => '15'] ].freeze # freeze to make this array constant

    and if i have in the DB something like this:

    create_table "prices", :force => true do |t|
    t.column "price_in_cents", :integer
    t.column "iva_in_cents", :integer
    t.column "total_in_cents", :integer
    end

    then if i had this values:

    price_in_cents = 10000 # $100.00
    TAX = 15 #15%

    how would i get the total price?
    I don't know how to go about so as to not mix a floating point number

  28. chubbard Says:

    After I installed this plugin I started getting problems in my testing code. I think my fixtures aren’t setup properly in the database and an exception is being thrown. Notice how the teardown method is blowing up. I think the setup method is not getting to start a transaction because of an exception, and the teardown is just blindly calling it. But, I can’t find any evidence of the initial exception. My log files don’t have it. Any ideas?

    Here is my stack trace:

    Exception: You have a nil object when you didn’t expect it!
    You might have expected an instance of Array.
    The error occured while evaluating nil.-
    D:/dev/ruby/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/transactions.rb:112:in `unlock_mutex’
    D:/dev/ruby/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/fixtures.rb:534:in `teardown’
    D:/dev/ruby/lib/ruby/1.8/test/unit/testcase.rb:77:in `run’
    D:/dev/ruby/lib/ruby/1.8/test/unit/testsuite.rb:32:in `run’
    D:/dev/ruby/lib/ruby/1.8/test/unit/testsuite.rb:31:in `run’
    D:/dev/ruby/lib/ruby/1.8/test/unit/testsuite.rb:32:in `run’
    D:/dev/ruby/lib/ruby/1.8/test/unit/testsuite.rb:31:in `run’
    D:/dev/ruby/lib/ruby/1.8/test/unit/ui/testrunnermediator.rb:44:in `run_suite’
    D:/dev/RadRails/plugins/org.rubypeople.rdt.testunit_0.8.0.604272100PRD/ruby/RemoteTestRunner.rb:107:in `start_mediator’
    D:/dev/RadRails/plugins/org.rubypeople.rdt.testunit_0.8.0.604272100PRD/ruby/RemoteTestRunner.rb:52:in `start’
    D:/dev/RadRails/plugins/org.rubypeople.rdt.testunit_0.8.0.604272100PRD/ruby/RemoteTestRunner.rb:272

  29. chubbard Says:

    Nevermind I figured it out. RadRails was not showing the exception when setup failed.

  30. TheBlatherskite Says:

    I spent much of today looking for a solution to the “rounding errors with price-as-float” issue, and so far I’m very happy I settled on dollars_and_cents. I wrote about the decision process here, if it’d interest anyone.

    Thanks Coda!

  31. Michael Campbell Says:

    Coda,

    Any progress on making the “…in_cents” more transparent on validation? (Your comment #10, above.)

  32. Alejandro » For many years the customers of Old Munich have accepted the place Says:

    [...] dollars_and_cents: a Rails plugin [...]

  33. Rails and Plugins « Jeff Rasmussen’s Healthcare IT Blog Says:

    [...] dollars_and_cents: a Rails plugin | Archives | codablog | Coda Hale [...]

  34. Jay Says:

    Hi,
    I finally got this plugin to work in the console, I used
    amount_in_cents and i could call the record and update it by
    @amt = Amount.find(3)
    @amt.amount = 23.90
    @amt.save
    and i took the value, but i don’t how to tie to the scaffold i have created. The scaffolding seems to have created a default integer entry field and is not taking decimal values. basically how do i update my record from the view with using this plugin. Appreciate all your help

  35. BenCurtis.com » Blog Archive » Penultimate Rails E-commerce Book Update Says:

    [...] So, instead of keeping people waiting, I decided to make this release, which isn’t quite as big as I was hoping it would be. I did toss in some fun stuff, though, like how to use the dollars_and_cents plugin for storing money amounts in the database and the acts_as_sluggable plugin to help with SEO for your store. You can check out the full list of changes, and of course, you can also buy it now! [...]

  36. cam Says:

    i’m loving your plugin, but the problem i’m running into is that if a input is blank, dollar_and_cents seems to default to 0.0 . I would like to make sure a user inputs a value as opposed to using a default value. is there a way to work around this?

  37. Chris Johnson Says:

    I’m loving the plugin as well, but I’m running into an issue I believe you’ve already solved. I just can’t find the solution.

    I’m working on an application where the users want to see money fields formatted as money, with dollar signs and comma separators. I have written the javascript to make that happen. The issue I’m having is that the dollars_and_cents plugin doesn’t like those changes. It changes the amount to 0.0 before the data is saved to the database.

    I tried to strip out the ‘$’ and commas using before_save and before_validation on the model, but it always finds 0 in the amount field.

    Have you answered this before? Where might I find a solution?

    Chris

  38. Jeff Says:

    hi, i’m working through agile webdev with rails and i’m using your plugin to handle the money side of things. i’m using postgres and it won’t allow me to follow along the mysql tutorial.

    however, i’m not sure how to get the Price formatted correctly - it just displays the number of cents.

    how can I get it to display correctly? i’m working around page 80 of the 2nd edition.

    thanks.

  39. Jed Hurt Says:

    Michael Campbell, you can just do this in your model until Coda gets around to fixing validations:


    def validate
    errors.add(:price, "can't be blank") if price_in_cents.blank?
    end

  40. Coda Says:

    Just to let everyone know, Rails 1.2 will have native support for the DECIMAL fixed-point type, using the BigDecimal class. This means that once 1.2 comes out, this plugin becomes useless. I don’t plan on doing any further development on it, so if you have a pressing need for functionality, please don’t wait for me.

  41. Dean Says:

    What I want to do on my blog, is every few hours take the oldest post and move it to the
    front of the queue, all automatically. Anyone know if there is a plugin that can do this or
    a simple way to set up another plugin to do this (use my own feed perhaps)?
    Thanks.

  42. Jed Hurt Says:

    Just to let everyone know, Rails 1.2 will have native support for the DECIMAL fixed-point type, using the BigDecimal class. This means that once 1.2 comes out, this plugin becomes useless. I don’t plan on doing any further development on it, so if you have a pressing need for functionality, please don’t wait for me

    I don’t know anything about that data type, but I’m assuming it means the we can force the DB to cut off floats at two decimal places?

  43. Coda Says:

    Jed: Effectively, yes. Fixed-point math is what we do with money, and the BigDecimal class and DECIMAL SQL type do that.

  44. Jesse Scott Says:

    Here are a couple of migration helper methods you can use. I created these so that I could write a migration to update my database to use decimal columns instead of the *_in_cents columns now that Rails 1.2 has been released.

    Example usage:


    require "migration_helpers"

    class ChangePriceColumnsToDecimal

    The helper method code should be placed in lib/migration_helpers.rb:


    module MigrationHelpers
    def decimalize_column(model_name, old_column, new_column)
    rename_column model_name.table_name, old_column, new_column
    change_column model_name.table_name, new_column, :decimal, :precision => 8, :scale => 2
    model_name.reset_column_information

    model_name.find(:all).each do |row|
    unless row[new_column] == nil
    row.update_attribute new_column, row[new_column] / 100
    end
    end
    end

    def undecimalize_column(model_name, old_column, new_column)
    model_name.find(:all).each do |row|
    unless row[new_column] == nil
    row.update_attribute new_column, row[new_column] * 100
    end
    end

    change_column model_name.table_name, new_column, :integer
    rename_column model_name.table_name, new_column, old_column
    end
    end

    It works for me, but I haven’t tested in real extensively. Hopefully this can save someone some time.

  45. Jesse Scott Says:

    Ack, well that got pretty badly eaten.

    I’ll post it up on my blog here in a minute. :)

    (Feel free to delete that abomination of a comment Coda…)

  46. Jesse Scott Says:

    Here’s the permalink to my decimal column migration helper methods.

  47. Pivotal Blabs : Standup 3/1/07 Says:

    [...] This is a bad idea in the long run since floats might store $2.50 as 2.50000001. See Coda Hale’s dollars_and_cents plugin. Ian adds: “Database tables for currency should be of type decimal. (e.g. amount DECIMAL(10,2)) [...]

  48. Good job, Housing Works! - Non-Profit Tech Blog Says:

    [...] this meant rounding errors. And no one wants to see rounding errors with their money. There were workarounds but it’s all been [...]

  49. labria Says:

    I found quite a bad bug.
    amount = “100″ #=> amount_in_cents = 10000 as it should be, but:
    amount = “-100″ #=> amount_in_cents = -100, now this is wrong!

  50. dollars_and_cents with Ruby on Rails | buffer overflow Says:

    [...] dollars_and_cents: a Rails plugin | Archives | codablog | Coda Hale [...]