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.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)
How to get full price for money gem
Alright i was able to figure it out, thanks everyone!
Here is my new code:
Database now is :price instead of :cents -
create_table :prices do |t|
t.integer :user_id
t.string :name
t.date :date
t.integer :price, :default => 0
t.string :currency
form area:
<div class="field">
<%= f.label :price %><br />
<%= f.text_field :price %>
</div>
new composed_of:
composed_of :price,
:class_name => "Money",
:mapping => [%w(price 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 conver #{value.class} to Money") }
I just had to make it so the composed_of matches the mapping, database column and form and for some strange reason it starting working entirely. Thanks everyone for the help, i learned a great deal about ruby and the rails.
money-rails, Money#== supports only zero numerics
The error comes from a numericality validation on your model.
Money gem does not allow to compare a money object with a Number, unless thee number is zero. Otherwise it expects you to compare Money with Money.
In irb you can try:
```ruby
2.5.1 :006 > Money.new(1000, "USD") != Money.new(1000, "USD")
=> false
2.5.1 :007 > Money.new(1000, "USD") != 1000
Traceback (most recent call last):
4: from /Users/andi/.rvm/rubies/ruby-2.5.1/bin/irb:11:in `<main>'
3: from (irb):7
2: from (irb):7:in `!='
1: from /Users/andi/.rvm/gems/ruby-2.5.1/gems/money-6.12.0/lib/money/money/arithmetic.rb:70:in `=='
ArgumentError (Money#== supports only zero numerics)
2.5.1 :008 > Money.new(1000, "USD") != 0
=> true
```
I believe that this error makes sense, as you can't really compare an arbitrary number with an amount of money in a given currency.
The money-rails gem again comes with it's own validators:
https://github.com/RubyMoney/money-rails#numericality-validation-options
Your backtrace shows that you are using the rails numericality validator instead.
How to initialize Money column with zero amount in Active Model?
after_initialize does not do what you think it does
the way too hook into init is as follows:
class MyModel < ActiveRecord::Base
def initialize(*args, &block)
super # no () form is equivalent to super(*args, &block)
set_defaults
end
private
def set_defaults
# Do your defaults here
end
end
Rails money gem and form builder
Given a migration as follows:
class CreateItems < ActiveRecord::Migration
def self.up
create_table :items do |t|
t.integer :cents
t.string :currency
t.timestamps
end
end
def self.down
drop_table :items
end
end
And a model as follows:
class Item < ActiveRecord::Base
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 conver #{value.class} to Money") }
end
Then this form code should work perfectly (I just tested under Rails 3.0.3), properly displaying and saving the dollar amount every time you save/edit. (This is using the default scaffold update/create methods).
<%= form_for(@item) do |f| %>
<div class="field">
<%= f.label :amount %><br />
<%= f.text_field :amount %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Entering in large dollar values into rails money object
Here in the money-rails README it shows that you can configure it to use other data types:
# Default ActiveRecord migration configuration values for columns:
#
# config.amount_column = { prefix: '', # column name prefix
# postfix: '_cents', # column name postfix
# column_name: nil, # full column name (overrides prefix, postfix and accessor name)
# type: :integer, # column type
# present: true, # column will be created
# null: false, # other options will be treated as column options
# default: 0
# }
Notice there's a type: :integer
option. Try changing that to :bigint
.
Related Topics
Why 'Self' Method of Module Cannot Become a Singleton Method of Class
Rubocop: Line Is Too Long ← How to Ignore
"Uninitialized Constant" Error When Including a Module
Rails: How to Access Restful Helpers
How to Get Constants Defined by Ruby's Module Class via Reflection
Ruby Loading Config (Yaml) File in Same Dir as Source
Difference Between an It Block and a Specify Block in Rspec
Relative Path to Your Project Directory
Active Admin: Customize Only New Form
Ruby 1.9, Force_Encoding, But Check
Rails - Invalid Authenticity Token After Deploy
Engine's Assets with Rails 3.1
Using Htaccess Password Protection on Rails