Search code examples
ruby-on-railsincrementloopsbigdecimal

Stepping or Iterating through BigDecimal


My starting point is basically Ryan Bate's RailsCast. I have modified his code to accomodate OR clauses as well as AND.

In the User model there are a couple of attributes named hour_price_high and hour_price_low. Users will set their hourly rate range such as $45.25/h to $55.50/h. Site visitors will search for registered users in a price range such as $50.00/h to $60.00/h. This search should return all overlapping users. The respective migration entries for these attributes are:

# CreateUser migration
...
t.decimal "hour_price_high", :precision => 6, :scale => 2
t.decimal "hour_price_low",  :precision => 6, :scale => 2
...

They are of type BigDecimal and I need to increment through these to search for users:

# Search.rb
def low_price_or_conditions
    ["users.hour_price_low IN (?)", price_low..price_high] unless price.blank?
end
def high_price_or_conditions
    ["users.hour_price_high IN (?)", price_low..price_high] unless price.blank?
end

When I run this code, I get this error:

TypeError:
can't iterate from BigDecimal

Any ideas of how to increment through these? I only need to increment back two decimal places. Thanks for looking!


Solution

  • You can't convert a Range of BigDecimal to an array:

    > a = BigDecimal.new('1')
     => #<BigDecimal:12a082bb0,'0.1E1',9(18)> 
    > b = BigDecimal.new('5')
     => #<BigDecimal:12a076ec8,'0.5E1',9(18)> 
    > (a..b).to_a
    TypeError: can't iterate from BigDecimal
    

    From the fine manual:

    You can only iterate if the start object of the range supports the succ method

    And BigDecimal doesn't have a succ method. When you feed this:

    ["users.hour_price_low IN (?)", price_low..price_high]
    

    to ActiveRecord as a condition, it probably sees that you've given it a Range and calls to_a on it to get something that can be SQL-ified and that's where your error comes from.

    I think you want to say this:

    [ "users.hour_price_low >= :lo AND users.hour_price_low <= :hi", { :lo => price_low, :hi => price_hi } ]
    

    Similar issues apply to your hour_price_high.

    Even if price_low and price_high were integers you wouldn't want to use IN, the range could expand to a rather large list and the database would have to compare each one individually.