diff options
Diffstat (limited to 'activerecord/test/cases/callbacks_test.rb')
-rw-r--r-- | activerecord/test/cases/callbacks_test.rb | 479 |
1 files changed, 479 insertions, 0 deletions
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb new file mode 100644 index 0000000000..3b283a3aa6 --- /dev/null +++ b/activerecord/test/cases/callbacks_test.rb @@ -0,0 +1,479 @@ +# frozen_string_literal: true + +require "cases/helper" +require "models/developer" +require "models/computer" + +class CallbackDeveloper < ActiveRecord::Base + self.table_name = "developers" + + class << self + def callback_proc(callback_method) + Proc.new { |model| model.history << [callback_method, :proc] } + end + + def define_callback_method(callback_method) + define_method(callback_method) do + history << [callback_method, :method] + end + send(callback_method, :"#{callback_method}") + 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 + + ActiveRecord::Callbacks::CALLBACKS.each do |callback_method| + next if callback_method.to_s.start_with?("around_") + define_callback_method(callback_method) + send(callback_method, callback_proc(callback_method)) + send(callback_method, callback_object(callback_method)) + send(callback_method) { |model| model.history << [callback_method, :block] } + end + + def history + @history ||= [] + end +end + +class CallbackDeveloperWithHaltedValidation < CallbackDeveloper + before_validation proc { |model| model.history << [:before_validation, :throwing_abort]; throw(:abort) } + before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] } +end + +class ParentDeveloper < ActiveRecord::Base + self.table_name = "developers" + attr_accessor :after_save_called + before_validation { |record| record.after_save_called = true } +end + +class ChildDeveloper < ParentDeveloper +end + +class ImmutableDeveloper < ActiveRecord::Base + self.table_name = "developers" + + validates_inclusion_of :salary, in: 50000..200000 + + before_save :cancel + before_destroy :cancel + + private + def cancel + false + end +end + +class DeveloperWithCanceledCallbacks < ActiveRecord::Base + self.table_name = "developers" + + validates_inclusion_of :salary, in: 50000..200000 + + before_save :cancel + before_destroy :cancel + + private + def cancel + throw(:abort) + end +end + +class OnCallbacksDeveloper < ActiveRecord::Base + self.table_name = "developers" + + before_validation { history << :before_validation } + before_validation(on: :create) { history << :before_validation_on_create } + before_validation(on: :update) { history << :before_validation_on_update } + + validate do + history << :validate + end + + after_validation { history << :after_validation } + after_validation(on: :create) { history << :after_validation_on_create } + after_validation(on: :update) { history << :after_validation_on_update } + + def history + @history ||= [] + end +end + +class ContextualCallbacksDeveloper < ActiveRecord::Base + self.table_name = "developers" + + before_validation { history << :before_validation } + before_validation :before_validation_on_create_and_update, on: [ :create, :update ] + + validate do + history << :validate + end + + after_validation { history << :after_validation } + after_validation :after_validation_on_create_and_update, on: [ :create, :update ] + + def before_validation_on_create_and_update + history << "before_validation_on_#{validation_context}".to_sym + end + + def after_validation_on_create_and_update + history << "after_validation_on_#{validation_context}".to_sym + end + + def history + @history ||= [] + end +end + +class CallbackHaltedDeveloper < ActiveRecord::Base + self.table_name = "developers" + + attr_reader :after_save_called, :after_create_called, :after_update_called, :after_destroy_called + attr_accessor :cancel_before_save, :cancel_before_create, :cancel_before_update, :cancel_before_destroy + + before_save { throw(:abort) if defined?(@cancel_before_save) } + before_create { throw(:abort) if @cancel_before_create } + before_update { throw(:abort) if @cancel_before_update } + before_destroy { throw(:abort) if @cancel_before_destroy } + + after_save { @after_save_called = true } + after_update { @after_update_called = true } + after_create { @after_create_called = true } + after_destroy { @after_destroy_called = true } +end + +class CallbacksTest < ActiveRecord::TestCase + fixtures :developers + + def test_initialize + david = CallbackDeveloper.new + assert_equal [ + [ :after_initialize, :method ], + [ :after_initialize, :proc ], + [ :after_initialize, :object ], + [ :after_initialize, :block ], + ], david.history + end + + def test_find + david = CallbackDeveloper.find(1) + assert_equal [ + [ :after_find, :method ], + [ :after_find, :proc ], + [ :after_find, :object ], + [ :after_find, :block ], + [ :after_initialize, :method ], + [ :after_initialize, :proc ], + [ :after_initialize, :object ], + [ :after_initialize, :block ], + ], david.history + end + + def test_new_valid? + david = CallbackDeveloper.new + david.valid? + assert_equal [ + [ :after_initialize, :method ], + [ :after_initialize, :proc ], + [ :after_initialize, :object ], + [ :after_initialize, :block ], + [ :before_validation, :method ], + [ :before_validation, :proc ], + [ :before_validation, :object ], + [ :before_validation, :block ], + [ :after_validation, :method ], + [ :after_validation, :proc ], + [ :after_validation, :object ], + [ :after_validation, :block ], + ], david.history + end + + def test_existing_valid? + david = CallbackDeveloper.find(1) + david.valid? + assert_equal [ + [ :after_find, :method ], + [ :after_find, :proc ], + [ :after_find, :object ], + [ :after_find, :block ], + [ :after_initialize, :method ], + [ :after_initialize, :proc ], + [ :after_initialize, :object ], + [ :after_initialize, :block ], + [ :before_validation, :method ], + [ :before_validation, :proc ], + [ :before_validation, :object ], + [ :before_validation, :block ], + [ :after_validation, :method ], + [ :after_validation, :proc ], + [ :after_validation, :object ], + [ :after_validation, :block ], + ], david.history + end + + def test_create + david = CallbackDeveloper.create("name" => "David", "salary" => 1000000) + assert_equal [ + [ :after_initialize, :method ], + [ :after_initialize, :proc ], + [ :after_initialize, :object ], + [ :after_initialize, :block ], + [ :before_validation, :method ], + [ :before_validation, :proc ], + [ :before_validation, :object ], + [ :before_validation, :block ], + [ :after_validation, :method ], + [ :after_validation, :proc ], + [ :after_validation, :object ], + [ :after_validation, :block ], + [ :before_save, :method ], + [ :before_save, :proc ], + [ :before_save, :object ], + [ :before_save, :block ], + [ :before_create, :method ], + [ :before_create, :proc ], + [ :before_create, :object ], + [ :before_create, :block ], + [ :after_create, :method ], + [ :after_create, :proc ], + [ :after_create, :object ], + [ :after_create, :block ], + [ :after_save, :method ], + [ :after_save, :proc ], + [ :after_save, :object ], + [ :after_save, :block ], + [ :after_commit, :block ], + [ :after_commit, :object ], + [ :after_commit, :proc ], + [ :after_commit, :method ] + ], david.history + end + + def test_validate_on_create + david = OnCallbacksDeveloper.create("name" => "David", "salary" => 1000000) + assert_equal [ + :before_validation, + :before_validation_on_create, + :validate, + :after_validation, + :after_validation_on_create + ], david.history + end + + def test_validate_on_contextual_create + david = ContextualCallbacksDeveloper.create("name" => "David", "salary" => 1000000) + assert_equal [ + :before_validation, + :before_validation_on_create, + :validate, + :after_validation, + :after_validation_on_create + ], david.history + end + + def test_update + david = CallbackDeveloper.find(1) + david.save + assert_equal [ + [ :after_find, :method ], + [ :after_find, :proc ], + [ :after_find, :object ], + [ :after_find, :block ], + [ :after_initialize, :method ], + [ :after_initialize, :proc ], + [ :after_initialize, :object ], + [ :after_initialize, :block ], + [ :before_validation, :method ], + [ :before_validation, :proc ], + [ :before_validation, :object ], + [ :before_validation, :block ], + [ :after_validation, :method ], + [ :after_validation, :proc ], + [ :after_validation, :object ], + [ :after_validation, :block ], + [ :before_save, :method ], + [ :before_save, :proc ], + [ :before_save, :object ], + [ :before_save, :block ], + [ :before_update, :method ], + [ :before_update, :proc ], + [ :before_update, :object ], + [ :before_update, :block ], + [ :after_update, :method ], + [ :after_update, :proc ], + [ :after_update, :object ], + [ :after_update, :block ], + [ :after_save, :method ], + [ :after_save, :proc ], + [ :after_save, :object ], + [ :after_save, :block ], + [ :after_commit, :block ], + [ :after_commit, :object ], + [ :after_commit, :proc ], + [ :after_commit, :method ] + ], david.history + end + + def test_validate_on_update + david = OnCallbacksDeveloper.find(1) + david.save + assert_equal [ + :before_validation, + :before_validation_on_update, + :validate, + :after_validation, + :after_validation_on_update + ], david.history + end + + def test_validate_on_contextual_update + david = ContextualCallbacksDeveloper.find(1) + david.save + assert_equal [ + :before_validation, + :before_validation_on_update, + :validate, + :after_validation, + :after_validation_on_update + ], david.history + end + + def test_destroy + david = CallbackDeveloper.find(1) + david.destroy + assert_equal [ + [ :after_find, :method ], + [ :after_find, :proc ], + [ :after_find, :object ], + [ :after_find, :block ], + [ :after_initialize, :method ], + [ :after_initialize, :proc ], + [ :after_initialize, :object ], + [ :after_initialize, :block ], + [ :before_destroy, :method ], + [ :before_destroy, :proc ], + [ :before_destroy, :object ], + [ :before_destroy, :block ], + [ :after_destroy, :method ], + [ :after_destroy, :proc ], + [ :after_destroy, :object ], + [ :after_destroy, :block ], + [ :after_commit, :block ], + [ :after_commit, :object ], + [ :after_commit, :proc ], + [ :after_commit, :method ] + ], david.history + end + + def test_delete + david = CallbackDeveloper.find(1) + CallbackDeveloper.delete(david.id) + assert_equal [ + [ :after_find, :method ], + [ :after_find, :proc ], + [ :after_find, :object ], + [ :after_find, :block ], + [ :after_initialize, :method ], + [ :after_initialize, :proc ], + [ :after_initialize, :object ], + [ :after_initialize, :block ], + ], david.history + end + + def assert_save_callbacks_not_called(someone) + assert !someone.after_save_called + assert !someone.after_create_called + assert !someone.after_update_called + end + private :assert_save_callbacks_not_called + + def test_before_create_throwing_abort + someone = CallbackHaltedDeveloper.new + someone.cancel_before_create = true + assert_predicate someone, :valid? + assert !someone.save + assert_save_callbacks_not_called(someone) + end + + def test_before_save_throwing_abort + david = DeveloperWithCanceledCallbacks.find(1) + assert_predicate david, :valid? + assert !david.save + exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! } + assert_equal david, exc.record + + david = DeveloperWithCanceledCallbacks.find(1) + david.salary = 10_000_000 + assert_not_predicate david, :valid? + assert !david.save + assert_raise(ActiveRecord::RecordInvalid) { david.save! } + + someone = CallbackHaltedDeveloper.find(1) + someone.cancel_before_save = true + assert_predicate someone, :valid? + assert !someone.save + assert_save_callbacks_not_called(someone) + end + + def test_before_update_throwing_abort + someone = CallbackHaltedDeveloper.find(1) + someone.cancel_before_update = true + assert_predicate someone, :valid? + assert !someone.save + assert_save_callbacks_not_called(someone) + end + + def test_before_destroy_throwing_abort + david = DeveloperWithCanceledCallbacks.find(1) + assert !david.destroy + exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! } + assert_equal david, exc.record + assert_not_nil ImmutableDeveloper.find_by_id(1) + + someone = CallbackHaltedDeveloper.find(1) + someone.cancel_before_destroy = true + assert !someone.destroy + assert_raise(ActiveRecord::RecordNotDestroyed) { someone.destroy! } + assert !someone.after_destroy_called + end + + def test_callback_throwing_abort + david = CallbackDeveloperWithHaltedValidation.find(1) + david.save + assert_equal [ + [ :after_find, :method ], + [ :after_find, :proc ], + [ :after_find, :object ], + [ :after_find, :block ], + [ :after_initialize, :method ], + [ :after_initialize, :proc ], + [ :after_initialize, :object ], + [ :after_initialize, :block ], + [ :before_validation, :method ], + [ :before_validation, :proc ], + [ :before_validation, :object ], + [ :before_validation, :block ], + [ :before_validation, :throwing_abort ], + [ :after_rollback, :block ], + [ :after_rollback, :object ], + [ :after_rollback, :proc ], + [ :after_rollback, :method ], + ], david.history + end + + def test_inheritance_of_callbacks + parent = ParentDeveloper.new + assert !parent.after_save_called + parent.save + assert parent.after_save_called + + child = ChildDeveloper.new + assert !child.after_save_called + child.save + assert child.after_save_called + end +end |