【Rails】コールバックの基本、応用、スキップを分かりやすく解説

はじめに

こんにちは、株式会社TOKOSのナオキです!

皆さんはコードの可読性を気にした設計が出来ていますか?
「とりあえず思い通りに動けばいいや」と思いコントローラーにロジックを詰め込んだり、同じ処理をコントローラー毎に記述していませんか?
もしそのような方がいましたら今回解説するコールバックを使用し、メソッドの複雑度をさげ可読性が上がるよう設計していきましょう!

対象読者

  • Rails初学者の方
  • コードの可読性を上げたいと思っている方

コールバックとは

Railsガイドより

オブジェクトのライフサイクルの特定の瞬間に呼び出されるメソッドのことです。
コールバックを使えば、Active Recordオブジェクトがデータベースで初期化・作成・保存・更新・削除・バリデーション・読み込みのたびに実行されるコードを記述できます。

詳細に知りたい方は以下を参照してください!

よく使用されるコールバックには以下のようなものがあります。

コールバックの種類説明
before_saveレコードが保存される前に実行
after_saveレコードが保存された後に実行
before_createレコードが作成される前に実行
after_createレコードが作成された後に実行
before_destroyレコードが削除される前に実行
after_destroyレコードが削除された後に実行

基本的な使用方法

コールバックを使用する際は、モデルクラス内で上記で紹介したようなコールバックを登録し、実行させたいメソッドを受け取ります。

以下のサンプルコードは、保存前に名前の余分な空白をなくす処理です。
保存時にデータを一貫した形式にしたい場合、このように記述する方法もあります。

user.rb
class User < ApplicationRecord
  before_save :normalize_name

  private

  def normalize_name
    self.name = name.strip
  end
end

このようにコールバックを登録することで、Userのレコードが保存されるたびにnormalize_nameメソッドが実行されます。
Userのレコードを保存するという処理を複数箇所で行っていた場合、本来であればその使用箇所全てに同じ記述をしなくてはいけません。
しかし、コールバックを登録しておけば他の箇所でその処理の記述をしなくても済みます。
それによりメソッドの記述量が減り、可読性をあげることができるのです!

コールバックの登録ではメソッド以外にも以下のようにブロックを受け取ることができます。
コールバックしたいロジックが短い時などはこちらを使用してみてもいいかもしれません。

user.rb
class User < ApplicationRecord
  before_save do
    self.name = name.strip
  end
end

例としてサンプルコードをもう一つあげます。

以下はユーザーを作成した後にそのユーザーに対してメールを送る処理です。

user.rb
class User < ApplicationRecord
  before_save :normalize_name

  private

  def normalize_name
    self.name = name.strip
  end
end

基本的にはこのような感じで使用していきます!

コールバックの応用

特定の条件下でのみコールバックを実行することも可能です。

以下はifを使用しユーザー作成後、emailが存在する場合にのみメール送信をします。
ifの逆であるunlessを使用することも可能です!

user.rb
class User < ApplicationRecord
  after_create :send_email, if: -> { email.present? }

  private

  def send_email
    UserMailer.welcome_email(self).deliver_now
  end
end

また、以下のようにif, unlessを併用してコールバック登録をすることも出来ます。

user.rb
class User < ApplicationRecord
  after_create :send_email, if: -> { email.present? }, unless: -> { status == guest }

  private

  def send_email
    UserMailer.welcome_email(self).deliver_now
  end
end

上記はユーザー作成時、emailが存在しなおかつ、statusguestでないユーザーに対してメール送信を行います。

コールバックのスキップ

スキップされてしまう場合

以下のメソッドを使用するとコールバックはスキップされます。
これはバリデーションも同様です!

  • delete
  • delete_all
  • update_column
  • update_all
  • upsert


スキップされるメソッドがあることを知らないと、なぜコールバックが実行されないのかで詰まってしまう可能性があります。
なので、コールバックがうまく実行されない場合は使用しているメソッドがスキップされるメソッドでないかを確認しましょう!

上記以外にもスキップされるメソッドがあるので詳しく知りたい方は、記事の上部に貼ってあるRailsガイドを参照してください。

あえてスキップしたい場合

通常はコールバックを行いたいが、例外的にスキップさせたい場合もあるかと思います。
そのような場合にはattr_accessorを使用する方法があります!

以下のようにattr_accessorで、コールバックをスキップするかどうかを管理するためのフラグを追加し、そのフラグの状態でコールバックの処理を実行するかどうか制御します。

user.rb
class User < ApplicationRecord
  attr_accessor :skip_normalize_name # 追加

  before_save :normalize_name, unless: -> { skip_normalize_name }

  private

  def normalize_name
    self.name = name.strip
  end
end

上記のように設定したらあとは以下のように使用するだけです!

通常時の保存の場合:

ターミナル
user = User.new(name: "  田中太郎")
user.save
puts user.name
# => "田中太郎"

コールバックをスキップしたい場合:

ターミナル
user = User.new(name: "  田中太郎")
user.skip_normalize_name = true
user.save
puts user.name
# => "  田中太郎"

このようにattr_accessorを使用したらコールバックの制御を簡単に行えます。
スキップ処理が必要な場合のみ適切に使用することが大切になります!

おわりに

今回はコールバックについて解説をしました!
コールバックは、モデルのライフサイクルに特定の処理を組み込む便利な機能ですが、使い方を誤るとコードが複雑化し、デバックが難しくなってしまいます。
本記事で紹介した、特定の条件下でのコールバック実行やスキップ機能をうまく活用し、必要な箇所にだけ適切にコールバックを取り入れることが大切になります!

ぜひ、この記事を参考に、プロジェクトでコールバックを効果的に活用してみてください!