【Rails】after_createとafter_create_commitの挙動の違い

## はじめに

Railsのコールバックメソッドである`after_create`と`after_create_commit`の違いを備忘録としてまとめます。

最後まで読めば、両者の挙動の違いや使い分けが理解できるかなと思います。

この記事におけるバージョンは**Rails 7.1**です。

## `after_create`と`after_create_commit`の挙動の違い

2つのメソッドの挙動をそれぞれまとめます。

なおこの後でてくる「トランザクション」は、`User.transaction do`のような明示的に開始するトランザクションだけを指しているわけではありません。

`User.create`などでデータベースに変更を加える際にRailsが自動的に管理する内部的なトランザクションも含めて指しています。

### `after_create`

`after_create`はレコードがデータベースへ保存された直後、**トランザクションがコミットされる前に実行されます。**

そのためコールバック内でエラーが発生した場合、トランザクション全体がロールバックされるのです。

つまり、`after_create`はトランザクション内の処理の一部として扱われます。

### `after_create_commit`

`after_create_commit`は**トランザクションが成功しコミットされた後に実行されます。**

そのためコールバック内でエラーが発生しても、トランザクションには影響を及ぼしません。

## `after_create`と`after_create_commit`の使い分け

`after_create`と`after_create_commit`の違いを意識せずに使っていると、データ整合性にまつわるエラーを引き起こす可能性があります。

そこでここからは以下の2つのケースについて、説明していきます。

- `after_create_commit`より`after_create`が適しているケース
- `after_create`より`after_create_commit`が適しているケース

### `after_create_commit`より`after_create`が適しているケース

ユーザー情報とプロフィール情報を別のテーブルで管理している状況を想定します。

下記のサンプルを見てください。

```ruby
# NG例
class User < ApplicationRecord
  after_create_commit :create_profile

  private

  def create_profile
    Profile.create!(user_id: id)
  end
end
```

`after_create_commit`を使用しているため、ユーザーの作成が成功し、`create_profile`によるプロフィール作成が失敗した場合に、ユーザーの作成処理はロールバックされません。

これにより、プロフィールのないユーザーが作成される可能性があります。

`after_create`を使えば、プロフィールの作成が失敗した場合に、ユーザーの作成もロールバックされるように実装できます。

```ruby
# OK例
class User < ApplicationRecord
  after_create :create_profile

  private

  def create_profile
    Profile.create!(user_id: id)
  end
end
```

このようにデータの整合性を担保した上で関連レコードを操作する必要がある場合は、トランザクション内で処理が行われる`after_create`が適しています。

※説明のためにこのケースをサンプルとして用意しましたが、こういったケースではそもそもコールバックを使用せずに明示的なトランザクションの中で処理をした方が良いと思われます。

### `after_create`より`after_create_commit`が適しているケース

ユーザー登録時にメールを送信する状況を想定します。

```ruby
# NG例
class User < ApplicationRecord
  after_create :send_welcome_email

  private

  def send_welcome_email
    WelcomeEmailJob.perform_later(id)
  end
end

class WelcomeEmailJob < ApplicationJob
  def perform(user_id)
    user = User.find_by(id: user_id)

    UserMailer.welcome(user).deliver_now
  end
end
```

この実装だと`user = User.find_by(id: user_id)`で問題が発生するかもしれません。

`after_create`を使用しているためトランザクションのコミット前に非同期ジョブがキューへ投入されるからです。

ジョブの実行時点でトランザクションがコミットされているとは限りません。

もしトランザクションのコミットよりも先にジョブが実行された場合、ユーザーのレコードがまだデータベースに保存されていないため、`user = User.find_by(id: user_id)`の結果は`nil`になります。

その結果、`UserMailer.welcome(user).deliver_now`がエラーとなってしまうのです。

`after_create_commit`を使えば、こういった懸念を排除できます。

```ruby
# OK例
class User < ApplicationRecord
  after_create_commit :send_welcome_email

  private

  def send_welcome_email
    WelcomeEmailJob.perform_later(id)
  end
end

class WelcomeEmailJob < ApplicationJob
  def perform(user_id)
    user = User.find_by(id: user_id)

    UserMailer.welcome(user).deliver_now
  end
end
```

このケースではトランザクションがコミットされたことを確認できてからジョブを実行すべきなので`after_create_commit`が適切です。

## おわりに

最後にまとめです。

- `after_create`: トランザクションがコミットされる前に実行
- `after_create_commit`: トランザクションがコミットされた後で実行

`after_create`を使用することで、関連レコードの作成に失敗した場合でもトランザクション全体をロールバックしてデータの不整合を防げます。

`after_create_commit`を使用することで、非同期ジョブを安全かつ確実に実行できます。

使い方を誤ると予期せぬ動作や意図しない結果を引き起こしてしまう可能性があるため、きちんと両者の挙動を理解して使い分けましょう。

## 参考資料

https://railsguides.jp/active_record_callbacks.html

https://api.rubyonrails.org/v7.1/classes/ActiveRecord/Callbacks/ClassMethods.html#method-i-after_create

https://api.rubyonrails.org/v7.1/classes/ActiveRecord/Transactions/ClassMethods.html#method-i-after_create_commit