diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb index bf3fe7ff04..c153ef4309 100644 --- a/activemodel/lib/active_model/validations/callbacks.rb +++ b/activemodel/lib/active_model/validations/callbacks.rb @@ -56,7 +56,8 @@ module ActiveModel options = args.last if options.is_a?(Hash) && options[:on] options[:if] = Array(options[:if]) - options[:if].unshift("self.validation_context == :#{options[:on]}") + options[:on] = Array(options[:on]) + options[:if].unshift("#{options[:on]}.include? self.validation_context") end set_callback(:validation, :before, *args, &block) end @@ -92,7 +93,10 @@ module ActiveModel options = args.extract_options! options[:prepend] = true options[:if] = Array(options[:if]) - options[:if].unshift("self.validation_context == :#{options[:on]}") if options[:on] + if options[:on] + options[:on] = Array(options[:on]) + options[:if].unshift("#{options[:on]}.include? self.validation_context") + end set_callback(:validation, :after, *(args << options), &block) end end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 04b67cdf3a..344ee6416d 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,9 @@ ## Rails 4.0.0 (unreleased) ## +* Allow before and after validations to take an array of lifecycle events + + *John Foley* + * Support for specifying transaction isolation level If your database supports setting the isolation level for a transaction, you can set diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb index deeef3a3fd..7457bafd4e 100644 --- a/activerecord/test/cases/callbacks_test.rb +++ b/activerecord/test/cases/callbacks_test.rb @@ -137,6 +137,32 @@ class OnCallbacksDeveloper < ActiveRecord::Base 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_#{self.validation_context}".to_sym + end + + def after_validation_on_create_and_update + history << "after_validation_on_#{self.validation_context}".to_sym + end + + def history + @history ||= [] + end +end + class CallbackCancellationDeveloper < ActiveRecord::Base self.table_name = 'developers' @@ -285,6 +311,17 @@ class CallbacksTest < ActiveRecord::TestCase ], 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 @@ -344,6 +381,18 @@ class CallbacksTest < ActiveRecord::TestCase ], 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 diff --git a/guides/source/active_record_validations_callbacks.md b/guides/source/active_record_validations_callbacks.md index e5957d8acb..f32c1050ce 100644 --- a/guides/source/active_record_validations_callbacks.md +++ b/guides/source/active_record_validations_callbacks.md @@ -995,6 +995,25 @@ class User < ActiveRecord::Base end ``` +Callbacks can also be registered to only fire on certain lifecycle events: + +class User < ActiveRecord::Base + before_validation :normalize_name, :on => :create + + # :on takes an array as well + after_validation :set_location, :on => [ :create, :update ] + + protected + def normalize_name + self.name = self.name.downcase.titleize + end + + def set_location + self.location = LocationService.query(self) + end +end + + It is considered good practice to declare callback methods as protected or private. If left public, they can be called from outside of the model and violate the principle of object encapsulation. Available Callbacks