6
votes

I'm using the money-rails gem in my Rails application. It have worked perfectly until now, but I've recently started getting the following error:

Money#== supports only zero numerics

I'm not really sure what caused this or how to solve it. I have recently run bundle update so I guess that something have been updated. My gemfile looks like this:

gem 'money-rails', '~>1'
gem 'eu_central_bank', "~>1.3.0"

My implementation looks like this:

# model
monetize :price_in_cents

# fetch / converting currencies
eu_bank = EuCentralBank.new
Money.default_bank = eu_bank
eu_bank.update_rates
converted_price = eu_bank.exchange_with(Money.new(price_to_convert * 100, from_currency), to_currency)

This have as I mentioned worked before, so I'm not sure what is breaking it.

Any ideas?

Update

To test I tried with the following.

money = Money.new(100, from_currency)

Then I got the same error as before. But if I tried:

money = Money.new(0, from_currency)

It seems to work. I find that a bit strange.

Update

Here is the backtrace from when I try to save the record:

["/Users/[user]/.rvm/gems/ruby-2.5.1/gems/money-6.11.3/lib/money/money/arithmetic.rb:70:in =='", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activemodel-5.1.6/lib/active_model/validations/numericality.rb:22:in!='", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activemodel-5.1.6/lib/active_model/validations/numericality.rb:22:in validate_each'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activemodel-5.1.6/lib/active_model/validator.rb:150:inblock in validate'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activemodel-5.1.6/lib/active_model/validator.rb:147:in each'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activemodel-5.1.6/lib/active_model/validator.rb:147:invalidate'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activesupport-5.1.6/lib/active_support/callbacks.rb:413:in block in make_lambda'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activesupport-5.1.6/lib/active_support/callbacks.rb:197:inblock (2 levels) in halting'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activesupport-5.1.6/lib/active_support/callbacks.rb:601:in block (2 levels) in default_terminator'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activesupport-5.1.6/lib/active_support/callbacks.rb:600:incatch'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activesupport-5.1.6/lib/active_support/callbacks.rb:600:in block in default_terminator'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activesupport-5.1.6/lib/active_support/callbacks.rb:198:inblock in halting'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activesupport-5.1.6/lib/active_support/callbacks.rb:507:in block in invoke_before'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activesupport-5.1.6/lib/active_support/callbacks.rb:507:ineach'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activesupport-5.1.6/lib/active_support/callbacks.rb:507:in invoke_before'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activesupport-5.1.6/lib/active_support/callbacks.rb:130:inrun_callbacks'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activesupport-5.1.6/lib/active_support/callbacks.rb:827:in _run_validate_callbacks'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activemodel-5.1.6/lib/active_model/validations.rb:405:inrun_validations!'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activemodel-5.1.6/lib/active_model/validations/callbacks.rb:114:in block in run_validations!'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activesupport-5.1.6/lib/active_support/callbacks.rb:97:inrun_callbacks'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activesupport-5.1.6/lib/active_support/callbacks.rb:827:in _run_validation_callbacks'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activemodel-5.1.6/lib/active_model/validations/callbacks.rb:114:inrun_validations!'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activemodel-5.1.6/lib/active_model/validations.rb:335:in valid?'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activerecord-5.1.6/lib/active_record/validations.rb:65:invalid?'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activerecord-5.1.6/lib/active_record/validations.rb:82:in perform_validations'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activerecord-5.1.6/lib/active_record/validations.rb:50:insave!'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activerecord-5.1.6/lib/active_record/attribute_methods/dirty.rb:43:in save!'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activerecord-5.1.6/lib/active_record/transactions.rb:313:inblock in save!'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activerecord-5.1.6/lib/active_record/transactions.rb:384:in block in with_transaction_returning_status'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activerecord-5.1.6/lib/active_record/connection_adapters/abstract/database_statements.rb:235:inblock in transaction'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activerecord-5.1.6/lib/active_record/connection_adapters/abstract/transaction.rb:194:in block in within_new_transaction'", "/Users/[user]/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/monitor.rb:226:inmon_synchronize'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activerecord-5.1.6/lib/active_record/connection_adapters/abstract/transaction.rb:191:in within_new_transaction'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activerecord-5.1.6/lib/active_record/connection_adapters/abstract/database_statements.rb:235:intransaction'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activerecord-5.1.6/lib/active_record/transactions.rb:210:in transaction'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activerecord-5.1.6/lib/active_record/transactions.rb:381:inwith_transaction_returning_status'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activerecord-5.1.6/lib/active_record/transactions.rb:313:in save!'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activerecord-5.1.6/lib/active_record/suppressor.rb:46:insave!'", "/Users/[user]/Documents/Development/wondery/[appname]/lib/importers/bts_wholesaler_importer.rb:245:in block in set_supported_currencys_for_product_variant'", "/Users/[user]/Documents/Development/wondery/[appname]/lib/importers/bts_wholesaler_importer.rb:240:ineach'", "/Users/[user]/Documents/Development/wondery/[appname]/lib/importers/bts_wholesaler_importer.rb:240:in set_supported_currencys_for_product_variant'", "/Users/[user]/Documents/Development/wondery/[appname]/lib/importers/bts_wholesaler_importer.rb:224:inset_up_product_variant'", "/Users/[user]/Documents/Development/wondery/[appname]/lib/importers/bts_wholesaler_importer.rb:208:in match_product_variant_to_product'", "/Users/[user]/Documents/Development/wondery/[appname]/lib/importers/bts_wholesaler_importer.rb:84:inblock in get_products_from_api_data'", "/Users/[user]/Documents/Development/wondery/[appname]/lib/importers/bts_wholesaler_importer.rb:75:in map'", "/Users/[user]/Documents/Development/wondery/[appname]/lib/importers/bts_wholesaler_importer.rb:75:inget_products_from_api_data'", "/Users/[user]/Documents/Development/wondery/[appname]/lib/importers/bts_wholesaler_importer.rb:25:in import'", "(irb):2:inirb_binding'", "/Users/[user]/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/irb/workspace.rb:85:in eval'", "/Users/[user]/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/irb/workspace.rb:85:inevaluate'", "/Users/[user]/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/irb/context.rb:380:in evaluate'", "/Users/[user]/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/irb.rb:491:inblock (2 levels) in eval_input'", "/Users/[user]/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/irb.rb:623:in signal_status'", "/Users/[user]/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/irb.rb:488:inblock in eval_input'", "/Users/[user]/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:246:in block (2 levels) in each_top_level_statement'", "/Users/[user]/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:232:inloop'", "/Users/[user]/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:232:in block in each_top_level_statement'", "/Users/[user]/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:231:incatch'", "/Users/[user]/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:231:in each_top_level_statement'", "/Users/[user]/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/irb.rb:487:ineval_input'", "/Users/[user]/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/irb.rb:428:in block in run'", "/Users/[user]/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/irb.rb:427:incatch'", "/Users/[user]/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/irb.rb:427:in run'", "/Users/[user]/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/irb.rb:383:instart'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/railties-5.1.6/lib/rails/commands/console/console_command.rb:62:in start'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/railties-5.1.6/lib/rails/commands/console/console_command.rb:17:instart'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/railties-5.1.6/lib/rails/commands/console/console_command.rb:97:in perform'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/thor-0.20.0/lib/thor/command.rb:27:inrun'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/thor-0.20.0/lib/thor/invocation.rb:126:in invoke_command'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/thor-0.20.0/lib/thor.rb:387:indispatch'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/railties-5.1.6/lib/rails/command/base.rb:63:in perform'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/railties-5.1.6/lib/rails/command.rb:44:ininvoke'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/railties-5.1.6/lib/rails/commands.rb:16:in <top (required)>'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:292:inrequire'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:292:in block in require'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:258:inload_dependency'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:292:in require'", "/Users/[user]/Documents/Development/wondery/[appname]/bin/rails:9:in'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:286:in load'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:286:inblock in load'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:258:in load_dependency'", "/Users/[user]/.rvm/gems/ruby-2.5.1/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:286:inload'", "/Users/[user]/.rvm/rubies/ruby-2.5.1/lib/ruby/site_ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in require'", "/Users/[user]/.rvm/rubies/ruby-2.5.1/lib/ruby/site_ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:inrequire'", "-e:1:in `'"]

3
Do you use code versioning, like git? If yes you should be able to diff your Gemfile.lock to see if and how the version(s) updated. With that information you should be able to find out what changed in either of the two involved gems, and maybe find a hint what went wrong... Otherwise, if you dont feel like you wanted or needed to update, you can revert to the previous version, by specifying the exact version in your Gemfile, and you can work your way forward from a working version.trueunlessfalse
Generally I would not recommend you to ever run "bundle update", especially not if you have gems locked to mayor versions, like its the case with '~>1'trueunlessfalse
Oh, and you could consider asking for help on the gems github page: github.com/RubyMoney/money-railstrueunlessfalse
Can you include a backtrace in your question? That would help narrow down the problem. While I don't know what your problem is at all, I imagine somehow you're trying to give some part of Money a float (like $100.05) as opposed to whole numbers of cents (10005c). Did you declare your database column as an integer, or a float/double, because the problem may have to do with how Rails is initializing stuff if the database column data type isn't what Money expects.Nate
@Nate I've added the full stack backtrace to my question now.Anders

3 Answers

8
votes

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.

4
votes

You might also come across this error when you are writing specs:

expect(order.subtotal).to eq 123.45
# ArgumentError:
#   Money#== supports only zero numerics

expect(order.subtotal).to eq Money.new(12345)
# works!
0
votes

Money Rails automatically adds "_cents" to the attribute you are monetizing. Then it creates an instance of that model.price which includes

<Money fractional:1500 currency:DKK>

Hence, you cannot do model.price + 10 as it would cause the error you are getting. You may therefore want to monetize price_to_convert, or any other value you are working with so that you can sum them up, etc.

Create a new migration file e.g. rails g migration AddMoneytizeToOrders

change migration file so that it reads as follow (change column/attribute to your values)

class AddMonetizeToOrder < ActiveRecord::Migration[6.0]
  def change
    add_monetize :orders, :price_to_convert, currency: { present: false }
  end
end

In your model you must include

monetize :price_to_convert_cents

then you can work with e.g.(rails c) Order.last.price_to_convert + Model.last.price, etc.

Please note that you cannot use self.[:price_to_convert] as money rails wouldn't understand it and you would get the error

money rails ActiveModel::MissingAttributeError (can't write unknown attribute...

Therefore use self.price_to_convert instead