Reputation: 7210
I have the following validation on an attribute of a model:
validates :on_ride_photo,
presence: true,
inclusion: { in: [true, false] }
I then have the following tests:
context 'on_ride_photo' do
it { should validate_presence_of(:on_ride_photo) }
it { should allow_value(true).for(:on_ride_photo) }
it { should allow_value(false).for(:on_ride_photo) }
it { should_not allow_value(nil).for(:on_ride_photo) }
it { should_not allow_value(4).for(:on_ride_photo) }
it { should_not allow_value('yes').for(:on_ride_photo) }
end
But I get the following error when running my specs:
2) Coaster validations should allow on_ride_photo to be set to false
Failure/Error: it { should allow_value(false).for(:on_ride_photo) }
Did not expect errors when on_ride_photo is set to false, got error:
# ./spec/models/coaster_spec.rb:86:in `block (3 levels) in <top (required)>'
Is the fact that I want to allow false as a valid value being knocked down by the fact it has to be present and that false is classed as not present? If so, then how can I work around this?
Upvotes: 3
Views: 3264
Reputation: 29389
Yes, the issue is with presence: true
, which checks that the attribute is not Object#blank?
. Since the validations are additive, the inclusion:
clause can't "override" the fact that false
fails the presence
test.
The following is from http://edgeguides.rubyonrails.org/active_record_validations.html#presence:
Since
false.blank?
istrue
, if you want to validate the presence of a boolean field you should usevalidates :field_name, inclusion: { in: [true, false] }.
See Billy Chan's answer for a discussion of how boolean fields are handled, including in particular information on which values are treated as nil
, true
and false
.
Upvotes: 2
Reputation: 24815
Finally I got the answer, thanks to Peter's updating.
t.boolean :foo
true
or false
value, see below. (I don't think it's necessary to unit test them though integration or functional tests for including parts of them would be okay)I just browsed the source code. The type boolean, either PostgreSQL, Sqlite3 or MySQL will finally point to ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value)
,
# convert something to a boolean
def value_to_boolean(value)
if value.is_a?(String) && value.empty?
nil
else
TRUE_VALUES.include?(value)
end
end
and then got judged by this constant
# the namespace is still "ActiveRecord::ConnectionAdapters::Column"
TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON'].to_set
FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set
So, we finally got it.
If empty, nil
If not empty, check if it is in TRUE_VALUE, if not, false. That's why "4", "yes" is false.
Upvotes: 1