この記事は 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の世界に触れていきましょう!