image by Obi Onyeador
Migrations, in common with much of Rails, are ‘sharp knives’ — easy to use, but potentially dangerous, and you can end up cutting yourself.
One way a migration can ‘cut’ you is by letting you make quick decisions about your database structure without considering the consequences. In this case we’ll examine the case of
NULL in a boolean field your SQL. This is converted to
nil when you use it in Ruby.
…allowing boolean values to be
NULL in your database.
class AddRecommendedIndexes < ActiveRecord::Migration[5.1] def change add_column :characters, :satisfied, :boolean end end
…use constraints in your database that prevent values that should be only
false from being
class AddRecommendedIndexes < ActiveRecord::Migration[5.1] def change add_column :characters, :satisfied, :boolean, default: false, null: false end end
This is a known issue called the Tri-state problem. Although
nil behaves a lot like
false in Ruby, they are not quite the same. Having a third state really complicates the logic of your application as you have to consider how
nil should be treated.
It also complicates your scopes. If you think of
false you’d have to use scopes like:
Character.where(satisfied: false).or(Character.where(satisfied: nil)) Character.where.not(satisfied: true)
Which is much more confusing than:
If you’re starting from scratch, just do this.
If you already have this issue — and heaven knows I’ve fallen into the trap — you will have to put together a migration strategy.
Changing or adding columns or default values to an existing table will lock the table in many databases, while it writes to every row. This isn’t a problem unique to this issue, but it is a problem when ‘fixing’ it.
This means you’ll have to consider downtime or perhaps multiple smaller migrations to ensure you minimise the impact on your application.
Last updated on December 17th, 2018 by @andycroll
An email newsletter, with one Ruby/Rails technique delivered with a ‘why?’ and a ‘how?’ every two weeks. It’s deliberately brief, focussed & opinionated.