Reputation: 35239
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
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
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