Search code examples
ruby-on-railsrubypostgresqlactiverecordbigdecimal

How to properly compare database-persisted BigDecimal to another value using ActiveRecord?


The objective is to use ActiveRecord methods like #where and #first_or_create to properly compare a database-persisted BigDecimal value against a Float or String value.

create_things migration:

t.decimal :position, :precision => 10, :scale => 4

relevant code:

position = "0.309624"
thing = Thing.where(:position => position).first_or_create
thing2 = Thing.where(:position => position).first_or_initialize

the issue:

I'm expecting Thing.count to equal 1 and thing2.persisted? to be true, meaning Active Record was able to properly compare position with thing.position to find the first record and assign it to thing2.

debugging:

thing.position #> #<BigDecimal:7ffbdabe42a8,'0.309624E0',9(18)>
thing.position == position #> false

it's possible to do a valid comparison by converting both variables...

thing.position.to_s == position#> true
thing.position.to_f == position.to_f #> true

... but this requires a conversion of the database-persisted value. Is there a way to make this happen using #where and/or #first_or_create?

see also How can I compare a BigDecimal with ActiveRecord decimal field?

edit: this question is different from the related question in that the accepted solution to the related question advocates rounding a float, which applied to this example would call for an assignment of position = "0.309624".to_f.round(4), which still produces an invalid comparison.


Solution

  • I think I found the answer, which is to convert the position to BigDecimal before comparison:

    position = BigDecimal.new("0.309624", 4) # where 4 is the decimal scale.
    

    It seems so simple now...

    edit: the BigDecimal also needs to be rounded before storing in the database and when comparing against database value:

    position = BigDecimal.new("0.309624", 4).round(4)