RailsのActiveRecordには、特定のタイミングでコールバックを実行する仕組みがあります。とはいえ、複雑な処理を行おうとすると少し悩むことになるかもしれません。
コールバックとは
詳細はRails Guideに譲りますが、before_save
/after_save
など、ActiveRecordが特定の動作をした段階で動くルーチンを仕掛けることができます。
コールバックとして設定できるもの
コールバックはbefore_save 動作
というような形で定義しますが、この「動作」に指定できるものも何種類かあります。なお、Railsのコールバックは内部的にActiveSupport::Callbacks
(詳細)を使っているので、指定できるものはこれに準じます。
ブロック、proc、lambda
「ルーチン」でいちばんイメージしやすいものですが、これらはinstance_eval
でそのインスタンスのコンテキストで実行されます1。比較的わかりやすいとは思いますが、複雑な処理を書くには向かない書式です。
- メリット: ごくかんたんな処理を行う場合、シンプルに書ける
- デメリット:複雑なものを書くには向かない
before_validation do
# 中身は省略
end
シンボル
シンボルを渡した場合、インスタンスに対してシンボルの名前のメソッドが実行されます。
- メリット:モデルのインスタンス変数も使える
- デメリット:コールバック専用のコードだった場合、インスタンスにメソッドとして持たせるのはじゃまになる
before_validation :convert_name
その他のオブジェクト
その他のオブジェクトを渡すと、そのオブジェクトの、コールバック名と同じメソッドを、モデルインスタンスを引数にして呼び出します。コールバックを別なところへ分離できて便利です。
コールバックオブジェクトのインスタンスについて
この形で指定する場合、after_save SomeObject.new
のようにも書けますが、クラス内に書くことからもわかるように、インスタンスはモデル全体で共通ですので、引数指定でインスタンスを作り分ける、というような状況でなければわざわざインスタンスを作る意味もそこまで大きくありません。そのメソッドをモジュールメソッドとして実装して、Moduleオブジェクトそのものを指定する、という手段もありです。
インスタンス変数を引き回したい!
ただ、モジュールメソッドで実装してしまうと、CallBackModule.after_xxx(model)
のような形で呼ばれることとなります。下請けメソッドが必要な規模のコールバックを書こうとすれば、モデル内の値を参照するのに、いちいちmodel
を引き回す必要が出てきてしまいます。
これでは不便ですし、モデル以外にインスタンス変数やメソッドを持たせても便利ですので、「外から見ればクラスメソッド、内部処理はインスタンスを作ってそこで処理」というような形で書けば、使う方も中身も比較的わかりやすくなります。
class SomeCallback
# インスタンス変数の準備
def initialize(model)
@model = model
@some_children = @model.some_children
end
# インスタンス変数を使って作業
def all_children?
@some_children.all? do
# 略
end
end
# メインルーチン
def after_save
some_value = all_children ? @model.some_value : 5
# 後略
end
# フロントエンド用メソッド
def self.after_save(model)
new(model).after_save
end
# 外部から直接newはしない
private_class_method :new
end
# モデル側
after_save SomeCallback
-
文字列も
instance_eval
されますが、これは非推奨とのことです。 ↩