ActiveSupport::Callbackを使うのをやめろ

## 追記:

個人的な最終結論が出たのでそちらをまとめました。

- [続・ActiveSupport::Callbackを使うのをやめろ](https://qiita.com/luccafort/items/c1f4290dca1a5b45c1eb)

## 概要

タイトルは1年ちょっと前の自分に向けた自戒なので絶対に使うのが悪というわけではない(と思う)。
だがぼくにはうまい付き合い方が見つけられそうになかったのでやめろという強い口調を意図して使っている。

## ActiveSupport::Callback is 何?

これ→[activesupport/lib/active_support/callbacks.rb](https://api.rubyonrails.org/classes/ActiveSupport/Callbacks.html)

Railsガイドにも記載されているのでRailsチュートリアル完走するかそれ相当のことをしていたら使う使わないは別にして知っているだろうと思う。

[Active Record コールバック](https://railsguides.jp/active_record_callbacks.html)

```ruby
# サンプルでよくみるやつ
class User < ApplicationRecord
  after_create :send_confirm_to_user

  private

  def send_confirm_to_user
    # sign upしたら最終確認のために送られるメールだと思ってほしい
    UserMailer.signup_confirmation(to: mail).deliver
  end
end
```

## 当時の考え

当時コールバックで実装すべきか、Service Objectで実装すべきか一瞬考えた……がrailsというフレームワークが提供している機能なので巨人の肩には乗っておいたほうが後々良いだろうと判断した。

その結果、いま地獄をみている。

## ActiveSupport::Callbackの問題

このエントリを書こうとしてから何故か見つかるという非常に悲しい思いを今まさにしているが以下のような現象に苦しまされるのでコールバックは使わない、使うとしてもコントロール可能な実装を用いて最小限で使うべきというのがいまの感想だ。

- [Rails: メールをActive Recordのコールバックで送信しないこと（翻訳）](https://techracho.bpsinc.jp/hachi8833/2019_09_12/76762)
- [Rails: Active Recordのコールバックを避けて「Domain Event」を使おう（翻訳）](https://techracho.bpsinc.jp/hachi8833/2019_09_12/76762)
- [メールを送信する処理をどこに書くべきか？](https://github.com/clean-rails/conversation/issues/1#issuecomment-353378881)

## 地獄とはなんであるか？

他にもあるが大きくは「テストがしにくい」「影響範囲がわからない」の2点にほぼ集約される。

- `FactoryBot.create :user`したらコールバックが走ってメールが送信される
- `rails console` で `User.create` したらメールが送信される
- migration内で `User.create` したらメールが（ｒｙ

などなど。
特に注意しないといけないのが`console`と`migration`内でモデルを使うときでこれらのときにコールバックが実行されることを意識していることはまれだと思う。
ぼくは全く意識しておらず実行して「😱😱😱」と何度かなった、マジでやばい。

上記の「Rails: メールをActive Recordのコールバックで送信しないこと（翻訳） おたより発掘」でも言及されているが「コールバックは副作用である」という説明が非常に端的かつわかりやすいと思う。

ぼくの場合FactoryBotで非常に困ったというかテストが書きにくくなってしまいました。
これはコールバックそれ自体だけではコントロールが難しい点に問題がある。

A、B、Cというユーザを新規作成し、それぞれに別の`Organization`に所属させるテストを書きたいだけなのにコールバックでメールを送信してしまうとこのときメールが実行されてしまいます。

一応コールバックには `skip_callback` で処理をスキップさせることができます……が今回の例のような非常に関心の高い `User` というモデルはテストでもよく使われますがそのたびに毎回 `skip_callback` を書く？
正気の沙汰じゃないですね！！！

またコールバックはデータの意図が明示的でないので問題が起こったときに調査しにくくなってしまいました。
極端な例ですが

1. `Organization` を削除したときに `Members` を `after_destroy` で削除
2. `Members` の `after_destroy` で `User` の `organization_id` を `nil` に変更

という処理を書いていた場合 `Organization` が `Member` と `User` に影響することを常に意識しなければいけない。

だが、現実的にこれは非常にしんどい。

テストを書いているときもコンソールを叩いているときもマイグレーションを実行するときにもどこでなにをするにも `Organization` が削除されたときにこれらを意識してデータが意図しない状態になっていないかを検知できなくてはいけないことになります。

将来的に影響する範囲が縮小するのであればいいですが、現実はその反対でより依存する数が増えるだろうと思います。
ぼくは増えてしまい大変困りました、困っています。

## ではどうするか？

1番、というか実質ほぼこれだと思うのですが、Service Object相当な場所に実装を移します。

```ruby
# CreateUserAndSendMailService.new(user).create をコントローラなどで呼び出す
class CreateUserAndSendMailService
  def initialize(user)
    @user = user
  end

  def create
    raise "なんかエラー起こった" unless @user.save
    UserMailer.signup_confirmation(to: @user.mail).deliver
  end
end
```

どうしてもコールバックでないといけない事情がある場合はやむなく以下のようにして通常はコールバックは実行されないようにし、どうしても必要な箇所だけで `can_send_mail=true` にすることでコールバックを実行する許可を与える実装にするかなと思います。
ただ基本的には上記のService Objectで実装するほうがよいと考えています。

理由としては依存を特定のService Objectに押し込めることができるため、リファクタリングをすることになっても無用な不安(自分の検知していないところに影響しないか)などを心配する必要がなくなります。

最悪なくならずとも最小限に抑えることができます。

```ruby
class User < ApplicationRecord
  # 初期値は nil になる
  # https://ref.xaio.jp/ruby/classes/module/attr_accessor
  attr_accessor :can_send_mail
  after_create :send_confirm_to_user if: :can_send_mail?

  private

  def can_send_mail?
    can_send_mail.present?
  end

  def send_confirm_to_user
    # sign upしたら最終確認のために送られるメールだと思ってほしい
    UserMailer.signup_confirmation(to: mail).deliver
  end
end

user.can_send_mail = true
user.save # メールが送信される

other.new(name: 'alice', mail: 'alice@example.com')
other.save # メールは送信されない
```

コールバックとは関係ないのですがどこでなにが呼ばれるかわからない不安はコントローラで`ActiveRecord`の`where`や`save`などのメソッドが出ているから心配になるのであって最初から`CreateUserService`や`DestroyOrganizationService`のようなものがありコントローラはそれが呼び出されているだけであれば心配や不安に思うこともないのでコールバックだけの責任ではないですね。

とはいえ、コールバックがコードに現れたらそれはほぼ実質的に黒なのでコードレビューの段階で阻止すべき事柄なのだなと学習しました。

## まとめ

正直なところ `default_scope` はあまり使うべきでない(`unscope`があるとはいえ)というのは知っていたのだけど `callback` もそうだとは思っておらず楽をしようとして大きな負債を積み上げてしまった。
`rails` 便利機能が非常に豊富である一方、この機能をうかつに使うとあとで大きなコストを払うことになる……みたいな初心者がよく躓くケースをまとめてRailsガイドに載せておいてくれといいたい。

普通に考えたらフレームワークが提供している機能のほうが自前で実装するよりもいいと思うじゃん。

## 追記

類似エントリにサジェストされていたおかげで↓の記事を読ませてもらった、2012年09月04日の話なので少し古いがテストがしにくいという問題は一応解決できそう。
ただ根本的解決というよりは暫定対応っぽい解決方法に感じるのでやはり `Service Object` 相当な実装のほうがいいのかなという印象は変わらず。

[FactoryGirlでコールバックをスキップする](https://qiita.com/naoty_k/items/40f806c29131717c1d81)