Boolean型のカラムを追加するときは必ずデフォルト値を設定しよう


## tl;dr (最初に結論)
Railsのモデルに新しくboolean型のカラムを追加するときは必ずデフォルト値を設定しておいた方がいいです。
すなわち、

```ruby
# NG
add_column :users, :notification_allowed, :boolean
```

ではなく、

```ruby
# OK
add_column :users, :notification_allowed, :boolean, default: false, null: false
```

のように書きましょう、ということです。

`default: false` はデフォルト値を `false` に設定することを示しています。
なので既存のレコードは `notification_allowed` カラムの値が `false` になります。

`null: false` は `NULL` (Rubyでいうところの `nil`)が設定されることを禁止するオプションです。
必須ではありませんが、`NULL` が入ると面倒な問題を引き起こしやすいので基本的に付けておくことをオススメします。

それではこの件に関する詳しい内容を以下で説明していきます。

## データベースとRubyで異なる NULL / nil の扱い
デフォルト値を明示的に設定しなかった場合、新しいカラムには `NULL` が入ります。

データベース（SQL）の世界において、`NULL` は特殊な存在です。
`NULL` は `false` の意味でもなく、「空（から）」の意味でもありません。
`NULL` は **「まだ決まっていないから何も評価できない」** の意味になります。（`NULL` の意味づけは文献によって多少異なるので、適宜ネットや技術書も参考にしてください）

つまり、`NULL` は  **「`true` でも `false` でもない状態」** を表します。
よって、Boolean型のカラムに `NULL` が入ると、「`true` / `false` / `NULL`」の3つの状態が存在することになるのです。

一方、Rubyでは **「`nil` と `false` はどちらも偽」** として扱われます。

このようにデータベースとRubyでは `NULL` / `nil` の取り扱いルールが異なっています。
なので、データの検索の考え方が非常にややこしくなります。

## 実際のRailsアプリケーションで確認してみる
言葉の説明だけではピンと来ない人もいると思うので、具体的なデータを使って確認してみましょう。

以下のサンプルコードの実行環境は以下の通りです。

- Rails 4.2.1
- PostgreSQL 9.4.3 

ここではPostgreSQLを使っていますが、MySQLでも同じ結果になります。

さて、このアプリケーションには User クラスがあります。
データベースには3件のレコードが入っています。

```
 id |  name   |         created_at         |         updated_at
----+---------+----------------------------+-----------------------------
  1 | Alice   | 2015-06-09 19:31:19.874835 | 2015-06-09 19:31:19.874835
  2 | Bob     | 2015-06-09 19:31:26.632052 | 2015-06-09 19:31:26.632052
  3 | Charlie | 2015-06-09 19:31:41.060407 | 2015-06-09 19:31:41.060407
```

このクラスに「メール通知を受け取るフラグ（`notification_allowed`）」を追加します。
migrationファイルには特にデフォルト値を設定しません。

```ruby
class AddNotificationAllowedToUsers < ActiveRecord::Migration
  def change
    add_column :users, :notification_allowed, :boolean
  end
end
```

マイグレーションを実行すると、マイグレーション直後のデータは次のようになります。

```
 id |  name   |         created_at         |         updated_at         | notification_allowed 
----+---------+----------------------------+----------------------------+----------------------
  1 | Alice   | 2015-06-09 19:31:19.874835 | 2015-06-09 19:31:19.874835 | 
  2 | Bob     | 2015-06-09 19:31:26.632052 | 2015-06-09 19:31:26.632052 | 
  3 | Charlie | 2015-06-09 19:31:41.060407 | 2015-06-09 19:31:41.060407 | 
```

ご覧の通り、`notification_allowed` は空欄になっています。これは `NULL` が入っている状態です。

さて、画面からこのデータを更新してみます。
Aliceさんは「メール通知を受け取る」設定にします。

