Search code examples
ruby-on-railsrubybigdecimal

Why does a BigDecimal scale changes if accessed through an association?


I have two Ruby on Rails models Farm and Harvest. A farm belongs to a harvest. Here are the models:

class Farm < ActiveRecord::Base
  acts_as_singleton
  belongs_to :harvest
  validates :harvest, presence: true, allow_blank: true
  serialize :harvest_time, Tod::TimeOfDay
  validates :harvest_time, presence: true, allow_blank: true
  validates :hash_rate, presence: true
  validates_with HashRateValidator
end

class Harvest < ActiveRecord::Base
  belongs_to :user
  validates :user, presence: true
  validates :date, presence: true
  validates :amount, presence: true
  validates :identifier, presence: true
  validates :amount, numericality: { :greater_than => 0 }
end

There is only one Farm (accomplished thanks to the acts as singleton gem). Every time a harvest is done the harvest association from the farm changes, since it always have to point to the latest harvest. Since I am using a Farm as a singleton model I update the Farm using the following code:

@harvest = Harvest.new(
      :date => DateTime.now,
      :amount => amount,
      :identifier => new_identifier,
      :user => current_user,
      :assigned => false
)

if @harvest.save
   Farm.instance.update_attributes(:harvest => @harvest)
   byebug

The weird thins is that the values of the harvest amount ans the amount from the harvest assigned to the farm do not match after this:

(byebug) Farm.instance.harvest.amount
435.435

(byebug) @harvest.amount
435.435345343

(byebug) Farm.instance.harvest.id
12

(byebug) @harvest.id
12

The amount decimal is suposed to have scale to 8 and precision to 6 (from the migration), here is the relevant part of the schema.rb file:

create_table "harvests", force: :cascade do |t|
    t.datetime "date"
    t.decimal  "amount",                          precision: 6, scale: 8
    t.integer  "identifier"
    t.datetime "created_at",                                                              null: false
    t.datetime "updated_at",                                                              null: false
    ...
  end

So, what's going on here? The amount should be the exact same value!


Solution

  • I figured it out. Scale and precision did not make sense. Precision is the amount of digits on the BigDecimal amount, scale is the amount of those digits that appear to the right the decimal point. Since precision was set to 6 scale could not accommodate 8 digits after the decimal point. So when the number came from the database it was truncated, when it came from memory it had all its digits after the decimal point. I fixed it by setting precision to 18 and scale to 8, which means 18 digits in total and 8 of those appearing to the right of the decimal points.

    Sqlite allowed the incoherent precision => 6 and scale => 8. Postgres did not.