Skip to content

Commit

Permalink
Merge pull request #6936 from jfoley/callbacks
Browse files Browse the repository at this point in the history
Fix collisions with before and after validation callbacks.
  • Loading branch information
spastorino committed Sep 24, 2012
2 parents dae474e + f31ea4d commit 1dbe4ba
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 2 deletions.
8 changes: 6 additions & 2 deletions activemodel/lib/active_model/validations/callbacks.rb
Expand Up @@ -56,7 +56,8 @@ def before_validation(*args, &block)
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
Expand Down Expand Up @@ -92,7 +93,10 @@ def after_validation(*args, &block)
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
Expand Down
4 changes: 4 additions & 0 deletions 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
Expand Down
49 changes: 49 additions & 0 deletions activerecord/test/cases/callbacks_test.rb
Expand Up @@ -137,6 +137,32 @@ def 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_#{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'

Expand Down Expand Up @@ -285,6 +311,17 @@ def test_validate_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
Expand Down Expand Up @@ -344,6 +381,18 @@ def test_validate_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
Expand Down
19 changes: 19 additions & 0 deletions guides/source/active_record_validations_callbacks.md
Expand Up @@ -995,6 +995,25 @@ class User < ActiveRecord::Base
end
```

Callbacks can also be registered to only fire on certain lifecycle events:
<ruby>
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
</ruby>

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
Expand Down

0 comments on commit 1dbe4ba

Please sign in to comment.