I'm trying to ensure that a field of my model is a boolean, but my tests keep on failing.
After reading this: Validating boolean value in Rspec and Rails and this Rails: how do I validate that something is a boolean? I ended up doing it like so:
class Model < ActiveRecord::Base
validates :my_field, :inclusion => { :in => [true, false] }
end
I've tried testing this a few different ways (using rspec and shoulda matchers) and since my tests keep on failing, I'm right now down to the dumbest possible (?) way. Still, the tests don't pass and I'm guessing that there's some mechanism that converts the value somewhere.
Here's what I'm using to find out what's going on:
# create instance without setting value ...
# these work as expected
model_instance.valid?.should be_false # passes
model_instance.my_field = true
model_instance.valid?.should be_true # passes
model_instance.my_field = false
model_instance.valid?.should be_true # passes
# works as expected
model_instance.my_field = ""
model_instance.valid?.should be_false # passes
# these should pass but fail
model_instance.my_field = "foo"
model_instance.my_field.should == "foo" # fails as well, my_field == false
model_instance.valid?.should be_false # fails
model_instance.my_field = "false"
model_instance.my_field.should == "false" # fails as well, my_field == false
model_instance.valid?.should be_false # fails
model_instance.my_field = "123"
model_instance.valid?.should be_false # fails
model_instance.my_field = "true"
model_instance.my_field.should == "true" # fails as well, my_field == true
model_instance.valid?.should be_false # fails
What am I missing? Seems the value is converted in a somewhat logical fashion but where and how to prevent it? How to do this kind of validation properly?
I don't know where this idea that you need to validate a boolean field as true/false came from, but i've seen it in a few unrelated projects recently so I'm starting to wonder if this isn't a meme that started somewhere.
Its a boolean field, it HAS to be true or false. AR takes care of the implementation details of boolean values for you. If you want a boolean field, create a boolean field in the database. End of story.
Looking at the source, here's the code that converts a value to a boolean:
# File activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb, line 147
147: def value_to_boolean(value)
148: if value.is_a?(String) && value.blank?
149: nil
150: else
151: TRUE_VALUES.include?(value)
152: end
153: end
And here are the values that equate to their boolean counterparts:
TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'].to_set
FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE'].to_set
So taking another look at your code above, everything is as it should be, except that what you're expecting is incorrect. assigning "foo" to a boolean value we see that "foo" is not in the list of acceptable true values, so the boolean field defaults to false, which is what it will return from its accessor later. The boolean value for the field is still :in => [true, false]
so the model is valid. "false" and "123" fail for the same reason. "true" is an acceptable value of the boolean true, so the field gets set to true and is still valid for the reason outlined above.
I'm no Rails guru but I'll stick my neck out here and say that you absolutely do want to validate boolean values for :inclusion => { :in => [true, false] }
--that is, assuming you do not want to allow nil (NULL) values.
Coming from a database programming background, I've learned to keep in mind that a boolean field can have THREE values: true, false, and NULL. When programming in native SQL, NULLs require special handling (is null
instead of = null
) that causes a lot of extra work.
In testing with Rails 3.2 today, it was no problem to create an unvalidated boolean field with a nil value and save it to my PostgreSQL database, where it was dutifully stored as NULL. To avoid all the problems that would cause, I'm planning on using this approach for booleans in Rails:
:null => false
in migrations.:inclusion => { :in => [true, false] }
to validate the field in the model. This gives a nice message if the field is uninitialized (nil).It's not intuitive at first that I can't use a :presence
validation, but it turns out :presence
validates that the value is not blank, and the value false
is blank. In other words, if you validate :presence => true
and set the value to false
, the validation will fail. See validates_presence_of in the API and @Paul A Jungwirth's comment on @Karmajunkie's answer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With