Как настроить гем Money-Rails для работы с более высокой точностью, чем 4 цифры - PullRequest
2 голосов
/ 13 марта 2020

Мы используем money-rails драгоценный камень в нашем приложении. До сих пор мы были в порядке с точностью до 4 десятичных цифр, но нам нужно переключиться на 6. К сожалению, я не могу сохранить числа с более высокой точностью в Postgres - число округляется перед сохранением.

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

Деньги, кажется, прекрасно работают с более высокой точностью

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

Тестирование модели в консоли. Все отлично работает перед сохранением.

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

И вывод ActiveRecord говорит, что усеченное число фактически отправляется в базу данных. (обратите внимание на 181.12).

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

Есть ли способ, позволяющий money-rails gem работать с большей точностью? Похоже, у самоцвета Money нет проблем с более высокой точностью.

1 Ответ

1 голос
/ 13 марта 2020

Такое поведение происходит из-за округления денег, чтобы его отключить, используйте:

Money.infinite_precision = true

вот тест:

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
...