fearless_fool
fearless_fool

Reputation: 35239

How can I compare a BigDecimal with ActiveRecord decimal field?

Assume a schema like this:

create_table "bills", :force => true do |t|
  t.decimal  "cost", :precision => 10, :scale => 5
end

I want to write a function that writes a new bill to the DB iff it is unique. The following does not work:

def load_bill_unless_exists(candidate)
  incumbents = Bill.scoped.where(:cost => candidate.cost)
  candidate.save unless incumbents.exists?
end

because the incumbent bills and the candidate bills have different limits in their BigDecimal representation, so the :cost => candidate.cost test fails. That is, it's comparing:

candidate: #<Bill id: nil, cost: #<BigDecimal:105e39850,'0.1670576666 6666666E4',27(27)>>

with

incumbent: #<ServiceBill id: 198449, cost: #<BigDecimal:105e35840,'0.167057667E4',18(18)>>

Notice the candidate's BigDecimal represents the cost with more digits than the incumbent.

So the question is simple: What's the right way to perform this comparison? I contemplated :cost => BigDecimal.new(candidate.cost.to_s, 18), but that doesn't feel right -- for example, where does that number 18 come from?

Upvotes: 3

Views: 5205

Answers (2)

dnch
dnch

Reputation: 9605

Try using BigDecimal#round:

def load_bill_unless_exists(candidate)
  incumbents = Bill.scoped.where(:cost => candidate.cost.round(5))
  candidate.save unless incumbents.exists?
end

From the docs:

Round to the nearest 1 (by default), returning the result as a BigDecimal. If n is specified and positive, the fractional part of the result has no more than that many digits.

Given that you've specified a precision of 5 in your schema, that's what you should be rounding to when doing comparisons.

Upvotes: 1

jdc
jdc

Reputation: 743

If casting like you were contemplating works, you probably have to go with that. The query you're using is just building a "WHERE cost = number" and if the database isn't able to compare properly with the number as passed, you need to pass it differently. It looks like it's the database stopping you and not anything within Rails necessarily.

If you just don't like casting within your query, you could always do it in the model:

def cost_with_incumbent_precision
  BigDecimal.new(cost.to_s, 18)
end

Upvotes: 1

Related Questions