alias_method_chain と define_callbacks
まだまだActiveRecordの便利さには遠いのでコールバック機能追加。
ActiveRecord とほぼ互換なコールバック関数を定義できるようにした。Rails 2.1 のソースを参考に、ActiveSupport::Callbacks を活用。alias_method_chain と define_callbacks の組み合わせだけでコールバック機能を簡単に既存クラスに追加できることができて驚愕。以下実例に基づくメモ。
まず alias_method_chain。これは、定義済みのメソッドを拡張する場合に使う。
module ExtensionName def self.included(base) base.send :alias_method_chain, :method_name, :ext end end
こうすると、method_name に対する拡張を method_name_with_ext というメソッドで定義できるようになる。さらに既存のメソッドは method_name_without_ext という名前に変わる。そこで、
module ExtensionName def self.included(base) base.send :alias_method_chain, :method_name, :ext end def method_name_with_ext # 拡張を書く # ...(snip)... # 既存のメソッドを呼び出す method_name_without_ext end end
で、実際に method_name という名前のメソッドを定義しているクラスにExtensionNameを include すると、method_name 呼び出し時には method_name_with_ext が呼ばれるようになる。したがって、コールバック関係は次のように定義できる。
module Callbacks def self.included(base) base.send :alias_method_chain, :save, :callbacks # コールバックメソッドを宣言する # 次に解説 end # コールバック付き save メソッドの実装 def save_with_callbacks # before_save を呼び出す # 次に解説 # オリジナルのsaveを呼び出す save_without_callbacks # after_save を呼び出す # 次に解説 end end
コールバックは、ActiveSupport::Callbacks をincludeすると簡単に定義できるようになる。
まず、クラスメソッドのdefine_callbacks(*callback_names) を呼び出してコールバックが存在することを定義する。
module Callbacks def self.included(base) base.send :alias_method_chain, :save, :callbacks # コールバックメソッドを宣言する base.send :include, ActiveSupport::Callbacks base.send :define_callbacks, :before_save, :after_save end #...(snip)...
これでbefore_save, after_saveをインスタンスメソッド内でrun_callbacks(callback_name)を使って呼び出せるようになる。
module Callbacks def self.included(base) base.send :alias_method_chain, :save, :callbacks # コールバックメソッドを宣言する base.send :include, ActiveSupport::Callbacks base.send :define_callbacks, :before_save, :after_save end # コールバック付き save メソッドの実装 def save_with_callbacks # before_save を呼び出す return false if run_callbacks(:before_save) == false # オリジナルのsaveを呼び出す result = save_without_callbacks # after_save を呼び出す run_callbacks(:after_save) result end end
これで完了。これは単なる拡張モジュールなので、次のようにして使う。
class PersistableClass def save # 保存するクラス end end # コールバック機能を拡張する PersistableClass.send :include, Callbacks # PersistableClass から派生クラス内でコールバックを定義する class MyPersistasbleClass < PersistableClass before_save :hoge private def hoge puts "before_save callbacked" end end
ActiveSupport::Callbacks のいいところは、宣言的にコールバックを定義できることと、派生クラスにもコールバックチェインを有効にできること。