Rails 3 validates numericality throws argument error when blank/nil - ruby-on-rails

This seems like kind of a no-brainer but I want to require a number and make sure it is not greater or less than a predetermined amount:
validates :age_min, presence: true, numericality: {
greater_than: 0, less_than_or_equal_to: :age_max
}
This test works as expected
test 'user should not be valid with age min greater than age max' do
user = FactoryGirl.build(:user, age_min: 30, age_max: 20)
assert !user.valid?
end
However, when I try and test that age_min is required:
test 'user should not be valid without age_min' do
user = FactoryGirl.build(:user, age_min: nil, age_max: 20)
assert !user.valid?
end
I get ArgumentError: comparison of Float with nil failed
It seems strange that Rails doesn't take the nil value into account, or am I missing something? It seems you should be able to get this to work without writing a custom validator, but perhaps I am mistaken.

Since your numericality validation for age_min id dependent on the value for age_max, and not a fixed value, I think you want to split your validation out and guard against nil values with procs
validates :age_min, :age_max, :presence => true
validates :age_min, :numericality => {greater_than: 0, less_than_or_equal_to: :age_max}, :unless => Proc.new {|user| user.age_min.nil? || user.age_max.nil? }

Related

Switch to Ruby 2.3.0 (from 2.0.0) creates issue on Active Record validates

I recently switched my Rails 4.2 app from ruby 2.0.0 to 2.3.0 and when I start my rails server ($ rails s), a new notifcation just appeared
/home/app/models/user.rb:127: warning: key :numericality is duplicated and overwritten on line 128
/homeapp/models/user.rb:127: warning: key :on is duplicated and overwritten on line 128
/home/app/admin/user.rb:142: warning: key :collection is duplicated and overwritten on line 147
/home/app/models/deal.rb:223: warning: key :numericality is duplicated and overwritten on line 226
/home/app/models/deal.rb:234: warning: key :numericality is duplicated and overwritten on line 237
Here is an example of the line causing issues, they are the ones where on creation of the account I set the attribute nb equal to 3 (on: :create), and the user, in the life of his account, can grow his number of permissions but will never be able to have more than 7 (on: :update).
validates :nb_of_permissions,
presence:true,
numericality: { equal_to: 3 }, on: :create,
numericality: { less_than_or_equal_to: 7 }, on: :update
What should I change ?
Thanks
The main difference here is that the Ruby upgrade is telling you about a
bug in your app that was previously passing unnoticed. Only the second numericality: { less_than_or_equal_to: 7 }, on: :update was ever really used.
irb(main):001:0> { foo: 1, foo: 2 }
(irb):1: warning: key :foo is duplicated and overwritten on line 1
=> {:foo=>2}
Use validates :att, {} when you have relatively simple conditions.
Since your validations apply to different lifecycle events you should declare each as a separate validation.
validates :nb_of_permissions, { presence: true }
validates_numericality_of :nb_of_permissions equal_to: 3, on: :create
validates_numericality_of :nb_of_permissions less_than_or_equal_to: 7, on: :update
Testing model validations base bones is actually pretty easy (although not as slick as with Shoulda-Matchers):
RSpec.describe Thing do
describe "validations"
describe "#nb_of_permissions" do
context "when updating" do
let(:thing) { Thing.create(nb_of_permissions: 3) }
let(:errors) { thing.valid?.errors[:nb_of_permissions] }
it 'is must be present' do
thing.nb_of_permissions = nil
expect(errors).to include 'must be present.'
end
it 'is must be at least 7' do
thing.nb_of_permissions = 10000
expect(errors).to include "must be less than or equal to 7"
end
end
end
end
end
You should try this
validates :nb_of_permissions, presence:true, numericality: { equal_to: 3 }, on: :create
validates :nb_of_permissions, presence:true, numericality: { less_than_or_equal_to: 7 }, on: :update
Your old code was buggy, and not doing what you think it was. There has been no change in behaviour here; you are just being shown a helpful warning to highlight the probable mistake!
Consider the following simple example:
hash = {a: 1, a: 2}
In ruby versions 2.0 and 2.3, this is equivalent to just defining:
hash = {a: 2}
...Because the hash key gets overridden. Likewise, your code:
validates :nb_of_permissions,
presence:true,
numericality: { equal_to: 3 }, on: :create,
numericality: { less_than_or_equal_to: 7 }, on: :update
...Is, and always was, equivalent to just writing:
validates :nb_of_permissions,
presence:true,
numericality: { less_than_or_equal_to: 7 },
on: :update
There is more than one way to do it, but for example you could fix this bug by defining two validations, such as:
validates :nb_of_permissions,
presence:true,
numericality: { less_than_or_equal_to: 7 },
on: :update
validates :nb_of_permissions,
presence:true,
numericality: { equal_to: 3 },
on: :create

rails 4 validates presence and length on the same parameter

