Rails: Money Gem Converts All Amounts to Zero

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



Leave a reply



Submit