A Developer with a Pencil

Rails Validates :presence of Boolean - Not What You Think

Rails validates :presence and boolean fields

First, take a look at this following model:

survey_question.rb
1
2
3
4
5
6
7
8
9
10
11
class SurveyQuestion < ActiveRecord::Base
  belongs_to :owner, :polymorphic => true
  has_many :dependent_questions, :class_name => "SurveyQuestion", :as => :owner
  has_many :survey_answers
  serialize :options, Hash

  attr_accessible :owner, :owner_id, :owner_type, :question_body, :question_type, :options, :required, :parent_answer_condition, :client_integration_param_name

  validates :required, :question_type, :question_body, presence: true

end

and a short run in the rails console:

Rails console
1
2
3
4
5
6
7
8
9
10
[3] pry(main)> SurveyQuestion.create!({owner_type: "Survey", owner_id: 1, required: false, question_body: "How old are you?", question_type: "text"})
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
ActiveRecord::RecordInvalid: Validation failed: Required can't be blank
from /Users/eladmeidar/.rvm/gems/ruby-1.9.3-p448/gems/activerecord-4.0.0/lib/active_record/validations.rb:57:in `save!'
[4] pry(main)> SurveyQuestion.create!({owner_type: "Survey", owner_id: 1, required: true, question_body: "How old are you?", question_type: "text"})
   (0.1ms)  begin transaction
  SQL (6.6ms)  INSERT INTO "survey_questions" ("options", "owner_id", "owner_type", "question_body", "question_type", "required") VALUES (?, ?, ?, ?, ?, ?)  [["options", "--- {}\n"], ["owner_id", 1], ["owner_type", "Survey"], ["question_body", "How old are you?"], ["question_type", "text"], ["required", true]]
   (2.3ms)  commit transaction
=> #<SurveyQuestion id: 1, owner_id: 1, owner_type: "Survey", question_body: "How old are you?", question_type: "text", options: {}, required: true, parent_answer_condition: nil, client_integration_param_name: nil>

Note that SurveyQuestion has a boolean attribute named required, we also added it to a :presence validation along side other attributes. When we tried to create an instance of SurveyQuestion in the console with a required: false value, we got the following exception

1
ActiveRecord::RecordInvalid: Validation failed: Required can't be blank

Obviously, it is there. what happened?

ActiveModel::Validations::PresenceValidator

ActiveModel::Validations::PresenceValidator is the class responsible to handle :presence validations in ActiveModel, there it is:

rails/activemodel/lib/active_model/validations/presence.rb
1
2
3
4
5
class PresenceValidator < EachValidator # :nodoc:
  def validate_each(record, attr_name, value)
    record.errors.add(attr_name, :blank, options) if value.blank?
  end
end

The PresenceValidator basically adds an error when the value of the validated field returns true for #blank? but, the way Object#blank? treats booleans is the way that causes this issue:

Object#blank?
1
2
[1] pry(main)> false.blank?
=> true

Which means, that this is the reason why the :presence validation is failing for boolean fields with a false value, it simply returns true for false values.

Solution

Not really a solution, rather an ugly workaround but the way to get it done is to use the :inclusion validation instead.

survey_question.rb
1
2
3
4
5
6
7
8
9
10
11
class SurveyQuestion < ActiveRecord::Base
  belongs_to :owner, :polymorphic => true
  has_many :dependent_questions, :class_name => "SurveyQuestion", :as => :owner
  has_many :survey_answers
  serialize :options, Hash

  attr_accessible :owner, :owner_id, :owner_type, :question_body, :question_type, :options, :required, :parent_answer_condition, :client_integration_param_name

  validates :question_type, :question_body, presence: true
  validates :required, inclusion: [true, false]
end

I think that a better solution for this problem is to pass the column/field object to the validation as well. It will allow any validator such as PresenceValidator to perform specific validation techniques that match the field/column type - in this case - allowing a boolean field to have a false value.

Comments