Is it a good idea to having validates like this:
validates :serialnumber, presence: true, length: {7..20}, format: {with: /\d{7,20/}
As you see it generates three errors if I don't type serialnumber.
I would like to see only one error.
If I type nothing, I would like to see 'serial number is required' only.
If I type 123ABC I would like to see 'wrong length' only
And if I type 123-ABC-123 I would like to see 'wrong format' only
How to do it?
Regards
You could split it into 2 validators, check if this would work
validates :serialnumber, presence: true
validates :serialnumber, length: {7..20}, format: { with: /\d{7,20}/ }, allow_blank: true
As I understand then you want to see only one error message at a time. If that's the case then custom validation method might help. For ex.
validate :serial_number_validation_one_by_one
private
def serial_number_validation_one_by_one
if !self.serial_number.present?
errors.add(:serial_number, "must be present!")
elsif self.serial_number.length < 7 || self.serial_number.length > 20
errors.add(:serial_number, "must have length between 7 and 20!")
elsif self.serial_number.match(<your regex pattern here>)
errors.add(:serial_number, "must be properly formatted!")
end
end
Keep in mind that custom validation methods are called by validate not by validates.

Rails 4 validates if num1 > num2 > num3

I'm currently facing 2 problems using custom validation on Rails 4. First problem, how can I make the following code more generic and efficient (if it's possible) ?
validates :p1, presence: true, numericality: { only_integer: false }
validate :p1_is_greater_than_p2_and_p3
validate :p2_between_p1_and_p3
validate :p3_is_less_than_p2_and_p1
def p1_is_greater_than_p2_and_p3
if self.p1.present?
errors.add(:p1, 'p1 must be greater than everything') unless
(self.p1 > self.p2) && (self.p1 > self.p3)
end
true
end
def p2_between_p1_and_p3
if self.p3.present?
errors.add(:p2, 'p2 bewteen p1 and p3') unless
self.p2.between?(self.p1, self.p3)
end
true
end
def p3_is_less_than_p2_and_p1
if self.p2.present? and self.p3.present?
errors.add(:p3, 'p3 must be inferior to eveything') unless
(self.p2 > self.p3) && (self.p1 > self.p3)
end
true
end
It's really bloated and dirty, isn't it?
Second issue, on errors.add, I can pass a symbol and an error message. However, if I don't pass any message, how can I define a custom yml key for my locales ? such as :
en:
activerecord:
errors:
models:
prices:
attributes:
custom_key_message_here: 'p1 must be greater than everything'
I want to keep this seperation of concern between locales and model. However, if I don't pass any message, it's show me is invalid. I would like something more explixit.
Thanks for your help.
From a quick look at the numericality validator, could you not just use:
validates :p1, presence: true, numericality: { greater_than: :p2 }
validates :p2, presence: true, numericality: { greater_than: :p3 }
validates :p3, presence: true
As long as p1 > p2 and p2 > p3, you shouldn't need to compare p1 and p3 directly. This is assuming all three values must be present, but you could probably adjust things to work if they're optional.

Rails 4 rspec test failing on numericality only_integer on boolean value?

I have in my model
validates :is_foo, presence: true, numericality: {only_integer:true, less_than_or_equal_to: 1, greater_than_or_equal_to: 0}
in my spec
describe "when is_foo is not an integer" do
before {#user.is_foo = true}
it {should_not be_valid}
end
The above test fails, but since I set is_foo to a boolean and not an integer, should the test pass?
Are booleans considered integers in ruby? Because when I did
true.to_i # error
true == 1 # false
false == 0 # false
The spec is correct. 1 is truthy (the only non-truthy values in Ruby are false and nil), but it is not a boolean. Booleans are not numeric; they are their own type. A numericality validation will validate that the type is actually numeric, and boolean is not numeric.
> true.class
=> TrueClass
> 1.class
=> Fixnum
What you probably want instead, though, is a validation that tests for a boolean value:
validates :is_foo, inclusion => {:in => [true, false]}
I guess ruby/rails substitutes 1 for true to a model class attribute when the attribute expects an integer. So the test failed because it was comparing 1 to an integer.

Duplicate flash error messages with multiple validations

I am currently validating my model using this code:
validates :price, :presence => true, :numericality => {:greater_than => 0}
This works fine, except that when I do not enter any value in this field, I get 2 errors - both "Price can't be blank" and "Price is not a number".
I can understand why this happens - clearly it is failing both tests. But i'm wondering if there is a way to ge the validation to stop after one test, since there is no point testing if the number is > 0 if there is no number at all?
Thanks!
Edit: For clarity, I don't want to allow the field to be blank, I just don't want the numericality test to run if it is blank, to avoid 2 error messages for what is really 1 error.
Not sure if it will work, but you can try:
validates :price, :presence => true, :numericality => {:greater_than => 0, :allow_blank => true }

Resources