Railsの意外な落とし穴？ before_xxx で false を返すと処理を停止するが、 nil を返すと続行する


## はじめに、というか最初に結論

この記事では以下のようなRails（ActiveRecord）のコールバック仕様を説明します。

**before_xxx が false を返した場合のみ保存処理が停止する**
**before_xxx が nil を返した場合や、after_xxx が false を返した場合はそのまま保存される**

この仕様を知らなかった人は先に進みましょう。
知っていた人も興味があれば続きを読んでください。

### 動作確認したRailsバージョン

- Rails 4.2.4

## Railsあるある問題のひとつ、「before_xxx の戻り値を間違える」

知っている人は知っている、そして知っている人でもたまにうっかり間違える、Railsのよくある落とし穴として before_valdiation や before_save の戻り値のワナがあります。

この問題については @suginoy さんがドンピシャな記事を書いてくれています。

[rails3 - ActiveRecordのbefore_saveでうっかり - Qiita](http://qiita.com/suginoy/items/b10a1829e008a44d8a0c)

以下は上の記事に載っているコード例です。

```ruby
class Item < ActiveRecord::Base

  before_save :prepare_save # This callback doesn't validate

  def prepare_save
    if hoge_check_ok?
      self.checked = true
    else
      self.checked = false
    end
  end
end
```

このコードの問題点は次の通りです。

まず、before_save コールバックで `false` が返されるとそこで処理が止まってしまい、レコードが保存されないというのがRailsの（なかなかいやらしい）仕様です。
なので、`else` に入ってくるとコールバックの戻り値が `false` になり、処理が止まってしまいます。（そして大半のケースで、これはバグとして扱われます）

詳細は元の記事の説明を参照してください。

## false と nil は同じか、別物か？

一方、Rubyでは `false` と `nil` を偽の値として扱い、それ以外の値を真として扱う、という言語仕様があります。
なので、コールバックの戻り値も `false` と `nil` はどちらも同じように扱われると思っていました。

が！！

before_xxx の場合は `false` だけが処理停止の条件になり、 `nil` は続行します。
これはこれでなんとも紛らわしい仕様だと思いませんか？
少なくとも僕はビックリしました。

## 公式ドキュメントにはどう書いてあるのか？

公式ドキュメントにはどう書いてあるのか調べてみました。

[ActiveRecord::Callbacks](http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html)

> Canceling callbacks

> If a before_* callback returns false, all the later callbacks and the associated action are cancelled. 

以下ざっくり翻訳です。

> コールバックのキャンセルについて

> before_* コールバックが false を返した場合、それ以降の全コールバックと、呼び出し元のアクションはキャンセルされます。

うーん、「`nil` は違うよ！」と明示的に書いてもらわないと、Rubyの慣習的に「`false` も `nil` も一緒だよね？」と思えなくもありません。（どうでしょうか？？） 


## テストを書いて確認してみた

というわけで、この仕様を確認するテストを書いてみました。

まず、こんなモデルを作ってみました。
コールバックメソッドを一通り定義しています。
（コールバックの一覧はこちらの[公式ドキュメント](http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html)に記載されています）

```ruby
# user.rb
class User < ActiveRecord::Base
  validates :name, presence: true

  before_validation :method_before_validation
  def method_before_validation
    true
  end

  after_validation :method_after_validation
  def method_after_validation
    true
  end

  before_save :method_before_save
  def method_before_save
    true
  end

  before_create :method_before_create
  def method_before_create
    true
  end

  after_create :method_after_create
  def method_after_create
    true
  end

  after_save :method_after_save
  def method_after_save
    true
  end

  after_commit :method_after_commit
  def method_after_commit
    true
  end
end
```

それから、RSpecのテストコードはこんなふうに書きました。
[rspec-parameterized](https://github.com/tomykaira/rspec-parameterized)とモックを使って順番にコールバックの戻り値を `true` または `false` に切り換え、保存できる/できないを検証しています。
（RSpecのモックの使い方については[こちらの記事](http://qiita.com/jnchito/items/640f17e124ab263a54dd)をご覧ください）

```ruby
# user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  using RSpec::Parameterized::TableSyntax

  let(:user) { User.new(name: 'Alice') }

  example 'default implementation' do
    expect(user.save).to be_truthy
  end

  where(:callback, :value, :result) do
    :method_before_validation | false | false
    :method_before_validation | nil   | true
    :method_after_validation  | false | true
    :method_after_validation  | nil   | true
    :method_before_save       | false | false
    :method_before_save       | nil   | true
    :method_before_create     | false | false
    :method_before_create     | nil   | true
    :method_after_create      | false | true
    :method_after_create      | nil   | true
    :method_after_save        | false | true
    :method_after_save        | nil   | true
    :method_after_commit      | false | true
    :method_after_commit      | nil   | true
  end

  with_them do
    example 'callback value' do
      allow(user).to receive(callback).and_return(value)
      expect(user.save).to eq result
    end
  end
end
```

上のテストコードから `result` が `false` になるケース（つまり、保存できないケース）だけを抜き出すと以下のようになります。

```ruby
:method_before_validation | false | false
:method_before_save       | false | false
:method_before_create     | false | false
```

これ以外はすべて `result` が `true` になります。

## 結論

上のテストからわかる結論は以下の通りです。

**before_xxx が false を返した場合のみ保存処理が停止する**
**before_xxx が nil を返した場合や、after_xxx が false を返した場合はそのまま保存される**

知らなかった人はこの機会に覚えておきましょう！

## 追記：Rails 5での仕様変更について

Rails 5では仕様が変わり、 before_xxx で `false` を返してもそのまま処理が続行されるようです。
コールバックで処理を停止したいときは `throw :abort` します。

```rb
before_save :ensure_admin

def ensure_admin
  throw(:abort) unless self.admin
end
```

こちらのスライドに詳しい情報が載っています。

[Better Callbacks in Rails 5 // Speaker Deck](https://speakerdeck.com/claudiob/better-callbacks-in-rails-5)

情報提供してくれた @suginoy さん、ありがとうございます！！