Presence, NULL, and Product Requirements in Rails
A short article why NOT NULL
constraints are essential for long-term product health.
An issue reported against active_record_doctor
made me revisit my reasoning behind certain active_record_doctor
rules, and I decided to put that in writing. Let’s start with the technical notions of presence, absence, and NULL
, and then discuss how they relate to product requirements.
Validators vs Constraints
Constraints are checks enforced by the database that are used for ensuring data consistency. One of those checks is marking a column as NOT NULL
, which tells the database to disallow NULL
values in that column. Attempting to insert a NULL
will result in a database error that gets translated into ActiveRecord::NotNullViolation
by Rails. I use database constraints a lot for three reasons:
- They cannot be circumvented, which makes them robust.
- As a form of executable documentation: they make it clear that a value will be present when reading, or will be required when writing.
- I see them as a way of enhancing testing by introducing small assertions that are checked throughout the entire test suite.
Presence and absence validators provided by Rails play a similar role, but are more focused on producing user-friendly error messages. Unfortunately, there are ways to skip them, which makes them less robust than database constraints. Additionally, being present means something more than not being nil
. An empty string or a string consisting of white space are examples of objects that are not present, but are definitely not nil
either.
That brief technical background is enough to discuss how these notions relate to product requirements.
Mapping Product Requirements to Engineering Concepts
Product requirements often mandate presence of specific inputs, like email addresses or names. The intent behind these requirements can be realized by using Rails presents validators. The primary reason for this is the fact that even when an input is left blank then the browser will still send it to the server as an empty string, so an actual NULL
is nowhere to be seen, and NOT NULL
would enforce absolutely nothing.
Theoretically, a presence validator is all we need. In practice, I find NOT NULL
constraints to be equally important, even if they’re more lenient than presence validators. There are three reasons for that:
- It’s easy to create a test fixture with an unexpected
NULL
, resulting in the test suite running on unrealistic data. - Operations like
insert_all
,upsert_all
, andupdate_all
skip validations, but maybe critical for performance. - Similarly, it’s easy to end up with an unwanted
NULL
when changing the database structure in a complex migration.
That’s why my recommendation is: if you add a presence validator to a model then add a NOT NULL
constraint on the underlying column. active_record_doctor
enforces that rule via the missing_non_null_constraint
check.
As a side note, I also recommend doing the inverse: adding a presence validator for each NOT NULL
in the database. This is a topic for a separate article, though.
Closing Thoughts
In this article, we discussed why every presence validator would benefit from the corresponding NOT NULL
constraint. That and many other rules are enforced by active_record_doctor
, so give it a shot if you want to solve database issues before they hit production.
Enjoyed the article? Follow me on Twitter!
I regularly post about Ruby, Ruby on Rails, PostgreSQL, and Hotwire.
Copyright © 2019-2023 Greg Navis. All rights reserved.