Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails: Validating inclusion of a boolean fails tests

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?

like image 904
polarblau Avatar asked Mar 02 '11 15:03

polarblau


2 Answers

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.

like image 67
Keith Gaddis Avatar answered Nov 02 '22 19:11

Keith Gaddis


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:

  • Define boolean fields with :null => false in migrations.
  • Use :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.

like image 28
Mark Berry Avatar answered Nov 02 '22 20:11

Mark Berry