Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails: money gem converts all amounts to zero

I'm trying to use the money gem to handle currency in my app but I'm running into a strange error. This is what I have in my "record" model:

composed_of :amount,
            :class_name => "Money",
            :mapping => [%w(cents cents), %w(currency currency_as_string)],
            :constructor => Proc.new { |cents, currency| Money.new(cents || 0, currency || Money.default_currency) },
            :converter => Proc.new { |value| value.respond_to?(:to_money) ? value.to_money : raise(ArgumentError, "Can't convert #{value.class} to Money") }

amount is an integer.

When I create a new record it ignores whatever value I put in the amount field and defaults it to 0. Is there something I need to add to the forms?

I'm using rails 3.0.3 and the money gem version is 3.5.5

like image 789
David Avatar asked Jan 25 '11 20:01

David


2 Answers

EDIT: Added Bonus at the end of the answer

Well, your question was interesting to me so I decided to try myself.

This works properly:

1) Product migration:

create_table :products do |t|
  t.string :name
  t.integer :cents, :default => 0
  t.string :currency
  t.timestamps
end

2) Product model

class Product < ActiveRecord::Base

   attr_accessible :name, :cents, :currency

  composed_of :price,
    :class_name => "Money",
    :mapping => [%w(cents cents), %w(currency currency_as_string)],
    :constructor => Proc.new { |cents, currency| Money.new(cents || 0, currency || Money.default_currency) },
    :converter => Proc.new { |value| value.respond_to?(:to_money) ? value.to_money : raise(ArgumentError, "Can't convert #{value.class} to Money") }
end

3) Form:

<%= form_for(@product) do |f| %>
  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %> 
  </div>
  <div class="field">
    <%= f.label :cents %><br />
    <%= f.text_field :cents %>
  </div>
  <div class="field">
    <%= f.label :currency %><br />      
   <%= f.select(:currency,all_currencies(Money::Currency::TABLE), {:include_blank => 'Select a Currency'}) %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

4) Products Helper (handmade):

module ProductsHelper
  def major_currencies(hash)
    hash.inject([]) do |array, (id, attributes)|
      priority = attributes[:priority]
      if priority && priority < 10
        array ||= []
        array << [attributes[:name], attributes[:iso_code]]
      end
      array
    end
  end

  def all_currencies(hash)
    hash.inject([]) do |array, (id, attributes)|
      array ||= []
      array << [attributes[:name], attributes[:iso_code]]
      array
    end
  end
end

BONUS:

If you want to add currency exchange rates:

1) Your gemfile

gem 'json' #important, was not set as a dependency, so I add it manually
gem 'google_currency'

2) Initializer

create money.rb in you initializers folder and put this inside:

require 'money'
require 'money/bank/google_currency'
Money.default_bank = Money::Bank::GoogleCurrency.new

reboot your server

3) Play!

Wherever you are, you can exchange the money.

Product.first.price.exchange_to('USD')

Display with nice rendering:

Product.first.price.format(:symbol => true)
like image 62
apneadiving Avatar answered Nov 09 '22 21:11

apneadiving


tl;dr: change :amount to :price or :anything_else.

I've concluded that :amount is a keyword used somewhere in the money gem, so using it in your application causes problems.

It's a stretch, but the author uses the word amount in the first line of the documentation to describe what it does.

"Provides a Money class which encapsulates all information about an certain amount of money, such as its value and its currency." http://money.rubyforge.org/

In my Rails 3.0 project I've got 3 very similar models that extend the money class: Labor, Part, and Payment.

Labor and Part work fine using the attribute :price, but I wanted to use :amount in my Payment model, because it sounded better when reading aloud or in my head.

The problem I experienced is that Payment would take valid form input, toss out the :amount, save 0 in the database, and throw an undefined method `round' for nil:NilClass error, upon viewing the record:

I'm pretty sure that 0 is a symptom of nil getting converted by my migration options (:null => false, default => 0). I ruled out the View by debugging with safari web inspector, and then the Controller by raising and inspecting each variable. This sort of problem in the Model doesn't make a lot of sense, so I figured it had to be money. Then, I found this thread, and put it all together.

After rolling back the migration, and changing all :amount references to :price, it works perfectly.

I know this thread is a few months old, but hopefully this will help someone else avoid this pitfall in the future.

In the meantime, I'll be sticking to :price.

like image 32
James Dunn Avatar answered Nov 09 '22 21:11

James Dunn