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 のいいところは、宣言的にコールバックを定義できることと、派生クラスにもコールバックチェインを有効にできること。