Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is Rails numericality validator not using normalized value?

My model has a decimal amount attribute.

create_table :foos do |t|
  t.decimal :amount
end

class Foo < ApplicationRecord
end

I always want the amount to be negative, so I add a normalisation:

class Foo < ApplicationRecord
  normalizes :amount, with: -> amount { - amount.abs }
end

This seems to work perfectly.

Now, to be safe, I add a validation:

class Foo < ApplicationRecord
  normalizes :amount, with: -> amount { - amount.abs }
  validates :amount, numericality: {less_than: 0}
end

Now when I set the amount to a positive value, although the normalisation converts it to a negative value, the validator seems to think the value is still positive and adds a validation error.

foo = Foo.new amount: 4
foo.amount  # => -4
foo.valid?  # => false
foo.errors  # => #<ActiveModel::Error attribute=amount, type=less_than, options={:value=>4, :count=>0}>

According to the tests for normalizes, normalisation happens before validation.

How can I get this to work?

like image 928
Andy Stewart Avatar asked Nov 01 '25 19:11

Andy Stewart


1 Answers

Numericality validator seems to be specifically using raw value for validation without taking normalization into account:
https://github.com/rails/rails/blob/v7.1.3/activemodel/lib/active_model/validations/numericality.rb#L129

if record.respond_to?(came_from_user)
  if record.public_send(came_from_user)
    raw_value = record.public_send(:"#{attr_name}_before_type_cast")

It needs to be this way because strings normalize to numbers ("foo".to_d # => 0.0) so validation wouldn't work if it happened after normalization.

You could write your own validation to bypass this problem:

validate do
  errors.add(:amount, :less_than, value: amount, count: 0) unless amount.negative?
end
like image 164
Alex Avatar answered Nov 04 '25 14:11

Alex



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!