2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Railsのコールバックはつらいよ

Last updated at Posted at 2025-12-10

この記事は CA Tech Lounge Advent Calendar 2025 の11日目の投稿となっております。
他の方の記事は上記のリンクからご覧ください!
CA Tech Lounge についてはこちらからご覧ください!

はじめに

どうも、りょうさんです。埼玉の田舎の方で情報系大学生をしている学部4年生です。
CA Tech Lounge ではバックエンドエンジニアとして所属しています。

時代の主流は Golang なのですが、私は Ruby が好きなので、タイトルにもあるように Rails の話をしようと思います。

Rails のコールバックについて

みなさんは Rails のコールバックについて知っていますか?コールバックというのは、ActiveRecord で標準搭載されている機能です。

以下、Rails ガイドの引用です。

コールバックを使うと、オブジェクトの状態の変更「前」または変更「後」にロジックをトリガーできます。コールバックとは、オブジェクトのライフサイクルの特定の瞬間に呼び出されるメソッドのことです。

具体例として、以下のように書きます。

class BirthdayCake < ApplicationRecord
  after_create -> { Rails.logger.info("Congratulations, the callback has run!") }
end

after_create というのはオブジェクトの作成後に呼び出されることを意味するので、Railsコンソールだと以下のように呼び出すことができます。

irb> BirthdayCake.create
Congratulations, the callback has run!

なぜ辛いのか

副作用が密結合している

まず最初に挙げられるのは副作用が密結合しているパターンです。
例としては以下のようなコードです。

class User < ApplicationRecord
  after_create :send_welcome_email

  private

  def send_welcome_email
    UserMailer.welcome(self).deliver_now
  end
end

これは、ActionMailer を使用して、オブジェクトが save された瞬間にメールが送られるという処理になります。Rails コンソールでメンテなど行って user.save を行った瞬間にメールが送られるという予期せぬ副作用を引き起こします。
また、テストコードを書く際にも FactoryBot.create(:user) を行うたびにメール送信処理が走り、テストが重くなる原因にもなります。

個人的にはテストが重くなるの結構しんどいです。

処理を詰め込みすぎるやつ

2つ目に紹介するのは、before_save とか before_validate とかのときに処理を詰め込みすぎるやつもしんどいです。以下のような例ですね。

class Post < ApplicationRecord
  belongs_to :user

  before_validation :assign_region

  private

  def assign_region
    # 保存前にユーザーの地域を取得
    self.region = user.profile.region
  end
end

あと、値のセットとかバリデーションチェックがコールバックに紛れているパターンもしんどいです。
例えば、以下のコントローラーがあるとしましょう。

# controller
def create
  @article = Article.create!(title: params[:title], body: params[:body])
end

一見シンプルにタイトルと記事の中身を create しているように見えます。
しかし、model/article.rb を除いてみると、

class Article < ApplicationRecord
  belongs_to :author, class_name: "User"

  before_validation :set_author
  before_save :ensure_author_can_post!

  private

  def set_author
    # どこからともなく Current.user が差し込まれる
    self.author ||= Current.user
  end

  def ensure_author_can_post!
    raise "author is not allowed to post" unless author&.can_post?
  end
end

一見「便利そう」に見えるんですが、これが地味にキツいです。

特定のアクションでしか動作しないコールバック

Rails のコールバックって if:on: で条件を付けられるんですが、
これが本当にしんどい原因になることが多いです。
たとえば、

class Order < ApplicationRecord
  after_commit :send_notification, on: :create

  private

  def send_notification
    OrderNotifier.new(self).call
  end
end

一見すると「create のときだけ」通知が飛ぶので便利なんですが、
実務だとこれが地味に罠になります。

@order.update!(status: :paid)

「画面によって通知が飛んだり飛ばなかったり見える」ということがあります。
見た目は“注文が支払い済みになったら通知が飛んでほしい”という更新処理です。

でも実際には 飛びません。
一方で、テストコードではこう書いています。

FactoryBot.create(:order, status: :paid)

本番・管理画面・バッチ・API・テスト……
ユースケースによって create と update のどちらが走るかが違うため、挙動が違う事がある、というのが個人的に辛いポイントです。

そして、

after_commit :notify_admin, if: -> { saved_change_to_status? && paid? }

のように、誰かがあとから if: 条件を追加し始めると、さらにカオスになります。こうなると、「paid に変更したときだけ通知してほしい」「でも特定の画面では status を直接いじらずに create している」「バッチ処理は違うパスを通る」「テストは create で済ませている」など、どのタイミングでどう動くのかがの把握がかなり困難になります。

まとめ

Rails のコールバックは便利な仕組みですが、使い方を誤るとアプリケーションの挙動が分かりづらくなり、後から触る人(自分含む)が確実に苦しみます。
今回挙げた3つの例は、個人的に体感したしんどい例となります。
適切なタイミングでコールバックは使用して、楽しいActiveRecordの世界に触れていきましょう!

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?