![Screen Shot 2015-06-10 at 4.38.51.png](https://qiita-image-store.s3.amazonaws.com/0/7465/991f0361-daf7-6820-32f8-459b72f7864d.png "Screen Shot 2015-06-10 at 4.38.51.png")

Charlie さんは「メール通知を受け取る」を変更せずに、自分の名前だけを変更しました。（それと同時に `notification_allowed` も `false` で更新されます）

![Screen Shot 2015-06-10 at 4.39.21.png](https://qiita-image-store.s3.amazonaws.com/0/7465/2d8a15bb-5d91-edd4-cd46-14460d8667fd.png "Screen Shot 2015-06-10 at 4.39.21.png")

その結果、データはこのようになりました。

```
 id | name  |         created_at         |         updated_at         | notification_allowed 
----+-------+----------------------------+----------------------------+----------------------
  1 | Alice | 2015-06-09 19:31:19.874835 | 2015-06-09 19:38:59.290501 | t
  2 | Bob   | 2015-06-09 19:31:26.632052 | 2015-06-09 19:31:26.632052 | 
  3 | Chris | 2015-06-09 19:31:41.060407 | 2015-06-09 19:39:29.294103 | f
```

ご覧の通り、`notification_allowed`には `t` / `NULL` / `f` の3種類が存在しています。

それでは `rails console` で、「通知を受け取るユーザ」を検索してみましょう。

```ruby
irb(main):004:0> User.where(notification_allowed: true)
  User Load (0.6ms)  SELECT "users".* FROM "users" WHERE "users"."notification_allowed" = 't'
=> #<ActiveRecord::Relation [#<User id: 1, name: "Alice", created_at: "2015-06-09 19:31:19", updated_at: "2015-06-09 19:38:59", notification_allowed: true>]>
```

Aliceさんが返ってきました。

続いて、「通知を受け取らないユーザ」を検索してみます。

```ruby
irb(main):005:0> User.where(notification_allowed: false)
  User Load (0.6ms)  SELECT "users".* FROM "users" WHERE "users"."notification_allowed" = 'f'
=> #<ActiveRecord::Relation [#<User id: 3, name: "Chris", created_at: "2015-06-09 19:31:41", updated_at: "2015-06-09 19:39:29", notification_allowed: false>]>
```

ここで返ってくるのはChrisさんだけです。Bobさんは含まれません。

Bobさんを検索するには次のように「通知を受け取るフラグが `NULL` のユーザ」を検索しなければいけません。

```ruby
irb(main):006:0> User.where(notification_allowed: nil)
  User Load (0.6ms)  SELECT "users".* FROM "users" WHERE "users"."notification_allowed" IS NULL
=> #<ActiveRecord::Relation [#<User id: 2, name: "Bob", created_at: "2015-06-09 19:31:26", updated_at: "2015-06-09 19:31:26", notification_allowed: nil>]>
```

しかし、現実の世界ではこの3つの状態をわざわざ使い分けたいと思うケースは滅多にないでしょう。
ふつうは「通知を受け取る」「受け取らない」のどちらかになっていることを期待するはずです。

デフォルト値の設定が必要になるのはこのためです。
また、万一 `NULL` が入るとこのややこしい問題が再浮上してしまうので、データベースレベルで `NULL` が入ることを禁止しておく（NOT NULL制約を付ける）方がベターです。

つまり、migrationファイルはこのように書くべきでした。


```ruby
class AddNotificationAllowedToUsers < ActiveRecord::Migration
  def change
    add_column :users, :notification_allowed, :boolean, default: false, null: false
  end
end
```

こうしておけば、既存のレコードは最初から `false` の状態になります。

```
 id |  name   |         created_at         |         updated_at         | notification_allowed 
----+---------+----------------------------+----------------------------+----------------------
  1 | Alice   | 2015-06-09 19:31:19.874835 | 2015-06-09 19:31:19.874835 | f
  2 | Bob     | 2015-06-09 19:31:26.632052 | 2015-06-09 19:31:26.632052 | f
  3 | Charlie | 2015-06-09 19:31:41.060407 | 2015-06-09 19:31:41.060407 | f
```

`NULL` は入ってこないので、検索する場合も `true` と `false` のどちらかを指定するだけで済みます。 

```ruby
# 「通知を受け取るユーザ」を検索
User.where(notification_allowed: true)

# 「通知を受け取らないユーザ」を検索
User.where(notification_allowed: false)

# NULLのデータは絶対に存在しないので実行結果は常に0件
User.where(notification_allowed: nil)
```

## NULL は false と見なして検索する方法
「気づくのが遅かった！すでにデータに `NULL` が入っちゃってます！」という場合は以下のようにすれば `NULL` と `false` のデータを同時に検索できます。
（`NULL` は `false` として扱う場合）

```ruby
irb(main):001:0> User.where("notification_allowed = ? OR notification_allowed IS NULL", false) 
  User Load (0.5ms)  SELECT "users".* FROM "users" WHERE (notification_allowed = 'f' OR notification_allowed IS NULL)
=> #<ActiveRecord::Relation [#<User id: 2, name: "Bob", created_at: "2015-06-09 19:31:26", updated_at: "2015-06-09 19:31:26", notification_allowed: nil, banned: false>, #<User id: 3, name: "Chris", created_at: "2015-06-09 19:31:41", updated_at: "2015-06-09 19:39:29", notification_allowed: false, banned: false>]>
```

## 既存の NULL のレコードを一気に false で更新する方法

既存のレコードに `NULL` が含まれる場合、毎回 `NULL` を考慮しながら検索するのは面倒です。
それよりも既存のレコードから一気に `NULL` を消し去ってしまう方があとあとの効率が良さそうです。

以下のようにすればSQL一発で `NULL` のレコードを全件 `false` に変換できます。

```ruby
irb(main):008:0> User.where(notification_allowed: nil).update_all(notification_allowed: false)
  SQL (2.2ms)  UPDATE "users" SET "notification_allowed" = 'f' WHERE "users"."notification_allowed" IS NULL
=> 1
```

## あとから NOT NULL制約を追加する方法

既存のレコードから `NULL` を消し去ったら、さらにマイグレーションを追加して `NULL` を禁止しておくと安全性がより向上します。
以下はあとから NOT NULL制約を追加するmigrationファイルの記述例です。

```ruby
class ChangeNotificationAllowedInUsers < ActiveRecord::Migration
  def change
    change_column :users, :notification_allowed, :boolean, null: false
  end
end
```

ただし、このマイグレーションを実行する場合はあらかじめ既存のレコードから `NULL` のレコードをなくしておく必要があります。
`NULL` のレコードがあると下記のようなエラーが発生して `NOT NULL` 制約を付与することができません。

```bash
$ bin/rake db:migrate
== 20150609212340 ChangeNotificationAllowedInUsers: migrating =================
-- change_column(:users, :notification_allowed, :boolean, {:null=>false})
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:

PG::NotNullViolation: ERROR:  column "notification_allowed" contains null values
: ALTER TABLE "users" ALTER "notification_allowed" SET NOT NULL/Users/jit/dev/sandbox/boolean-default-postgres/db/migrate/20150609212340_change_notification_allowed_in_users.rb:3:in `change'
-e:1:in `<main>'
ActiveRecord::StatementInvalid: PG::NotNullViolation: ERROR:  column "notification_allowed" contains null values
: ALTER TABLE "users" ALTER "notification_allowed" SET NOT NULL
/Users/jit/dev/sandbox/boolean-default-postgres/db/migrate/20150609212340_change_notification_allowed_in_users.rb:3:in `change'
-e:1:in `<main>'
PG::NotNullViolation: ERROR:  column "notification_allowed" contains null values
/Users/jit/dev/sandbox/boolean-default-postgres/db/migrate/20150609212340_change_notification_allowed_in_users.rb:3:in `change'
-e:1:in `<main>'
Tasks: TOP => db:migrate
(See full trace by running task with --trace)
```

## MySQLで実行した場合

前述の通り、MySQLで実行してもPostgreSQLの場合と同じ結果になります。

```
+----+-------+---------------------+---------------------+----------------------+
| id | name  | created_at          | updated_at          | notification_allowed |
+----+-------+---------------------+---------------------+----------------------+
|  1 | Alice | 2015-06-09 19:51:41 | 2015-06-09 19:56:28 |                    1 |
|  2 | Bob   | 2015-06-09 19:51:48 | 2015-06-09 20:31:23 |                 NULL |
|  3 | Chris | 2015-06-09 19:51:57 | 2015-06-09 19:56:37 |                    0 |
+----+-------+---------------------+---------------------+----------------------+
```

上の図のように、PostgreSQLは `t` / `f`、MySQLは `1` / `0` になりますが、その違いはRailsが吸収してくれるのでコードを書くときは特に意識する必要はありません。 

ちなみに、以下は僕が実際に試したMySQLでの実行環境です。

- Rails 4.2.1
- MySQL 5.6.25

## 疑わしいmigrationファイルが存在しないかgrep検索する方法

One more thing. 

この記事を読んで「あー、そんなこと今まで気にしてなかったわ。もしかしたらやっちゃってるかも・・・」と心配になってきた人はRailsプロジェクトのディレクトリで以下のコマンドを実行してみてください。

```bash
grep -e 'add_column.*boolean' db/migrate/* | grep -v 'default'
```

これを実行するとデフォルト値なしでboolean型のカラムを追加したmigrationファイルを一覧表示してくれます。（以下はMac OS Xでの実行例です）

```bash
$ grep -e 'add_column.*boolean' db/migrate/* | grep -v 'default'
db/migrate/20150609193431_add_notification_allowed_to_users.rb:    add_column :users, :notification_allowed, :boolean
```

該当するmigrationファイルが見つかった場合は `NULL` が入っているデータがないか、またそのカラムを使った検索処理を実行していないかチェックしておきましょう。
もしかしたら取得すべきデータを取得できていないかもしれませんよ？

## まとめ
今回説明したような内容はデータベースのことがよく分かっている人でないと気づきにくい問題です。
変な不具合を埋め込んでしまわないようにboolean型のカラムを追加するときは必ずデフォルト値を設定するようにしましょう。

・・・といっても、人間はつい忘れちゃうのでマイグレーションを本番環境で実行する前に **誰かにコードレビューしてもらう** ことをオススメします。

また、Boolean型に限らず `NULL` はいろいろとやっかいな問題を引き起こすので、データベースと`NULL`の関係はしっかり勉強しておいた方がいいですね。