callbacksの簡易版を実装
いやぁ、Ruby on Rails Guilds:Get Startedをやってみて、validationまわりがどうなっているのか気になったので読んでみました。validatesはActive SupportのCallbacksモジュールのset_callbackでValidatorオブジェクトがvalidateをコールするのをvalid?を実行するまで遅延させていたので、まずはそっちの方を読みながら簡易版を作りました。
#callbacks.rb class Class def class_attribute name class_eval <<-RUBY, __FILE__, __LINE__ + 1 def self.#{name}() nil end def self.#{name}=(val) singleton_class.class_eval { undef_method :#{name} define_method(:#{name}) { val } } val end RUBY end end module MyRails module Concern def append_features(base) base.extend const_get("ClassMethods") super end end module Callbacks extend Concern class Callback @@_callback_sequence = 0 attr_reader :filter def initialize(callback_name, callback_obj, callbacks_class) @filter = "_callback_#{callback_name}_#{next_id}" callbacks_class.send(:define_method, "#{@filter}_object") { callback_obj } callbacks_class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{@filter} #{@filter}_object.#{callback_name} end RUBY_EVAL end def next_id @@_callback_sequence += 1 end end def run_callbacks(callback_name) self.class.send("_#{callback_name}_callbacks").each do|callback| send(callback.filter) end end module ClassMethods def define_callback(callback_name) attr_name = "_#{callback_name}_callbacks" class_attribute attr_name send("#{attr_name}=", Array.new) end def set_callback(callback_name, callback_obj) send("_#{callback_name}_callbacks").push(Callback.new(callback_name, callback_obj, self)) end end end end
class_evalは文字列も受け付けるんですね。テストはこんな感じ。
require './callbacks' require 'test/unit' require 'stringio' class C class_attribute :name end class CallbackObj def callback_method puts "1" end end class CallbackObj2 def callback_method puts "2" end end class Callbacks include MyRails::Callbacks define_callback :callback_method set_callback(:callback_method, CallbackObj.new) set_callback(:callback_method, CallbackObj2.new) end class MyRailsTests < Test::Unit::TestCase def test_class_attribute name = "nabeyang" assert_equal name, (C.name = name) assert_equal name, C.name end def test_callbacks_set_callback assert_equal 2, Callbacks._callback_method_callbacks.size end def test_callbacks_run_callback obj = Callbacks.new s = StringIO.new $stdout = s obj.run_callbacks(:callback_method) $stdout = STDOUT assert_equal "1\n2\n", s.string end end