Rails: money gem converts all amounts to zero
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.timestampsend
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 endend
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 manuallygem '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)
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.