How to validate the presence of a boolean field in a Rails model

Stefanni Brasil

There is a subtle bug that happens when validating the presence of boolean fields in Rails. Ensuring adequate test coverage helps us find those issues before they hit production.

Broken code

Here is an example that looks right but hides a subtle problem:

class Question < ApplicationRecord
  validates :required, presence: true
end

Here is the spec that describes the behavior we want. We want required to be either true or false but not nil. This helps avoid the Three-state boolean problem.

describe "#required" do
  it "only allows true or false" do
    expect(Question.new(required: false)).to be_valid
    expect(Question.new(required: true)).to be_valid
    expect(Question.new(required: nil)).to be_invalid
  end
end

But when we run the test, we get an error message:

Failure/Error: expect(Question.new(required: false)).to be_valid
  expected #<Question id: nil, required: false, created_at: nil, updated_at: nil> to be valid, but got errors: Required can't be blank

Aha! The error message highlights the subtle bug in our implementation. The spec fails because the Rails presence validator rejects any falsy values, not just nil.

How to validate the presence of a boolean column in Rails

According to Rails validations guides, we should use the inclusion validator to ensure the required boolean field is either true or false, but not nil.

class Question < ApplicationRecord
  validates :required, inclusion: [true, false]
end

Now, running the spec again, it passes and we avoided getting paged for an incident during the night 🎉

(Thanks, Joël Quenneville, for the great review!).