Search code examples
ruby-on-railsrubycurrencymoney-rails

How to configure money-rails gem to work with higher precision than 4 digits


We are using money-rails gem in our application. Until now, we were OK with 4 decimal digits precision, but we need to switch to 6. Unfortunately I'm unable to store higher precision numbers into Postgres - number is rounded before saving.

class MyModel < ApplicationRecord
  monetize :price_cents # :price_cents, :decimal, default: 0, precision: 30, scale: 10
end

Money itself seems to work fine with higher precision

pry(main)> Money.new(181.123456789).to_f
=> 1.81123456789

Testing model in console. All works fine before save.

my_model = MyModel.find(1)
my_model.price = Money.new(181.123456789)
my_model.price.to_f # => 1.81123456789
my_model.save
my_model.price.to_f # => 1.8112

And the ActiveRecord output says the trimmed number is actually being send to database. (notice the 181.12).

UPDATE "my_models" SET "price_cents" = $1 ... [["price_cents", "181.12"] ...]

Is there any way to allow money-rails gem to work with more precision? It seems like Money gem has no problem with higher precision on its own.


Solution

  • That behavior comes from money rounding, to turn it off, use:

    Money.infinite_precision = true
    

    here's test:

    require "bundler/inline"
    
    gemfile(ENV['INSTALL']=='1') do
      source "https://rubygems.org"
      gem "rails", '~>6.0'
      gem "sqlite3"
      gem 'money-rails'
    end
    
    require "minitest/autorun"
    require "logger"
    require "active_record"
    require 'money-rails'
    
    ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
    ActiveRecord::Base.logger = Logger.new(STDOUT)
    ActiveRecord::Schema.define do
      create_table(:models, force: true){|t| t.decimal :price_cents, default: 0, precision: 30, scale: 10 }
    end
    
    # to remove config warnings:
    Money.locale_backend = :currency
    Money.default_currency= :usd
    
    # actual fix:
    Money.infinite_precision = true
    
    MoneyRails::Hooks.init # in a normal app is called automatically
    
    
    class Model < ActiveRecord::Base
      monetize :price_cents 
    end
    
    class BugTest < Minitest::Test
      def test_association_stuff
        m = Model.create!(price_cents: 181.123456789).reload
        assert_equal 181.123456789, m.price_cents
        assert_equal 1.81123456789, m.price.to_f
    
        m2 = Model.create(price: Money.new(182.123456789)).reload
        assert_equal 1.82123456789, m2.price.to_f
      end
    end