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.
Related
Model Plan has a jsonb column :per_unit_quantities_configuration . It is a hash with 3 keys/values pairs, min, max and step.
class Plan < ApplicationRecord
store_accessor :per_unit_quantities_configuration, :min, :max, :step, prefix: true
end
I have validations in place to prevent awkward configurations and/or infinite loops building an array of options based on those configuration settings.
First, I cast the values to from string to float before_validation
before_validation :cast_per_unit_quantities_config_values
def cast_per_unit_quantities_config_values
return unless per_unit_quantities_configuration_changed?
self.per_unit_quantities_configuration_min = per_unit_quantities_configuration_min&.to_f
self.per_unit_quantities_configuration_max = per_unit_quantities_configuration_max&.to_f
self.per_unit_quantities_configuration_step = per_unit_quantities_configuration_step&.to_f
end
And then I have each individual fields values' validations:
validates :per_unit_quantities_configuration_min,
numericality: { greater_than_or_equal_to: 0 }, allow_nil: false
validates :per_unit_quantities_configuration_max,
numericality: { greater_than: lambda { |p| p.per_unit_quantities_configuration_min } }
validates :per_unit_quantities_configuration_max
numericality: { greater_than_or_equal_to: 0 }, allow_nil: false
validates :per_unit_quantities_configuration_step,
numericality: { greater_than: 0 }, allow_nil: false
The problem I'm having is that when the user tries to send the form with the min field empty (nil), it is transformed to 0 which is a valid value for the field but is not appopiate since API users would receive no feedback that the change is being made.
What is converting the nil value to 0 ? And why is the allow_nil: false validation not triggered instead?
What is converting the nil value to 0?
The call to .to_f:
nil.to_f
=> 0.0
If you're starting with an empty string instead of a nil then the safe navigation operator won't save you:
nil&.to_f
=> nil
""&.to_f
=> 0.0
And why is the allow_nil: false validation not triggered instead?
You changed the value in a before_validation callback. The validations will run against the new value.
You may want to remove the before_validation. This way, you can use the Rails numericality validations to filter out any invalid values (nil, "", "x", etc.). If you still need to do something to the value before it gets stored in the database you may want to use an after_validation callback instead.
Currently, my model and validation is this:
class Suya < ActiveRecord::Base
belongs_to :vendor
validates :meat, presence: true
validates_inclusion_of :spicy, :in => [true, false]
end
The problem is that when I run this test:
test "suya is invalid if spiciness is not a boolean" do
suya = Suya.new(meat: "beef", spicy: 1)
suya1 = Suya.new(meat: "beef", spicy: "some string")
assert suya.invalid?
refute suya1.valid?
end
I get a deprecation warning that says:
DEPRECATION WARNING: You attempted to assign a value which is not
explicitly true or false to a boolean column. Currently this value
casts to false. This will change to match Ruby's semantics, and will
cast to true in Rails 5.
So I think my validation is not doing what I think it should be doing. I think my validation checks the presence of the column value and if it IS or is converted to true or false. So I think my test fixtures both convert to false and therefore pass the test which I don't want. What can I do?
You can use custom validation like:
validate :check_boolean_field
def check_boolean_field
false unless self.spicy.is_a?(Boolean)
end
Rails performs type casting any time you assign a value to an attribute. This is a convenience thing. It's not really your text case's fault, it's just how Rails works. If the attribute is a Boolean it'll convert truthy-looking values (true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON') to true and anything else to false. For example:
suya.spicy = "asdf"
suya.spicy # => false
# Likewise for other attribute types:
# Assuming Suya has an `id` attribute that is an Integer
suya.id = "asdf"
suya.id # => 0 # Because "asdf".to_i # => 0
# Assuming Suya has a `name` attribute that is a String
suya.name = 1
suya.name # => "1" # Because 1.to_s # => "1"
So this is just how rails works. In your test case your values are being typecast into their respective attributes' types via mass-assignment.
You can either test out Rails's typecasting by assigning "some value" to your booleans or you can just use more obvious boolean values like true and false in your test cases.
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? }
I'm using Shoulda and Rspec for testing.
When I try this in my test spec it keeps passing when I haven't done the validations in the model:
it { should ensure_inclusion_of(:private).in_array(%w[true false]) }
The attribute is a boolean that is either true or false:
validates_inclusion_of :private, :in => [true, false]
How would I write this correctly?
True and False are not strings so don't use %w.
it { should ensure_inclusion_of(:private).in_array([true, false]) }
Update - 10th Apr 2014
This validation will not work in current versions of Shoulda and, as per this recent commit, it will not be fixed but will instead raise an exception.
As any value assigned to a boolean field will be cast to either true (set by true, 1, '1', 't', 'T', 'true', 'TRUE') or false (set by anything else) my preferred approach for testing a boolean field is as follows:
For a boolean that allows nulls in the database - no test is required, any possible value will be valid
For a boolean that does not allow nulls in the database - use it { should_not allow_value(nil).for(:field) which will pass when the validates :field, inclusion: { in: [true,false] } is set on the model
Having a rfq model in the app. There are two fields. One is need_report which is a boolean. Another one is report_language which is a string. The logic is if need_report is true, then there should be an entry in report_language. Otherwise, if need_report if false, report_language could be empty. Here is the code in rfq.rb:
validates :need_report, :presence => true
validates_inclusion_of :need_report, :in => [true, false]
validates :report_language, :presence => {:if => :need_report?}
def need_report?
need_report
end
However the following rspec case failed:
it "should be OK for nil report_language if need_report is false" do
rfq = Factory.build(:rfq, :need_report => false, :report_language => nil)
rfq.should be_valid
end
The error is that the rfq is not valid:
1) Rfq should be OK for nil report_language if need_report is false
Failure/Error: rfq.should be_valid
expected valid? to return true, got false
# ./spec/models/rfq_spec.rb:57:in `block (2 levels) in <top (required)>'
This case could pass if "validates :need_report, :presence => true" is removed from the model. It seems that if need_report is true, then report_language can not be empty.
Any thoughts about the problem? Thanks.
You can't use validates_presence_of or validates :column, :presence => true to check if boolean columns are empty.
http://api.rubyonrails.org/classes/ActiveModel/Validations/HelperMethods.html#method-i-validates_presence_of
Answer: Instead you need to use validates_inclusion_of and specify an array of accepted inputs, which you already have. This should be sufficient validation for what you want to do.
Explanation: Your first validation is seeing false in the column (which, in Ruby is equivalent to nil). It then runs .blank? on nil and gets back true (false == nil & nil.blank? == true), meaning it thinks the column is blank and it throws an error.