require 'abstract_unit' class Record include ActiveSupport::Callbacks define_callbacks :before_save, :after_save class << self def callback_symbol(callback_method) "#{callback_method}_method".tap do |method_name| define_method(method_name) do history << [callback_method, :symbol] end end end def callback_string(callback_method) "history << [#{callback_method.to_sym.inspect}, :string]" end def callback_proc(callback_method) Proc.new { |model| model.history << [callback_method, :proc] } end def callback_object(callback_method) klass = Class.new klass.send(:define_method, callback_method) do |model| model.history << [callback_method, :object] end klass.new end end def history @history ||= [] end end class Person < Record [:before_save, :after_save].each do |callback_method| callback_method_sym = callback_method.to_sym send(callback_method, callback_symbol(callback_method_sym)) send(callback_method, callback_string(callback_method_sym)) send(callback_method, callback_proc(callback_method_sym)) send(callback_method, callback_object(callback_method_sym)) send(callback_method) { |model| model.history << [callback_method_sym, :block] } end def save run_callbacks(:before_save) run_callbacks(:after_save) end end class ConditionalPerson < Record # proc before_save Proc.new { |r| r.history << [:before_save, :proc] }, :if => Proc.new { |r| true } before_save Proc.new { |r| r.history << "b00m" }, :if => Proc.new { |r| false } before_save Proc.new { |r| r.history << [:before_save, :proc] }, :unless => Proc.new { |r| false } before_save Proc.new { |r| r.history << "b00m" }, :unless => Proc.new { |r| true } # symbol before_save Proc.new { |r| r.history << [:before_save, :symbol] }, :if => :yes before_save Proc.new { |r| r.history << "b00m" }, :if => :no before_save Proc.new { |r| r.history << [:before_save, :symbol] }, :unless => :no before_save Proc.new { |r| r.history << "b00m" }, :unless => :yes # string before_save Proc.new { |r| r.history << [:before_save, :string] }, :if => 'yes' before_save Proc.new { |r| r.history << "b00m" }, :if => 'no' before_save Proc.new { |r| r.history << [:before_save, :string] }, :unless => 'no' before_save Proc.new { |r| r.history << "b00m" }, :unless => 'yes' # Array with conditions before_save Proc.new { |r| r.history << [:before_save, :symbol_array] }, :if => [:yes, :other_yes] before_save Proc.new { |r| r.history << "b00m" }, :if => [:yes, :no] before_save Proc.new { |r| r.history << [:before_save, :symbol_array] }, :unless => [:no, :other_no] before_save Proc.new { |r| r.history << "b00m" }, :unless => [:yes, :no] # Combined if and unless before_save Proc.new { |r| r.history << [:before_save, :combined_symbol] }, :if => :yes, :unless => :no before_save Proc.new { |r| r.history << "b00m" }, :if => :yes, :unless => :yes # Array with different types of conditions before_save Proc.new { |r| r.history << [:before_save, :symbol_proc_string_array] }, :if => [:yes, Proc.new { |r| true }, 'yes'] before_save Proc.new { |r| r.history << "b00m" }, :if => [:yes, Proc.new { |r| true }, 'no'] # Array with different types of conditions comibned if and unless before_save Proc.new { |r| r.history << [:before_save, :combined_symbol_proc_string_array] }, :if => [:yes, Proc.new { |r| true }, 'yes'], :unless => [:no, 'no'] before_save Proc.new { |r| r.history << "b00m" }, :if => [:yes, Proc.new { |r| true }, 'no'], :unless => [:no, 'no'] def yes; true; end def other_yes; true; end def no; false; end def other_no; false; end def save run_callbacks(:before_save) run_callbacks(:after_save) end end class CallbacksTest < Test::Unit::TestCase def test_save_person person = Person.new assert_equal [], person.history person.save assert_equal [ [:before_save, :symbol], [:before_save, :string], [:before_save, :proc], [:before_save, :object], [:before_save, :block], [:after_save, :symbol], [:after_save, :string], [:after_save, :proc], [:after_save, :object], [:after_save, :block] ], person.history end end class ConditionalCallbackTest < Test::Unit::TestCase def test_save_conditional_person person = ConditionalPerson.new person.save assert_equal [ [:before_save, :proc], [:before_save, :proc], [:before_save, :symbol], [:before_save, :symbol], [:before_save, :string], [:before_save, :string], [:before_save, :symbol_array], [:before_save, :symbol_array], [:before_save, :combined_symbol], [:before_save, :symbol_proc_string_array], [:before_save, :combined_symbol_proc_string_array] ], person.history end end class CallbackTest < Test::Unit::TestCase include ActiveSupport::Callbacks def test_eql callback = Callback.new(:before, :save, :identifier => :lifesaver) assert callback.eql?(Callback.new(:before, :save, :identifier => :lifesaver)) assert callback.eql?(Callback.new(:before, :save)) assert callback.eql?(:lifesaver) assert callback.eql?(:save) assert !callback.eql?(Callback.new(:before, :destroy)) assert !callback.eql?(:destroy) end def test_dup a = Callback.new(:before, :save) assert_equal({}, a.options) b = a.dup b.options[:unless] = :pigs_fly assert_equal({:unless => :pigs_fly}, b.options) assert_equal({}, a.options) end end class CallbackChainTest < Test::Unit::TestCase include ActiveSupport::Callbacks def setup @chain = CallbackChain.build(:make, :bacon, :lettuce, :tomato) end def test_build assert_equal 3, @chain.size assert_equal [:bacon, :lettuce, :tomato], @chain.map(&:method) end def test_find assert_equal :bacon, @chain.find(:bacon).method end def test_replace_or_append assert_equal [:bacon, :lettuce, :tomato], (@chain.replace_or_append!(Callback.new(:make, :bacon))).map(&:method) assert_equal [:bacon, :lettuce, :tomato, :turkey], (@chain.replace_or_append!(Callback.new(:make, :turkey))).map(&:method) assert_equal [:bacon, :lettuce, :tomato, :turkey, :mayo], (@chain.replace_or_append!(Callback.new(:make, :mayo))).map(&:method) end def test_delete assert_equal [:bacon, :lettuce, :tomato], @chain.map(&:method) @chain.delete(:bacon) assert_equal [:lettuce, :tomato], @chain.map(&:method) end end