0

I'm new to Ruby on Rails. I have the following model:

class Logo < ApplicationRecord
  validates :name, presence: true,  length: { maximum: 255 }
  validates :price, presence: true, 
    numericality: { greater_than_or_equal_to: 0 }

  validates :stitch_count, presence: true,
    numericality: { greater_than: 0, only_integer: true }
end

Every logo has a price. I want to validate the scale of the price. It must accept the following: [10.44, 12.1, 16] and reject the following: [10.123, 13.11111, 16.123], as these are obviously not valid prices. I've tried validating by format using the following regex: /\A\d+(?:\.\d{0,2})?\z/, which, according to another poster on Stack Overflow, validates for at most 2 decimal places. It doesn't work. Another poster on Stack Overflow pointed out that the number of decimal places only matters when the value is a string, and that by the time the validator runs, it's no longer a string. His suggestions, as well as a few more, don't work. So how would I run a regex on a decimal? You would think Rails would have something to validate if a number is a valid price or not.

I run the following test:

  def setup
    @logo = Logo.new(name: "ASG Security", price: 12, stitch_count: 15000, note: "Ali")
  end

  test "price scale must be less than 2" do
    @logo.price = 14.995
    assert_not @logo.valid?
  end
izaguirrejoe
  • 117
  • 8

3 Answers3

1

You could use the money gem, which I've used, or maybe try this rails wrapper for it which provides premade money validations.

chad_
  • 3,749
  • 2
  • 22
  • 22
1

Generally it's not a good idea to use decimals for currencies.

If you're using USD, you can make the attribute price_cents instead of price.

For example:

Rather than passing 1.23 as price, you would pass 123 as price_cents.

And echoing what @chad_ mentioned, it's a good idea to use the Money gem. (Additionally the Money gem will expect currencies as "cents")

tywhang
  • 299
  • 4
  • 5
0

By the time the validation runs, the price is no longer a String. You can verify that a number has at most two decimals by comparing price with price.round(2):

10.44  == 10.44.round(2)   # true
10.123 == 10.123.round(2)  # false

You can write a custom validator that makes this comparison and adds an error to the attribute in case it is not equal.

Bustikiller
  • 2,423
  • 2
  • 16
  • 34
  • 1
    It's usually risky to compare floats with `==`, but this seems to work fine for reasonably large numbers. `123456789012345678.12` fails for example, but I'm not sure if it's a problem. – Eric Duminil Feb 05 '21 at 22:42