mirror of
https://github.com/github/rails.git
synced 2026-04-26 03:00:59 -04:00
Merge commit 'rails/master'
This commit is contained in:
@@ -10,7 +10,7 @@ module AbstractController
|
||||
include ActiveSupport::NewCallbacks
|
||||
|
||||
included do
|
||||
define_callbacks :process_action, "response_body"
|
||||
define_callbacks :process_action, :terminator => "response_body"
|
||||
end
|
||||
|
||||
# Override AbstractController::Base's process_action to run the
|
||||
|
||||
@@ -6,10 +6,10 @@ require 'active_support/callbacks'
|
||||
module ActiveModel
|
||||
module Validations
|
||||
extend ActiveSupport::Concern
|
||||
include ActiveSupport::Callbacks
|
||||
include ActiveSupport::NewCallbacks
|
||||
|
||||
included do
|
||||
define_callbacks :validate
|
||||
define_callbacks :validate, :scope => :name
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
@@ -64,7 +64,7 @@ module ActiveModel
|
||||
attrs = attrs.flatten
|
||||
|
||||
# Declare the validation.
|
||||
send(validation_method(options[:on]), options) do |record|
|
||||
validate options do |record|
|
||||
attrs.each do |attr|
|
||||
value = record.send(:read_attribute_for_validation, attr)
|
||||
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
|
||||
@@ -73,10 +73,14 @@ module ActiveModel
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def validation_method(on)
|
||||
:validate
|
||||
def validate(*args, &block)
|
||||
options = args.last
|
||||
if options.is_a?(Hash) && options.key?(:on)
|
||||
options[:if] = Array(options[:if])
|
||||
options[:if] << "@_on_validate == :#{options[:on]}"
|
||||
end
|
||||
set_callback(:validate, *args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the Errors object that holds all information about attribute error messages.
|
||||
@@ -87,7 +91,7 @@ module ActiveModel
|
||||
# Runs all the specified validations and returns true if no errors were added otherwise false.
|
||||
def valid?
|
||||
errors.clear
|
||||
run_callbacks(:validate)
|
||||
_run_validate_callbacks
|
||||
errors.empty?
|
||||
end
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ module ActiveModel
|
||||
|
||||
# can't use validates_each here, because it cannot cope with nonexistent attributes,
|
||||
# while errors.add_on_empty can
|
||||
send(validation_method(configuration[:on]), configuration) do |record|
|
||||
validate configuration do |record|
|
||||
record.errors.add_on_blank(attr_names, configuration[:message])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -51,7 +51,7 @@ module ActiveModel
|
||||
def validates_with(*args)
|
||||
configuration = args.extract_options!
|
||||
|
||||
send(validation_method(configuration[:on]), configuration) do |record|
|
||||
validate configuration do |record|
|
||||
args.each do |klass|
|
||||
klass.new(record, configuration.except(:on, :if, :unless)).validate
|
||||
end
|
||||
|
||||
@@ -2,44 +2,34 @@ module ActiveModel
|
||||
module ValidationsRepairHelper
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
module Toolbox
|
||||
def self.record_validations(*model_classes)
|
||||
model_classes.inject({}) do |repair, klass|
|
||||
repair[klass] ||= {}
|
||||
[:validate, :validate_on_create, :validate_on_update].each do |callback|
|
||||
ivar = "@#{callback.to_s}_callbacks"
|
||||
the_callback = klass.instance_variable_get(ivar) if klass.instance_variable_defined?(ivar)
|
||||
repair[klass][callback] = (the_callback.nil? ? nil : the_callback.dup)
|
||||
end
|
||||
repair
|
||||
end
|
||||
end
|
||||
|
||||
def self.reset_validations(recorded)
|
||||
recorded.each do |klass, repairs|
|
||||
[:validate, :validate_on_create, :validate_on_update].each do |callback|
|
||||
klass.instance_variable_set("@#{callback.to_s}_callbacks", repairs[callback])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def repair_validations(*model_classes)
|
||||
setup do
|
||||
@validation_repairs = Toolbox.record_validations(*model_classes)
|
||||
@_stored_callbacks = {}
|
||||
model_classes.each do |k|
|
||||
@_stored_callbacks[k] = k._validate_callbacks.dup
|
||||
end
|
||||
end
|
||||
teardown do
|
||||
Toolbox.reset_validations(@validation_repairs)
|
||||
model_classes.each do |k|
|
||||
k._validate_callbacks = @_stored_callbacks[k]
|
||||
k.__update_callbacks(:validate)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def repair_validations(*model_classes, &block)
|
||||
validation_repairs = Toolbox.record_validations(*model_classes)
|
||||
@__stored_callbacks = {}
|
||||
model_classes.each do |k|
|
||||
@__stored_callbacks[k] = k._validate_callbacks.dup
|
||||
end
|
||||
return block.call
|
||||
ensure
|
||||
Toolbox.reset_validations(validation_repairs)
|
||||
model_classes.each do |k|
|
||||
k._validate_callbacks = @__stored_callbacks[k]
|
||||
k.__update_callbacks(:validate)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,7 +5,7 @@ require 'models/person'
|
||||
|
||||
class I18nGenerateMessageValidationTest < Test::Unit::TestCase
|
||||
def setup
|
||||
reset_callbacks Person
|
||||
Person.reset_callbacks(:validate)
|
||||
@person = Person.new
|
||||
|
||||
@old_load_path, @old_backend = I18n.load_path, I18n.backend
|
||||
@@ -45,12 +45,6 @@ class I18nGenerateMessageValidationTest < Test::Unit::TestCase
|
||||
I18n.backend = @old_backend
|
||||
end
|
||||
|
||||
def reset_callbacks(*models)
|
||||
models.each do |model|
|
||||
model.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||
end
|
||||
end
|
||||
|
||||
# validates_inclusion_of: generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value)
|
||||
def test_generate_message_inclusion_with_default_message
|
||||
assert_equal 'is not included in the list', @person.errors.generate_message(:title, :inclusion, :default => nil, :value => 'title')
|
||||
|
||||
@@ -7,8 +7,7 @@ class I18nValidationTest < ActiveModel::TestCase
|
||||
include ActiveModel::TestsDatabase
|
||||
|
||||
def setup
|
||||
reset_callbacks Person
|
||||
|
||||
Person.reset_callbacks(:validate)
|
||||
@person = Person.new
|
||||
|
||||
@old_load_path, @old_backend = I18n.load_path, I18n.backend
|
||||
@@ -18,17 +17,11 @@ class I18nValidationTest < ActiveModel::TestCase
|
||||
end
|
||||
|
||||
def teardown
|
||||
reset_callbacks Person
|
||||
Person.reset_callbacks(:validate)
|
||||
I18n.load_path.replace @old_load_path
|
||||
I18n.backend = @old_backend
|
||||
end
|
||||
|
||||
def reset_callbacks(*models)
|
||||
models.each do |model|
|
||||
model.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||
end
|
||||
end
|
||||
|
||||
def test_percent_s_interpolation_syntax_in_error_messages_was_deprecated
|
||||
assert_not_deprecated do
|
||||
default = "%s interpolation syntax was deprecated"
|
||||
@@ -532,4 +525,4 @@ class I18nValidationTest < ActiveModel::TestCase
|
||||
assert_equal ["I am a custom error"], @person.errors[:title]
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -121,8 +121,8 @@ class ValidationsTest < ActiveModel::TestCase
|
||||
end
|
||||
|
||||
def test_invalid_validator
|
||||
Topic.validate 3
|
||||
assert_raise(ArgumentError) { t = Topic.create }
|
||||
Topic.validate :i_dont_exist
|
||||
assert_raise(NameError) { t = Topic.create }
|
||||
end
|
||||
|
||||
def test_errors_to_xml
|
||||
@@ -189,4 +189,4 @@ class ValidationsTest < ActiveModel::TestCase
|
||||
all_errors = t.errors.to_a
|
||||
assert_deprecated { assert_equal all_errors, t.errors.each_full{|err| err} }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,11 +2,11 @@ require 'models/topic'
|
||||
|
||||
class Reply < Topic
|
||||
validate :errors_on_empty_content
|
||||
validate_on_create :title_is_wrong_create
|
||||
validate :title_is_wrong_create, :on => :create
|
||||
|
||||
validate :check_empty_title
|
||||
validate_on_create :check_content_mismatch
|
||||
validate_on_update :check_wrong_update
|
||||
validate :check_content_mismatch, :on => :create
|
||||
validate :check_wrong_update, :on => :update
|
||||
|
||||
attr_accessible :title, :author_name, :author_email_address, :written_on, :content, :last_read
|
||||
|
||||
|
||||
@@ -1491,24 +1491,43 @@ module ActiveRecord
|
||||
end
|
||||
before_destroy method_name
|
||||
when :delete_all
|
||||
module_eval %Q{
|
||||
before_destroy do |record| # before_destroy do |record|
|
||||
delete_all_has_many_dependencies(record, # delete_all_has_many_dependencies(record,
|
||||
"#{reflection.name}", # "posts",
|
||||
#{reflection.class_name}, # Post,
|
||||
%@#{dependent_conditions}@) # %@...@) # this is a string literal like %(...)
|
||||
end # end
|
||||
}
|
||||
# before_destroy do |record|
|
||||
# self.class.send(:delete_all_has_many_dependencies,
|
||||
# record,
|
||||
# "posts",
|
||||
# Post,
|
||||
# %@...@) # this is a string literal like %(...)
|
||||
# end
|
||||
# end
|
||||
module_eval <<-CALLBACK
|
||||
before_destroy do |record|
|
||||
self.class.send(:delete_all_has_many_dependencies,
|
||||
record,
|
||||
"#{reflection.name}",
|
||||
#{reflection.class_name},
|
||||
%@#{dependent_conditions}@)
|
||||
end
|
||||
CALLBACK
|
||||
when :nullify
|
||||
module_eval %Q{
|
||||
before_destroy do |record| # before_destroy do |record|
|
||||
nullify_has_many_dependencies(record, # nullify_has_many_dependencies(record,
|
||||
"#{reflection.name}", # "posts",
|
||||
#{reflection.class_name}, # Post,
|
||||
"#{reflection.primary_key_name}", # "user_id",
|
||||
%@#{dependent_conditions}@) # %@...@) # this is a string literal like %(...)
|
||||
end # end
|
||||
}
|
||||
# before_destroy do |record|
|
||||
# self.class.send(:nullify_has_many_dependencies,
|
||||
# record,
|
||||
# "posts",
|
||||
# Post,
|
||||
# "user_id",
|
||||
# %@...@) # this is a string literal like %(...)
|
||||
# end
|
||||
# end
|
||||
module_eval <<-CALLBACK
|
||||
before_destroy do |record|
|
||||
self.class.send(:nullify_has_many_dependencies,
|
||||
record,
|
||||
"#{reflection.name}",
|
||||
#{reflection.class_name},
|
||||
"#{reflection.primary_key_name}",
|
||||
%@#{dependent_conditions}@)
|
||||
end
|
||||
CALLBACK
|
||||
else
|
||||
raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, or :nullify (#{reflection.options[:dependent].inspect})"
|
||||
end
|
||||
|
||||
@@ -1691,13 +1691,8 @@ module ActiveRecord #:nodoc:
|
||||
object.instance_variable_set("@attributes", record)
|
||||
object.instance_variable_set("@attributes_cache", Hash.new)
|
||||
|
||||
if object.respond_to_without_attributes?(:after_find)
|
||||
object.send(:callback, :after_find)
|
||||
end
|
||||
|
||||
if object.respond_to_without_attributes?(:after_initialize)
|
||||
object.send(:callback, :after_initialize)
|
||||
end
|
||||
object.send(:_run_find_callbacks)
|
||||
object.send(:_run_initialize_callbacks)
|
||||
|
||||
object
|
||||
end
|
||||
@@ -2456,7 +2451,7 @@ module ActiveRecord #:nodoc:
|
||||
self.attributes = attributes unless attributes.nil?
|
||||
self.class.send(:scope, :create).each { |att,value| self.send("#{att}=", value) } if self.class.send(:scoped?, :create)
|
||||
result = yield self if block_given?
|
||||
callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
|
||||
_run_initialize_callbacks
|
||||
result
|
||||
end
|
||||
|
||||
|
||||
@@ -10,16 +10,14 @@ module ActiveRecord
|
||||
# * (-) <tt>save</tt>
|
||||
# * (-) <tt>valid</tt>
|
||||
# * (1) <tt>before_validation</tt>
|
||||
# * (2) <tt>before_validation_on_create</tt>
|
||||
# * (-) <tt>validate</tt>
|
||||
# * (-) <tt>validate_on_create</tt>
|
||||
# * (3) <tt>after_validation</tt>
|
||||
# * (4) <tt>after_validation_on_create</tt>
|
||||
# * (5) <tt>before_save</tt>
|
||||
# * (6) <tt>before_create</tt>
|
||||
# * (2) <tt>after_validation</tt>
|
||||
# * (3) <tt>before_save</tt>
|
||||
# * (4) <tt>before_create</tt>
|
||||
# * (-) <tt>create</tt>
|
||||
# * (7) <tt>after_create</tt>
|
||||
# * (8) <tt>after_save</tt>
|
||||
# * (5) <tt>after_create</tt>
|
||||
# * (6) <tt>after_save</tt>
|
||||
#
|
||||
# That's a total of eight callbacks, which gives you immense power to react and prepare for each state in the
|
||||
# Active Record lifecycle. The sequence for calling <tt>Base#save</tt> for an existing record is similar, except that each
|
||||
@@ -212,162 +210,122 @@ module ActiveRecord
|
||||
# instead of quietly returning +false+.
|
||||
module Callbacks
|
||||
extend ActiveSupport::Concern
|
||||
include ActiveSupport::NewCallbacks
|
||||
|
||||
CALLBACKS = %w(
|
||||
after_find after_initialize before_save after_save before_create after_create before_update after_update before_validation
|
||||
after_validation before_validation_on_create after_validation_on_create before_validation_on_update
|
||||
after_validation_on_update before_destroy after_destroy
|
||||
)
|
||||
CALLBACKS = [
|
||||
:after_initialize, :after_find, :before_validation, :after_validation,
|
||||
:before_save, :around_save, :after_save, :before_create, :around_create,
|
||||
:after_create, :before_update, :around_update, :after_update,
|
||||
:before_destroy, :around_destroy, :after_destroy
|
||||
]
|
||||
|
||||
included do
|
||||
extend Observable
|
||||
|
||||
[:create_or_update, :valid?, :create, :update, :destroy].each do |method|
|
||||
alias_method_chain method, :callbacks
|
||||
end
|
||||
|
||||
include ActiveSupport::Callbacks
|
||||
define_callbacks(*CALLBACKS)
|
||||
define_callbacks :initialize, :find, :save, :create, :update, :destroy,
|
||||
:validation, :terminator => "result == false", :scope => [:kind, :name]
|
||||
end
|
||||
|
||||
# Is called when the object was instantiated by one of the finders, like <tt>Base.find</tt>.
|
||||
#def after_find() end
|
||||
|
||||
# Is called after the object has been instantiated by a call to <tt>Base.new</tt>.
|
||||
#def after_initialize() end
|
||||
|
||||
# Is called _before_ <tt>Base.save</tt> (regardless of whether it's a +create+ or +update+ save).
|
||||
def before_save() end
|
||||
|
||||
# Is called _after_ <tt>Base.save</tt> (regardless of whether it's a +create+ or +update+ save).
|
||||
# Note that this callback is still wrapped in the transaction around +save+. For example, if you
|
||||
# invoke an external indexer at this point it won't see the changes in the database.
|
||||
#
|
||||
# class Contact < ActiveRecord::Base
|
||||
# after_save { logger.info( 'New contact saved!' ) }
|
||||
# end
|
||||
def after_save() end
|
||||
def create_or_update_with_callbacks #:nodoc:
|
||||
return false if callback(:before_save) == false
|
||||
if result = create_or_update_without_callbacks
|
||||
callback(:after_save)
|
||||
module ClassMethods
|
||||
def after_initialize(*args, &block)
|
||||
options = args.extract_options!
|
||||
options[:prepend] = true
|
||||
set_callback(:initialize, :after, *(args << options), &block)
|
||||
end
|
||||
|
||||
def after_find(*args, &block)
|
||||
options = args.extract_options!
|
||||
options[:prepend] = true
|
||||
set_callback(:find, :after, *(args << options), &block)
|
||||
end
|
||||
|
||||
[:save, :create, :update, :destroy].each do |callback|
|
||||
module_eval <<-CALLBACKS, __FILE__, __LINE__
|
||||
def before_#{callback}(*args, &block)
|
||||
set_callback(:#{callback}, :before, *args, &block)
|
||||
end
|
||||
|
||||
def around_#{callback}(*args, &block)
|
||||
set_callback(:#{callback}, :around, *args, &block)
|
||||
end
|
||||
|
||||
def after_#{callback}(*args, &block)
|
||||
options = args.extract_options!
|
||||
options[:prepend] = true
|
||||
options[:if] = Array(options[:if]) << "!halted && value != false"
|
||||
set_callback(:#{callback}, :after, *(args << options), &block)
|
||||
end
|
||||
CALLBACKS
|
||||
end
|
||||
|
||||
def before_validation(*args, &block)
|
||||
options = args.extract_options!
|
||||
if options[:on]
|
||||
options[:if] = Array(options[:if])
|
||||
options[:if] << "@_on_validate == :#{options[:on]}"
|
||||
end
|
||||
set_callback(:validation, :before, *(args << options), &block)
|
||||
end
|
||||
|
||||
def after_validation(*args, &block)
|
||||
options = args.extract_options!
|
||||
options[:if] = Array(options[:if])
|
||||
options[:if] << "!halted"
|
||||
options[:if] << "@_on_validate == :#{options[:on]}" if options[:on]
|
||||
options[:prepend] = true
|
||||
set_callback(:validation, :after, *(args << options), &block)
|
||||
end
|
||||
|
||||
def method_added(meth)
|
||||
super
|
||||
if CALLBACKS.include?(meth.to_sym)
|
||||
ActiveSupport::Deprecation.warn("Base##{meth} has been deprecated, please use Base.#{meth} :method instead", caller[0,1])
|
||||
send(meth.to_sym, meth.to_sym)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create_or_update_with_callbacks #:nodoc:
|
||||
_run_save_callbacks do
|
||||
create_or_update_without_callbacks
|
||||
end
|
||||
result
|
||||
end
|
||||
private :create_or_update_with_callbacks
|
||||
|
||||
# Is called _before_ <tt>Base.save</tt> on new objects that haven't been saved yet (no record exists).
|
||||
def before_create() end
|
||||
|
||||
# Is called _after_ <tt>Base.save</tt> on new objects that haven't been saved yet (no record exists).
|
||||
# Note that this callback is still wrapped in the transaction around +save+. For example, if you
|
||||
# invoke an external indexer at this point it won't see the changes in the database.
|
||||
#
|
||||
# class Contact < ActiveRecord::Base
|
||||
# after_create { |record| logger.info( "Contact #{record.id} was created." ) }
|
||||
# end
|
||||
def after_create() end
|
||||
def create_with_callbacks #:nodoc:
|
||||
return false if callback(:before_create) == false
|
||||
result = create_without_callbacks
|
||||
callback(:after_create)
|
||||
result
|
||||
_run_create_callbacks do
|
||||
create_without_callbacks
|
||||
end
|
||||
end
|
||||
private :create_with_callbacks
|
||||
|
||||
# Is called _before_ <tt>Base.save</tt> on existing objects that have a record.
|
||||
#
|
||||
# class Contact < ActiveRecord::Base
|
||||
# before_update { |record| logger.info( "Contact #{record.id} is about to be updated." ) }
|
||||
# end
|
||||
def before_update() end
|
||||
|
||||
# Is called _after_ <tt>Base.save</tt> on existing objects that have a record.
|
||||
# Note that this callback is still wrapped in the transaction around +save+. For example, if you
|
||||
# invoke an external indexer at this point it won't see the changes in the database.
|
||||
#
|
||||
# class Contact < ActiveRecord::Base
|
||||
# after_update { |record| logger.info( "Contact #{record.id} was updated." ) }
|
||||
# end
|
||||
def after_update() end
|
||||
|
||||
def update_with_callbacks(*args) #:nodoc:
|
||||
return false if callback(:before_update) == false
|
||||
result = update_without_callbacks(*args)
|
||||
callback(:after_update)
|
||||
result
|
||||
_run_update_callbacks do
|
||||
update_without_callbacks(*args)
|
||||
end
|
||||
end
|
||||
private :update_with_callbacks
|
||||
|
||||
# Is called _before_ <tt>Validations.validate</tt> (which is part of the <tt>Base.save</tt> call).
|
||||
def before_validation() end
|
||||
|
||||
# Is called _after_ <tt>Validations.validate</tt> (which is part of the <tt>Base.save</tt> call).
|
||||
def after_validation() end
|
||||
|
||||
# Is called _before_ <tt>Validations.validate</tt> (which is part of the <tt>Base.save</tt> call) on new objects
|
||||
# that haven't been saved yet (no record exists).
|
||||
def before_validation_on_create() end
|
||||
|
||||
# Is called _after_ <tt>Validations.validate</tt> (which is part of the <tt>Base.save</tt> call) on new objects
|
||||
# that haven't been saved yet (no record exists).
|
||||
def after_validation_on_create() end
|
||||
|
||||
# Is called _before_ <tt>Validations.validate</tt> (which is part of the <tt>Base.save</tt> call) on
|
||||
# existing objects that have a record.
|
||||
def before_validation_on_update() end
|
||||
|
||||
# Is called _after_ <tt>Validations.validate</tt> (which is part of the <tt>Base.save</tt> call) on
|
||||
# existing objects that have a record.
|
||||
def after_validation_on_update() end
|
||||
|
||||
def valid_with_callbacks? #:nodoc:
|
||||
return false if callback(:before_validation) == false
|
||||
if new_record? then result = callback(:before_validation_on_create) else result = callback(:before_validation_on_update) end
|
||||
return false if false == result
|
||||
|
||||
result = valid_without_callbacks?
|
||||
|
||||
callback(:after_validation)
|
||||
if new_record? then callback(:after_validation_on_create) else callback(:after_validation_on_update) end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
# Is called _before_ <tt>Base.destroy</tt>.
|
||||
#
|
||||
# Note: If you need to _destroy_ or _nullify_ associated records first,
|
||||
# use the <tt>:dependent</tt> option on your associations.
|
||||
#
|
||||
# class Contact < ActiveRecord::Base
|
||||
# after_destroy { |record| logger.info( "Contact #{record.id} is about to be destroyed." ) }
|
||||
# end
|
||||
def before_destroy() end
|
||||
|
||||
# Is called _after_ <tt>Base.destroy</tt> (and all the attributes have been frozen).
|
||||
#
|
||||
# class Contact < ActiveRecord::Base
|
||||
# after_destroy { |record| logger.info( "Contact #{record.id} was destroyed." ) }
|
||||
# end
|
||||
def after_destroy() end
|
||||
def destroy_with_callbacks #:nodoc:
|
||||
return false if callback(:before_destroy) == false
|
||||
result = destroy_without_callbacks
|
||||
callback(:after_destroy)
|
||||
result
|
||||
end
|
||||
|
||||
private
|
||||
def callback(method)
|
||||
result = run_callbacks(method) { |result, object| false == result }
|
||||
|
||||
if result != false && respond_to_without_attributes?(method)
|
||||
result = send(method)
|
||||
end
|
||||
|
||||
notify_observers(method)
|
||||
|
||||
return result
|
||||
@_on_validate = new_record? ? :create : :update
|
||||
_run_validation_callbacks do
|
||||
valid_without_callbacks?
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_with_callbacks #:nodoc:
|
||||
_run_destroy_callbacks do
|
||||
destroy_without_callbacks
|
||||
end
|
||||
end
|
||||
|
||||
def deprecated_callback_method(symbol) #:nodoc:
|
||||
if respond_to?(symbol)
|
||||
ActiveSupport::Deprecation.warn("Base##{symbol} has been deprecated, please use Base.#{symbol} :method instead")
|
||||
send(symbol)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
require 'singleton'
|
||||
require 'set'
|
||||
|
||||
module ActiveRecord
|
||||
# Observer classes respond to lifecycle callbacks to implement trigger-like
|
||||
# behavior outside the original class. This is a great way to reduce the
|
||||
@@ -88,11 +85,17 @@ module ActiveRecord
|
||||
# singletons and that call instantiates and registers them.
|
||||
#
|
||||
class Observer < ActiveModel::Observer
|
||||
extlib_inheritable_accessor(:observed_methods){ [] }
|
||||
|
||||
def initialize
|
||||
super
|
||||
observed_subclasses.each { |klass| add_observer!(klass) }
|
||||
end
|
||||
|
||||
def self.method_added(method)
|
||||
observed_methods << method if ActiveRecord::Callbacks::CALLBACKS.include?(method.to_sym)
|
||||
end
|
||||
|
||||
protected
|
||||
def observed_subclasses
|
||||
observed_classes.sum([]) { |klass| klass.send(:subclasses) }
|
||||
@@ -100,8 +103,15 @@ module ActiveRecord
|
||||
|
||||
def add_observer!(klass)
|
||||
super
|
||||
if respond_to?(:after_find) && !klass.method_defined?(:after_find)
|
||||
klass.class_eval 'def after_find() end'
|
||||
|
||||
# Check if a notifier callback was already added to the given class. If
|
||||
# it was not, add it.
|
||||
self.observed_methods.each do |method|
|
||||
callback = :"_notify_observers_for_#{method}"
|
||||
if (klass.instance_methods & [callback, callback.to_s]).empty?
|
||||
klass.class_eval "def #{callback}; notify_observers(:#{method}); end"
|
||||
klass.send(method, callback)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -110,8 +110,6 @@ module ActiveRecord
|
||||
included do
|
||||
alias_method_chain :save, :validation
|
||||
alias_method_chain :save!, :validation
|
||||
|
||||
define_callbacks :validate_on_create, :validate_on_update
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
@@ -127,17 +125,6 @@ module ActiveRecord
|
||||
object
|
||||
end
|
||||
end
|
||||
|
||||
def validation_method(on)
|
||||
case on
|
||||
when :create
|
||||
:validate_on_create
|
||||
when :update
|
||||
:validate_on_update
|
||||
else
|
||||
:validate
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
@@ -165,27 +152,15 @@ module ActiveRecord
|
||||
def valid?
|
||||
errors.clear
|
||||
|
||||
run_callbacks(:validate)
|
||||
@_on_validate = new_record? ? :create : :update
|
||||
_run_validate_callbacks
|
||||
|
||||
if respond_to?(:validate)
|
||||
ActiveSupport::Deprecation.warn("Base#validate has been deprecated, please use Base.validate :method instead")
|
||||
validate
|
||||
end
|
||||
deprecated_callback_method(:validate)
|
||||
|
||||
if new_record?
|
||||
run_callbacks(:validate_on_create)
|
||||
|
||||
if respond_to?(:validate_on_create)
|
||||
ActiveSupport::Deprecation.warn("Base#validate_on_create has been deprecated, please use Base.validate_on_create :method instead")
|
||||
validate_on_create
|
||||
end
|
||||
deprecated_callback_method(:validate_on_create)
|
||||
else
|
||||
run_callbacks(:validate_on_update)
|
||||
|
||||
if respond_to?(:validate_on_update)
|
||||
ActiveSupport::Deprecation.warn("Base#validate_on_update has been deprecated, please use Base.validate_on_update :method instead")
|
||||
validate_on_update
|
||||
end
|
||||
deprecated_callback_method(:validate_on_update)
|
||||
end
|
||||
|
||||
errors.empty?
|
||||
|
||||
@@ -813,7 +813,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
|
||||
firm = companies(:first_firm)
|
||||
clients = firm.clients
|
||||
assert_equal 2, clients.length
|
||||
clients.last.instance_eval { def before_destroy() raise "Trigger rollback" end }
|
||||
clients.last.instance_eval { def overwrite_to_raise() raise "Trigger rollback" end }
|
||||
|
||||
firm.destroy rescue "do nothing"
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ class Comment < ActiveRecord::Base
|
||||
|
||||
before_validation :record_callers
|
||||
|
||||
def after_validation
|
||||
after_validation do
|
||||
record_callers
|
||||
end
|
||||
|
||||
@@ -32,7 +32,6 @@ class CallbacksObserversTest < ActiveRecord::TestCase
|
||||
CommentObserver.instance.callers = callers
|
||||
|
||||
comment.valid?
|
||||
|
||||
assert_equal [Comment, Comment, CommentObserver], callers, "model callbacks did not fire before observers were notified"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,8 +13,8 @@ class CallbackDeveloper < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def define_callback_method(callback_method)
|
||||
define_method("#{callback_method}_method") do |model|
|
||||
model.history << [callback_method, :method]
|
||||
define_method(callback_method) do
|
||||
self.history << [callback_method, :method]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -27,26 +27,20 @@ class CallbackDeveloper < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
ActiveRecord::Callbacks::CALLBACKS.each do |callback_method|
|
||||
callback_method_sym = callback_method.to_sym
|
||||
define_callback_method(callback_method_sym)
|
||||
send(callback_method, 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] }
|
||||
ActiveSupport::Deprecation.silence do
|
||||
ActiveRecord::Callbacks::CALLBACKS.each do |callback_method|
|
||||
next if callback_method.to_s =~ /^around_/
|
||||
define_callback_method(callback_method)
|
||||
send(callback_method, callback_string(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
|
||||
end
|
||||
|
||||
def history
|
||||
@history ||= []
|
||||
end
|
||||
|
||||
# after_initialize and after_find are invoked only if instance methods have been defined.
|
||||
def after_initialize
|
||||
end
|
||||
|
||||
def after_find
|
||||
end
|
||||
end
|
||||
|
||||
class ParentDeveloper < ActiveRecord::Base
|
||||
@@ -108,12 +102,12 @@ class ImmutableMethodDeveloper < ActiveRecord::Base
|
||||
@cancelled == true
|
||||
end
|
||||
|
||||
def before_save
|
||||
before_save do
|
||||
@cancelled = true
|
||||
false
|
||||
end
|
||||
|
||||
def before_destroy
|
||||
before_destroy do
|
||||
@cancelled = true
|
||||
false
|
||||
end
|
||||
@@ -125,15 +119,15 @@ class CallbackCancellationDeveloper < ActiveRecord::Base
|
||||
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
|
||||
|
||||
def before_save; !@cancel_before_save; end
|
||||
def before_create; !@cancel_before_create; end
|
||||
def before_update; !@cancel_before_update; end
|
||||
def before_destroy; !@cancel_before_destroy; end
|
||||
before_save { !@cancel_before_save }
|
||||
before_create { !@cancel_before_create }
|
||||
before_update { !@cancel_before_update }
|
||||
before_destroy { !@cancel_before_destroy }
|
||||
|
||||
def after_save; @after_save_called = true; end
|
||||
def after_update; @after_update_called = true; end
|
||||
def after_create; @after_create_called = true; end
|
||||
def after_destroy; @after_destroy_called = true; end
|
||||
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
|
||||
@@ -142,6 +136,7 @@ class CallbacksTest < ActiveRecord::TestCase
|
||||
def test_initialize
|
||||
david = CallbackDeveloper.new
|
||||
assert_equal [
|
||||
[ :after_initialize, :method ],
|
||||
[ :after_initialize, :string ],
|
||||
[ :after_initialize, :proc ],
|
||||
[ :after_initialize, :object ],
|
||||
@@ -152,10 +147,12 @@ class CallbacksTest < ActiveRecord::TestCase
|
||||
def test_find
|
||||
david = CallbackDeveloper.find(1)
|
||||
assert_equal [
|
||||
[ :after_find, :method ],
|
||||
[ :after_find, :string ],
|
||||
[ :after_find, :proc ],
|
||||
[ :after_find, :object ],
|
||||
[ :after_find, :block ],
|
||||
[ :after_initialize, :method ],
|
||||
[ :after_initialize, :string ],
|
||||
[ :after_initialize, :proc ],
|
||||
[ :after_initialize, :object ],
|
||||
@@ -167,26 +164,21 @@ class CallbacksTest < ActiveRecord::TestCase
|
||||
david = CallbackDeveloper.new
|
||||
david.valid?
|
||||
assert_equal [
|
||||
[ :after_initialize, :method ],
|
||||
[ :after_initialize, :string ],
|
||||
[ :after_initialize, :proc ],
|
||||
[ :after_initialize, :object ],
|
||||
[ :after_initialize, :block ],
|
||||
[ :before_validation, :method ],
|
||||
[ :before_validation, :string ],
|
||||
[ :before_validation, :proc ],
|
||||
[ :before_validation, :object ],
|
||||
[ :before_validation, :block ],
|
||||
[ :before_validation_on_create, :string ],
|
||||
[ :before_validation_on_create, :proc ],
|
||||
[ :before_validation_on_create, :object ],
|
||||
[ :before_validation_on_create, :block ],
|
||||
[ :after_validation, :method ],
|
||||
[ :after_validation, :string ],
|
||||
[ :after_validation, :proc ],
|
||||
[ :after_validation, :object ],
|
||||
[ :after_validation, :block ],
|
||||
[ :after_validation_on_create, :string ],
|
||||
[ :after_validation_on_create, :proc ],
|
||||
[ :after_validation_on_create, :object ],
|
||||
[ :after_validation_on_create, :block ]
|
||||
], david.history
|
||||
end
|
||||
|
||||
@@ -194,68 +186,63 @@ class CallbacksTest < ActiveRecord::TestCase
|
||||
david = CallbackDeveloper.find(1)
|
||||
david.valid?
|
||||
assert_equal [
|
||||
[ :after_find, :method ],
|
||||
[ :after_find, :string ],
|
||||
[ :after_find, :proc ],
|
||||
[ :after_find, :object ],
|
||||
[ :after_find, :block ],
|
||||
[ :after_initialize, :method ],
|
||||
[ :after_initialize, :string ],
|
||||
[ :after_initialize, :proc ],
|
||||
[ :after_initialize, :object ],
|
||||
[ :after_initialize, :block ],
|
||||
[ :before_validation, :method ],
|
||||
[ :before_validation, :string ],
|
||||
[ :before_validation, :proc ],
|
||||
[ :before_validation, :object ],
|
||||
[ :before_validation, :block ],
|
||||
[ :before_validation_on_update, :string ],
|
||||
[ :before_validation_on_update, :proc ],
|
||||
[ :before_validation_on_update, :object ],
|
||||
[ :before_validation_on_update, :block ],
|
||||
[ :after_validation, :method ],
|
||||
[ :after_validation, :string ],
|
||||
[ :after_validation, :proc ],
|
||||
[ :after_validation, :object ],
|
||||
[ :after_validation, :block ],
|
||||
[ :after_validation_on_update, :string ],
|
||||
[ :after_validation_on_update, :proc ],
|
||||
[ :after_validation_on_update, :object ],
|
||||
[ :after_validation_on_update, :block ]
|
||||
], david.history
|
||||
end
|
||||
|
||||
def test_create
|
||||
david = CallbackDeveloper.create('name' => 'David', 'salary' => 1000000)
|
||||
assert_equal [
|
||||
[ :after_initialize, :method ],
|
||||
[ :after_initialize, :string ],
|
||||
[ :after_initialize, :proc ],
|
||||
[ :after_initialize, :object ],
|
||||
[ :after_initialize, :block ],
|
||||
[ :before_validation, :method ],
|
||||
[ :before_validation, :string ],
|
||||
[ :before_validation, :proc ],
|
||||
[ :before_validation, :object ],
|
||||
[ :before_validation, :block ],
|
||||
[ :before_validation_on_create, :string ],
|
||||
[ :before_validation_on_create, :proc ],
|
||||
[ :before_validation_on_create, :object ],
|
||||
[ :before_validation_on_create, :block ],
|
||||
[ :after_validation, :method ],
|
||||
[ :after_validation, :string ],
|
||||
[ :after_validation, :proc ],
|
||||
[ :after_validation, :object ],
|
||||
[ :after_validation, :block ],
|
||||
[ :after_validation_on_create, :string ],
|
||||
[ :after_validation_on_create, :proc ],
|
||||
[ :after_validation_on_create, :object ],
|
||||
[ :after_validation_on_create, :block ],
|
||||
[ :before_save, :method ],
|
||||
[ :before_save, :string ],
|
||||
[ :before_save, :proc ],
|
||||
[ :before_save, :object ],
|
||||
[ :before_save, :block ],
|
||||
[ :before_create, :method ],
|
||||
[ :before_create, :string ],
|
||||
[ :before_create, :proc ],
|
||||
[ :before_create, :object ],
|
||||
[ :before_create, :block ],
|
||||
[ :after_create, :method ],
|
||||
[ :after_create, :string ],
|
||||
[ :after_create, :proc ],
|
||||
[ :after_create, :object ],
|
||||
[ :after_create, :block ],
|
||||
[ :after_save, :method ],
|
||||
[ :after_save, :string ],
|
||||
[ :after_save, :proc ],
|
||||
[ :after_save, :object ],
|
||||
@@ -267,42 +254,42 @@ class CallbacksTest < ActiveRecord::TestCase
|
||||
david = CallbackDeveloper.find(1)
|
||||
david.save
|
||||
assert_equal [
|
||||
[ :after_find, :method ],
|
||||
[ :after_find, :string ],
|
||||
[ :after_find, :proc ],
|
||||
[ :after_find, :object ],
|
||||
[ :after_find, :block ],
|
||||
[ :after_initialize, :method ],
|
||||
[ :after_initialize, :string ],
|
||||
[ :after_initialize, :proc ],
|
||||
[ :after_initialize, :object ],
|
||||
[ :after_initialize, :block ],
|
||||
[ :before_validation, :method ],
|
||||
[ :before_validation, :string ],
|
||||
[ :before_validation, :proc ],
|
||||
[ :before_validation, :object ],
|
||||
[ :before_validation, :block ],
|
||||
[ :before_validation_on_update, :string ],
|
||||
[ :before_validation_on_update, :proc ],
|
||||
[ :before_validation_on_update, :object ],
|
||||
[ :before_validation_on_update, :block ],
|
||||
[ :after_validation, :method ],
|
||||
[ :after_validation, :string ],
|
||||
[ :after_validation, :proc ],
|
||||
[ :after_validation, :object ],
|
||||
[ :after_validation, :block ],
|
||||
[ :after_validation_on_update, :string ],
|
||||
[ :after_validation_on_update, :proc ],
|
||||
[ :after_validation_on_update, :object ],
|
||||
[ :after_validation_on_update, :block ],
|
||||
[ :before_save, :method ],
|
||||
[ :before_save, :string ],
|
||||
[ :before_save, :proc ],
|
||||
[ :before_save, :object ],
|
||||
[ :before_save, :block ],
|
||||
[ :before_update, :method ],
|
||||
[ :before_update, :string ],
|
||||
[ :before_update, :proc ],
|
||||
[ :before_update, :object ],
|
||||
[ :before_update, :block ],
|
||||
[ :after_update, :method ],
|
||||
[ :after_update, :string ],
|
||||
[ :after_update, :proc ],
|
||||
[ :after_update, :object ],
|
||||
[ :after_update, :block ],
|
||||
[ :after_save, :method ],
|
||||
[ :after_save, :string ],
|
||||
[ :after_save, :proc ],
|
||||
[ :after_save, :object ],
|
||||
@@ -314,18 +301,22 @@ class CallbacksTest < ActiveRecord::TestCase
|
||||
david = CallbackDeveloper.find(1)
|
||||
david.destroy
|
||||
assert_equal [
|
||||
[ :after_find, :method ],
|
||||
[ :after_find, :string ],
|
||||
[ :after_find, :proc ],
|
||||
[ :after_find, :object ],
|
||||
[ :after_find, :block ],
|
||||
[ :after_initialize, :method ],
|
||||
[ :after_initialize, :string ],
|
||||
[ :after_initialize, :proc ],
|
||||
[ :after_initialize, :object ],
|
||||
[ :after_initialize, :block ],
|
||||
[ :before_destroy, :method ],
|
||||
[ :before_destroy, :string ],
|
||||
[ :before_destroy, :proc ],
|
||||
[ :before_destroy, :object ],
|
||||
[ :before_destroy, :block ],
|
||||
[ :after_destroy, :method ],
|
||||
[ :after_destroy, :string ],
|
||||
[ :after_destroy, :proc ],
|
||||
[ :after_destroy, :object ],
|
||||
@@ -337,10 +328,12 @@ class CallbacksTest < ActiveRecord::TestCase
|
||||
david = CallbackDeveloper.find(1)
|
||||
CallbackDeveloper.delete(david.id)
|
||||
assert_equal [
|
||||
[ :after_find, :method ],
|
||||
[ :after_find, :string ],
|
||||
[ :after_find, :proc ],
|
||||
[ :after_find, :object ],
|
||||
[ :after_find, :block ],
|
||||
[ :after_initialize, :method ],
|
||||
[ :after_initialize, :string ],
|
||||
[ :after_initialize, :proc ],
|
||||
[ :after_initialize, :object ],
|
||||
@@ -407,14 +400,17 @@ class CallbacksTest < ActiveRecord::TestCase
|
||||
CallbackDeveloper.before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] }
|
||||
david.save
|
||||
assert_equal [
|
||||
[ :after_find, :method ],
|
||||
[ :after_find, :string ],
|
||||
[ :after_find, :proc ],
|
||||
[ :after_find, :object ],
|
||||
[ :after_find, :block ],
|
||||
[ :after_initialize, :method ],
|
||||
[ :after_initialize, :string ],
|
||||
[ :after_initialize, :proc ],
|
||||
[ :after_initialize, :object ],
|
||||
[ :after_initialize, :block ],
|
||||
[ :before_validation, :method ],
|
||||
[ :before_validation, :string ],
|
||||
[ :before_validation, :proc ],
|
||||
[ :before_validation, :object ],
|
||||
|
||||
@@ -12,8 +12,6 @@ require 'active_record/test_case'
|
||||
require 'active_record/fixtures'
|
||||
require 'connection'
|
||||
|
||||
require 'cases/repair_helper'
|
||||
|
||||
begin
|
||||
require 'ruby-debug'
|
||||
rescue LoadError
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
require "cases/helper"
|
||||
require 'cases/helper'
|
||||
require 'models/topic'
|
||||
require 'models/developer'
|
||||
require 'models/reply'
|
||||
require 'models/minimalistic'
|
||||
|
||||
class Topic; def after_find() end end
|
||||
class Developer; def after_find() end end
|
||||
class SpecialDeveloper < Developer; end
|
||||
|
||||
class TopicManualObserver
|
||||
@@ -43,6 +41,11 @@ class TopicObserver < ActiveRecord::Observer
|
||||
def after_find(topic)
|
||||
@topic = topic
|
||||
end
|
||||
|
||||
# Create an after_save callback, so a notify_observer hook is created
|
||||
# on :topic.
|
||||
def after_save(nothing)
|
||||
end
|
||||
end
|
||||
|
||||
class MinimalisticObserver < ActiveRecord::Observer
|
||||
@@ -159,34 +162,6 @@ class LifecycleTest < ActiveRecord::TestCase
|
||||
assert_equal topic, observer.topic
|
||||
end
|
||||
|
||||
def test_after_find_is_not_created_if_its_not_used
|
||||
# use a fresh class so an observer can't have defined an
|
||||
# after_find on it
|
||||
model_class = Class.new(ActiveRecord::Base)
|
||||
observer_class = Class.new(ActiveRecord::Observer)
|
||||
observer_class.observe(model_class)
|
||||
|
||||
observer = observer_class.instance
|
||||
|
||||
assert !model_class.method_defined?(:after_find)
|
||||
end
|
||||
|
||||
def test_after_find_is_not_clobbered_if_it_already_exists
|
||||
# use a fresh observer class so we can instantiate it (Observer is
|
||||
# a Singleton)
|
||||
model_class = Class.new(ActiveRecord::Base) do
|
||||
def after_find; end
|
||||
end
|
||||
original_method = model_class.instance_method(:after_find)
|
||||
observer_class = Class.new(ActiveRecord::Observer) do
|
||||
def after_find; end
|
||||
end
|
||||
observer_class.observe(model_class)
|
||||
|
||||
observer = observer_class.instance
|
||||
assert_equal original_method, model_class.instance_method(:after_find)
|
||||
end
|
||||
|
||||
def test_invalid_observer
|
||||
assert_raise(ArgumentError) { Topic.observers = Object.new; Topic.instantiate_observers }
|
||||
end
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
module ActiveRecord
|
||||
module Testing
|
||||
module RepairHelper
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
module Toolbox
|
||||
def self.record_validations(*model_classes)
|
||||
model_classes.inject({}) do |repair, klass|
|
||||
repair[klass] ||= {}
|
||||
[:validate, :validate_on_create, :validate_on_update].each do |callback|
|
||||
the_callback = klass.instance_variable_get("@#{callback.to_s}_callbacks")
|
||||
repair[klass][callback] = (the_callback.nil? ? nil : the_callback.dup)
|
||||
end
|
||||
repair
|
||||
end
|
||||
end
|
||||
|
||||
def self.reset_validations(recorded)
|
||||
recorded.each do |klass, repairs|
|
||||
[:validate, :validate_on_create, :validate_on_update].each do |callback|
|
||||
klass.instance_variable_set("@#{callback.to_s}_callbacks", repairs[callback])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def repair_validations(*model_classes)
|
||||
setup do
|
||||
@validation_repairs = ActiveRecord::Testing::RepairHelper::Toolbox.record_validations(*model_classes)
|
||||
end
|
||||
teardown do
|
||||
ActiveRecord::Testing::RepairHelper::Toolbox.reset_validations(@validation_repairs)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def repair_validations(*model_classes, &block)
|
||||
validation_repairs = ActiveRecord::Testing::RepairHelper::Toolbox.record_validations(*model_classes)
|
||||
return block.call
|
||||
ensure
|
||||
ActiveRecord::Testing::RepairHelper::Toolbox.reset_validations(validation_repairs)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -382,28 +382,28 @@ class TransactionTest < ActiveRecord::TestCase
|
||||
|
||||
private
|
||||
def add_exception_raising_after_save_callback_to_topic
|
||||
Topic.class_eval { def after_save() raise "Make the transaction rollback" end }
|
||||
Topic.class_eval "def after_save_for_transaction; raise 'Make the transaction rollback' end"
|
||||
end
|
||||
|
||||
def remove_exception_raising_after_save_callback_to_topic
|
||||
Topic.class_eval { remove_method :after_save }
|
||||
Topic.class_eval "def after_save_for_transaction; end"
|
||||
end
|
||||
|
||||
def add_exception_raising_after_create_callback_to_topic
|
||||
Topic.class_eval { def after_create() raise "Make the transaction rollback" end }
|
||||
Topic.class_eval "def after_create_for_transaction; raise 'Make the transaction rollback' end"
|
||||
end
|
||||
|
||||
def remove_exception_raising_after_create_callback_to_topic
|
||||
Topic.class_eval { remove_method :after_create }
|
||||
Topic.class_eval "def after_create_for_transaction; end"
|
||||
end
|
||||
|
||||
%w(validation save destroy).each do |filter|
|
||||
define_method("add_cancelling_before_#{filter}_with_db_side_effect_to_topic") do
|
||||
Topic.class_eval "def before_#{filter}() Book.create; false end"
|
||||
Topic.class_eval "def before_#{filter}_for_transaction() Book.create; false end"
|
||||
end
|
||||
|
||||
define_method("remove_cancelling_before_#{filter}_with_db_side_effect_to_topic") do
|
||||
Topic.class_eval "remove_method :before_#{filter}"
|
||||
Topic.class_eval "def before_#{filter}_for_transaction; end"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,9 +2,9 @@ require "cases/helper"
|
||||
require 'models/topic'
|
||||
require 'models/reply'
|
||||
|
||||
class I18nGenerateMessageValidationTest < Test::Unit::TestCase
|
||||
class I18nGenerateMessageValidationTest < ActiveRecord::TestCase
|
||||
def setup
|
||||
reset_callbacks Topic
|
||||
Topic.reset_callbacks(:validate)
|
||||
@topic = Topic.new
|
||||
I18n.backend.store_translations :'en', {
|
||||
:activerecord => {
|
||||
@@ -17,14 +17,6 @@ class I18nGenerateMessageValidationTest < Test::Unit::TestCase
|
||||
}
|
||||
end
|
||||
|
||||
def reset_callbacks(*models)
|
||||
models.each do |model|
|
||||
model.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||
model.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||
model.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||
end
|
||||
end
|
||||
|
||||
# validates_inclusion_of: generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value)
|
||||
def test_generate_message_inclusion_with_default_message
|
||||
assert_equal 'is not included in the list', @topic.errors.generate_message(:title, :inclusion, :default => nil, :value => 'title')
|
||||
|
||||
@@ -4,7 +4,7 @@ require 'models/reply'
|
||||
|
||||
class I18nValidationTest < ActiveRecord::TestCase
|
||||
def setup
|
||||
reset_callbacks Topic
|
||||
Topic.reset_callbacks(:validate)
|
||||
@topic = Topic.new
|
||||
@old_load_path, @old_backend = I18n.load_path, I18n.backend
|
||||
I18n.load_path.clear
|
||||
@@ -13,7 +13,7 @@ class I18nValidationTest < ActiveRecord::TestCase
|
||||
end
|
||||
|
||||
def teardown
|
||||
reset_callbacks Topic
|
||||
Topic.reset_callbacks(:validate)
|
||||
I18n.load_path.replace @old_load_path
|
||||
I18n.backend = @old_backend
|
||||
end
|
||||
@@ -30,14 +30,6 @@ class I18nValidationTest < ActiveRecord::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
def reset_callbacks(*models)
|
||||
models.each do |model|
|
||||
model.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||
model.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||
model.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||
end
|
||||
end
|
||||
|
||||
def test_percent_s_interpolation_syntax_in_error_messages_was_deprecated
|
||||
assert_not_deprecated do
|
||||
default = "%s interpolation syntax was deprecated"
|
||||
@@ -710,9 +702,9 @@ class I18nValidationTest < ActiveRecord::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
class ActiveRecordValidationsGenerateMessageI18nTests < ActiveSupport::TestCase
|
||||
class ActiveRecordValidationsGenerateMessageI18nTests < ActiveRecord::TestCase
|
||||
|
||||
def setup
|
||||
reset_callbacks Topic
|
||||
@topic = Topic.new
|
||||
I18n.backend.store_translations :'en', {
|
||||
:activerecord => {
|
||||
@@ -743,14 +735,6 @@ class ActiveRecordValidationsGenerateMessageI18nTests < ActiveSupport::TestCase
|
||||
}
|
||||
end
|
||||
|
||||
def reset_callbacks(*models)
|
||||
models.each do |model|
|
||||
model.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||
model.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||
model.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
|
||||
end
|
||||
end
|
||||
|
||||
# validates_inclusion_of: generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value)
|
||||
def test_generate_message_inclusion_with_default_message
|
||||
assert_equal 'is not included in the list', @topic.errors.generate_message(:title, :inclusion, :default => nil, :value => 'title')
|
||||
|
||||
@@ -161,12 +161,9 @@ class ValidationsTest < ActiveRecord::TestCase
|
||||
end
|
||||
|
||||
def test_validates_acceptance_of_as_database_column
|
||||
repair_validations(Reply) do
|
||||
Reply.validates_acceptance_of(:author_name)
|
||||
|
||||
reply = Reply.create("author_name" => "Dan Brown")
|
||||
assert_equal "Dan Brown", reply["author_name"]
|
||||
end
|
||||
Topic.validates_acceptance_of(:author_name)
|
||||
topic = Topic.create("author_name" => "Dan Brown")
|
||||
assert_equal "Dan Brown", topic["author_name"]
|
||||
end
|
||||
|
||||
def test_deprecated_validation_instance_methods
|
||||
|
||||
@@ -94,8 +94,9 @@ class Author < ActiveRecord::Base
|
||||
belongs_to :author_address_extra, :dependent => :delete, :class_name => "AuthorAddress"
|
||||
|
||||
attr_accessor :post_log
|
||||
after_initialize :set_post_log
|
||||
|
||||
def after_initialize
|
||||
def set_post_log
|
||||
@post_log = []
|
||||
end
|
||||
|
||||
|
||||
@@ -111,6 +111,8 @@ class Client < Company
|
||||
true
|
||||
end
|
||||
|
||||
before_destroy :overwrite_to_raise
|
||||
|
||||
# Used to test that read and question methods are not generated for these attributes
|
||||
def ruby_type
|
||||
read_attribute :ruby_type
|
||||
@@ -120,6 +122,9 @@ class Client < Company
|
||||
query_attribute :rating
|
||||
end
|
||||
|
||||
def overwrite_to_raise
|
||||
end
|
||||
|
||||
class << self
|
||||
private
|
||||
|
||||
|
||||
@@ -22,8 +22,9 @@ class Project < ActiveRecord::Base
|
||||
has_and_belongs_to_many :well_payed_salary_groups, :class_name => "Developer", :group => "developers.salary", :having => "SUM(salary) > 10000", :select => "SUM(salary) as salary"
|
||||
|
||||
attr_accessor :developers_log
|
||||
after_initialize :set_developers_log
|
||||
|
||||
def after_initialize
|
||||
def set_developers_log
|
||||
@developers_log = []
|
||||
end
|
||||
|
||||
|
||||
@@ -8,13 +8,13 @@ class Reply < Topic
|
||||
has_many :replies, :class_name => "SillyReply", :dependent => :destroy, :foreign_key => "parent_id"
|
||||
|
||||
validate :errors_on_empty_content
|
||||
validate_on_create :title_is_wrong_create
|
||||
validate :title_is_wrong_create, :on => :create
|
||||
|
||||
attr_accessible :title, :author_name, :author_email_address, :written_on, :content, :last_read, :parent_title
|
||||
|
||||
validate :check_empty_title
|
||||
validate_on_create :check_content_mismatch
|
||||
validate_on_update :check_wrong_update
|
||||
validate :check_content_mismatch, :on => :create
|
||||
validate :check_wrong_update, :on => :update
|
||||
|
||||
def check_empty_title
|
||||
errors[:title] << "Empty" unless attribute_present?("title")
|
||||
@@ -47,4 +47,4 @@ module Web
|
||||
class Reply < Web::Topic
|
||||
belongs_to :topic, :foreign_key => "parent_id", :counter_cache => true, :class_name => 'Web::Topic'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -52,6 +52,15 @@ class Topic < ActiveRecord::Base
|
||||
id
|
||||
end
|
||||
|
||||
before_validation :before_validation_for_transaction
|
||||
before_save :before_save_for_transaction
|
||||
before_destroy :before_destroy_for_transaction
|
||||
|
||||
after_save :after_save_for_transaction
|
||||
after_create :after_create_for_transaction
|
||||
|
||||
after_initialize :set_email_address
|
||||
|
||||
protected
|
||||
def approved=(val)
|
||||
@custom_approved = val
|
||||
@@ -66,15 +75,21 @@ class Topic < ActiveRecord::Base
|
||||
self.class.delete_all "parent_id = #{id}"
|
||||
end
|
||||
|
||||
def after_initialize
|
||||
def set_email_address
|
||||
if self.new_record?
|
||||
self.author_email_address = 'test@test.com'
|
||||
end
|
||||
end
|
||||
|
||||
def before_validation_for_transaction; end
|
||||
def before_save_for_transaction; end
|
||||
def before_destroy_for_transaction; end
|
||||
def after_save_for_transaction; end
|
||||
def after_create_for_transaction; end
|
||||
end
|
||||
|
||||
module Web
|
||||
class Topic < ActiveRecord::Base
|
||||
has_many :replies, :dependent => :destroy, :foreign_key => "parent_id", :class_name => 'Web::Reply'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -38,16 +38,34 @@ module ActiveSupport
|
||||
end
|
||||
|
||||
private
|
||||
# constant-time comparison algorithm to prevent timing attacks
|
||||
def secure_compare(a, b)
|
||||
if a.length == b.length
|
||||
result = 0
|
||||
for i in 0..(a.length - 1)
|
||||
result |= a[i] ^ b[i]
|
||||
if "foo".respond_to?(:force_encoding)
|
||||
# constant-time comparison algorithm to prevent timing attacks
|
||||
def secure_compare(a, b)
|
||||
a = a.force_encoding(Encoding::BINARY)
|
||||
b = b.force_encoding(Encoding::BINARY)
|
||||
|
||||
if a.length == b.length
|
||||
result = 0
|
||||
for i in 0..(a.length - 1)
|
||||
result |= a[i].ord ^ b[i].ord
|
||||
end
|
||||
result == 0
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
else
|
||||
# For 1.8
|
||||
def secure_compare(a, b)
|
||||
if a.length == b.length
|
||||
result = 0
|
||||
for i in 0..(a.length - 1)
|
||||
result |= a[i] ^ b[i]
|
||||
end
|
||||
result == 0
|
||||
else
|
||||
false
|
||||
end
|
||||
result == 0
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -15,17 +15,17 @@ module ActiveSupport
|
||||
# end
|
||||
#
|
||||
# class ConfigStorage < Storage
|
||||
# save_callback :before, :saving_message
|
||||
# set_callback :save, :before, :saving_message
|
||||
# def saving_message
|
||||
# puts "saving..."
|
||||
# end
|
||||
#
|
||||
# save_callback :after do |object|
|
||||
# set_callback :save, :after do |object|
|
||||
# puts "saved"
|
||||
# end
|
||||
#
|
||||
# def save
|
||||
# _run_save_callbacks do
|
||||
# _run_set_callback :save,s do
|
||||
# puts "- save"
|
||||
# end
|
||||
# end
|
||||
@@ -47,24 +47,24 @@ module ActiveSupport
|
||||
#
|
||||
# define_callbacks :save
|
||||
#
|
||||
# save_callback :before, :prepare
|
||||
# set_callback :save, :before, :prepare
|
||||
# def prepare
|
||||
# puts "preparing save"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# class ConfigStorage < Storage
|
||||
# save_callback :before, :saving_message
|
||||
# set_callback :save, :before, :saving_message
|
||||
# def saving_message
|
||||
# puts "saving..."
|
||||
# end
|
||||
#
|
||||
# save_callback :after do |object|
|
||||
# set_callback :save, :after do |object|
|
||||
# puts "saved"
|
||||
# end
|
||||
#
|
||||
# def save
|
||||
# _run_save_callbacks do
|
||||
# _run_set_callback :save,s do
|
||||
# puts "- save"
|
||||
# end
|
||||
# end
|
||||
@@ -78,22 +78,23 @@ module ActiveSupport
|
||||
# saving...
|
||||
# - save
|
||||
# saved
|
||||
#
|
||||
module NewCallbacks
|
||||
def self.included(klass)
|
||||
klass.extend ClassMethods
|
||||
end
|
||||
|
||||
|
||||
def run_callbacks(kind, options = {}, &blk)
|
||||
send("_run_#{kind}_callbacks", &blk)
|
||||
end
|
||||
|
||||
|
||||
class Callback
|
||||
@@_callback_sequence = 0
|
||||
|
||||
attr_accessor :filter, :kind, :name, :options, :per_key, :klass
|
||||
def initialize(filter, kind, options, klass)
|
||||
@kind, @klass = kind, klass
|
||||
|
||||
|
||||
attr_accessor :name, :filter, :kind, :options, :per_key, :klass
|
||||
|
||||
def initialize(name, filter, kind, options, klass)
|
||||
@name, @kind, @klass = name, kind, klass
|
||||
normalize_options!(options)
|
||||
|
||||
@per_key = options.delete(:per_key)
|
||||
@@ -104,9 +105,10 @@ module ActiveSupport
|
||||
|
||||
_compile_per_key_options
|
||||
end
|
||||
|
||||
|
||||
def clone(klass)
|
||||
obj = super()
|
||||
obj.name = name
|
||||
obj.klass = klass
|
||||
obj.per_key = @per_key.dup
|
||||
obj.options = @options.dup
|
||||
@@ -114,36 +116,39 @@ module ActiveSupport
|
||||
obj.per_key[:unless] = @per_key[:unless].dup
|
||||
obj.options[:if] = @options[:if].dup
|
||||
obj.options[:unless] = @options[:unless].dup
|
||||
obj.options[:scope] = @options[:scope].dup
|
||||
obj
|
||||
end
|
||||
|
||||
|
||||
def normalize_options!(options)
|
||||
options[:if] = Array.wrap(options[:if])
|
||||
options[:unless] = Array.wrap(options[:unless])
|
||||
|
||||
options[:scope] ||= [:kind]
|
||||
options[:scope] = Array.wrap(options[:scope])
|
||||
|
||||
options[:per_key] ||= {}
|
||||
options[:per_key][:if] = Array.wrap(options[:per_key][:if])
|
||||
options[:per_key][:unless] = Array.wrap(options[:per_key][:unless])
|
||||
end
|
||||
|
||||
|
||||
def next_id
|
||||
@@_callback_sequence += 1
|
||||
end
|
||||
|
||||
|
||||
def matches?(_kind, _filter)
|
||||
@kind == _kind &&
|
||||
@filter == _filter
|
||||
@kind == _kind && @filter == _filter
|
||||
end
|
||||
|
||||
def _update_filter(filter_options, new_options)
|
||||
filter_options[:if].push(new_options[:unless]) if new_options.key?(:unless)
|
||||
filter_options[:unless].push(new_options[:if]) if new_options.key?(:if)
|
||||
end
|
||||
|
||||
|
||||
def recompile!(_options, _per_key)
|
||||
_update_filter(self.options, _options)
|
||||
_update_filter(self.per_key, _per_key)
|
||||
|
||||
|
||||
@callback_id = next_id
|
||||
@filter = _compile_filter(@raw_filter)
|
||||
@compiled_options = _compile_options(@options)
|
||||
@@ -164,14 +169,13 @@ module ActiveSupport
|
||||
# contents for after filters (for the forward pass).
|
||||
def start(key = nil, options = {})
|
||||
object, terminator = (options || {}).values_at(:object, :terminator)
|
||||
|
||||
return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
|
||||
|
||||
|
||||
terminator ||= false
|
||||
|
||||
|
||||
# options[0] is the compiled form of supplied conditions
|
||||
# options[1] is the "end" for the conditional
|
||||
|
||||
|
||||
if @kind == :before || @kind == :around
|
||||
if @kind == :before
|
||||
# if condition # before_save :filter_name, :if => :condition
|
||||
@@ -183,7 +187,7 @@ module ActiveSupport
|
||||
halted = (#{terminator})
|
||||
end
|
||||
RUBY_EVAL
|
||||
|
||||
|
||||
[@compiled_options[0], filter, @compiled_options[1]].compact.join("\n")
|
||||
else
|
||||
# Compile around filters with conditions into proxy methods
|
||||
@@ -200,7 +204,7 @@ module ActiveSupport
|
||||
# yield self
|
||||
# end
|
||||
# end
|
||||
|
||||
#
|
||||
name = "_conditional_callback_#{@kind}_#{next_id}"
|
||||
txt, line = <<-RUBY_EVAL, __LINE__ + 1
|
||||
def #{name}(halted)
|
||||
@@ -223,9 +227,8 @@ module ActiveSupport
|
||||
# before filters (for the backward pass).
|
||||
def end(key = nil, options = {})
|
||||
object = (options || {})[:object]
|
||||
|
||||
return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
|
||||
|
||||
|
||||
if @kind == :around || @kind == :after
|
||||
# if condition # after_save :filter_name, :if => :condition
|
||||
# filter_name
|
||||
@@ -237,28 +240,30 @@ module ActiveSupport
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
# Options support the same options as filters themselves (and support
|
||||
# symbols, string, procs, and objects), so compile a conditional
|
||||
# expression based on the options
|
||||
def _compile_options(options)
|
||||
def _compile_options(options)
|
||||
return [] if options[:if].empty? && options[:unless].empty?
|
||||
|
||||
|
||||
conditions = []
|
||||
|
||||
|
||||
unless options[:if].empty?
|
||||
conditions << Array.wrap(_compile_filter(options[:if]))
|
||||
end
|
||||
|
||||
|
||||
unless options[:unless].empty?
|
||||
conditions << Array.wrap(_compile_filter(options[:unless])).map {|f| "!#{f}"}
|
||||
end
|
||||
|
||||
|
||||
["if #{conditions.flatten.join(" && ")}", "end"]
|
||||
end
|
||||
|
||||
|
||||
# Filters support:
|
||||
#
|
||||
# Arrays:: Used in conditions. This is used to specify
|
||||
# multiple conditions. Used internally to
|
||||
# merge conditions from skip_* filters
|
||||
@@ -269,6 +274,7 @@ module ActiveSupport
|
||||
#
|
||||
# All of these objects are compiled into methods and handled
|
||||
# the same after this point:
|
||||
#
|
||||
# Arrays:: Merged together into a single filter
|
||||
# Symbols:: Already methods
|
||||
# Strings:: class_eval'ed into methods
|
||||
@@ -276,6 +282,7 @@ module ActiveSupport
|
||||
# Objects::
|
||||
# a method is created that calls the before_foo method
|
||||
# on the object.
|
||||
#
|
||||
def _compile_filter(filter)
|
||||
method_name = "_callback_#{@kind}_#{next_id}"
|
||||
case filter
|
||||
@@ -294,10 +301,11 @@ module ActiveSupport
|
||||
@klass.send(:define_method, "#{method_name}_object") { filter }
|
||||
|
||||
_normalize_legacy_filter(kind, filter)
|
||||
method_to_call = @options[:scope].map{ |s| s.is_a?(Symbol) ? send(s) : s }.join("_")
|
||||
|
||||
@klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
||||
def #{method_name}(&blk)
|
||||
#{method_name}_object.send(:#{kind}, self, &blk)
|
||||
#{method_name}_object.send(:#{method_to_call}, self, &blk)
|
||||
end
|
||||
RUBY_EVAL
|
||||
|
||||
@@ -318,59 +326,67 @@ module ActiveSupport
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# An Array with a compile method
|
||||
class CallbackChain < Array
|
||||
def initialize(symbol)
|
||||
attr_reader :symbol, :config
|
||||
|
||||
def initialize(symbol, config)
|
||||
@symbol = symbol
|
||||
@config = config
|
||||
end
|
||||
|
||||
def compile(key = nil, options = {})
|
||||
|
||||
def compile(key=nil, options={})
|
||||
options = config.merge(options)
|
||||
|
||||
method = []
|
||||
method << "value = nil"
|
||||
method << "halted = false"
|
||||
|
||||
each do |callback|
|
||||
method << callback.start(key, options)
|
||||
end
|
||||
method << "yield self if block_given? && !halted"
|
||||
|
||||
method << "value = yield if block_given? && !halted"
|
||||
|
||||
reverse_each do |callback|
|
||||
method << callback.end(key, options)
|
||||
end
|
||||
|
||||
method << "halted ? false : (block_given? ? value : true)"
|
||||
method.compact.join("\n")
|
||||
end
|
||||
|
||||
|
||||
def clone(klass)
|
||||
chain = CallbackChain.new(@symbol)
|
||||
chain = CallbackChain.new(@symbol, @config.dup)
|
||||
chain.push(*map {|c| c.clone(klass)})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
module ClassMethods
|
||||
CHAINS = {:before => :before, :around => :before, :after => :after}
|
||||
|
||||
# Make the _run_save_callbacks method. The generated method takes
|
||||
# Make the _run_set_callback :save method. The generated method takes
|
||||
# a block that it'll yield to. It'll call the before and around filters
|
||||
# in order, yield the block, and then run the after filters.
|
||||
#
|
||||
# _run_save_callbacks do
|
||||
# _run_set_callback :save do
|
||||
# save
|
||||
# end
|
||||
#
|
||||
# The _run_save_callbacks method can optionally take a key, which
|
||||
# The _run_set_callback :save,s method can optionally take a key, which
|
||||
# will be used to compile an optimized callback method for each
|
||||
# key. See #define_callbacks for more information.
|
||||
def _define_runner(symbol)
|
||||
body = send("_#{symbol}_callbacks").
|
||||
compile(nil, :terminator => send("_#{symbol}_terminator"))
|
||||
#
|
||||
def __define_runner(symbol) #:nodoc:
|
||||
body = send("_#{symbol}_callbacks").compile(nil)
|
||||
|
||||
body, line = <<-RUBY_EVAL, __LINE__
|
||||
def _run_#{symbol}_callbacks(key = nil, &blk)
|
||||
if key
|
||||
name = "_run__\#{self.class.name.hash.abs}__#{symbol}__\#{key.hash.abs}__callbacks"
|
||||
|
||||
|
||||
unless respond_to?(name)
|
||||
self.class._create_keyed_callback(name, :#{symbol}, self, &blk)
|
||||
self.class.__create_keyed_callback(name, :#{symbol}, self, &blk)
|
||||
end
|
||||
|
||||
send(name, &blk)
|
||||
@@ -379,7 +395,7 @@ module ActiveSupport
|
||||
end
|
||||
end
|
||||
RUBY_EVAL
|
||||
|
||||
|
||||
undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks")
|
||||
class_eval body, __FILE__, line
|
||||
end
|
||||
@@ -387,32 +403,36 @@ module ActiveSupport
|
||||
# This is called the first time a callback is called with a particular
|
||||
# key. It creates a new callback method for the key, calculating
|
||||
# which callbacks can be omitted because of per_key conditions.
|
||||
def _create_keyed_callback(name, kind, obj, &blk)
|
||||
#
|
||||
def __create_keyed_callback(name, kind, obj, &blk) #:nodoc:
|
||||
@_keyed_callbacks ||= {}
|
||||
@_keyed_callbacks[name] ||= begin
|
||||
str = send("_#{kind}_callbacks").
|
||||
compile(name, :object => obj, :terminator => send("_#{kind}_terminator"))
|
||||
|
||||
str = send("_#{kind}_callbacks").compile(name, :object => obj)
|
||||
class_eval "def #{name}() #{str} end", __FILE__, __LINE__
|
||||
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def __update_callbacks(name, filters = CallbackChain.new(name, {}), block = nil)
|
||||
type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
|
||||
options = filters.last.is_a?(Hash) ? filters.pop : {}
|
||||
filters.unshift(block) if block
|
||||
|
||||
callbacks = send("_#{name}_callbacks")
|
||||
yield callbacks, type, filters, options if block_given?
|
||||
|
||||
__define_runner(name)
|
||||
end
|
||||
|
||||
# Define callbacks.
|
||||
#
|
||||
# Creates a <name>_callback method that you can use to add callbacks.
|
||||
#
|
||||
# Syntax:
|
||||
# save_callback :before, :before_meth
|
||||
# save_callback :after, :after_meth, :if => :condition
|
||||
# save_callback :around {|r| stuff; yield; stuff }
|
||||
# set_callback :save, :before, :before_meth
|
||||
# set_callback :save, :after, :after_meth, :if => :condition
|
||||
# set_callback :save, :around {|r| stuff; yield; stuff }
|
||||
#
|
||||
# The <name>_callback method also updates the _run_<name>_callbacks
|
||||
# method, which is the public API to run the callbacks.
|
||||
#
|
||||
# Also creates a skip_<name>_callback method that you can use to skip
|
||||
# callbacks.
|
||||
# It also updates the _run_<name>_callbacks method, which is the public
|
||||
# API to run the callbacks. Use skip_callback to skip any defined one.
|
||||
#
|
||||
# When creating or skipping callbacks, you can specify conditions that
|
||||
# are always the same for a given key. For instance, in ActionPack,
|
||||
@@ -430,25 +450,12 @@ module ActiveSupport
|
||||
# In that case, each action_name would get its own compiled callback
|
||||
# method that took into consideration the per_key conditions. This
|
||||
# is a speed improvement for ActionPack.
|
||||
def _update_callbacks(name, filters = CallbackChain.new(name), block = nil)
|
||||
type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
|
||||
options = filters.last.is_a?(Hash) ? filters.pop : {}
|
||||
filters.unshift(block) if block
|
||||
|
||||
callbacks = send("_#{name}_callbacks")
|
||||
yield callbacks, type, filters, options if block_given?
|
||||
|
||||
_define_runner(name)
|
||||
end
|
||||
|
||||
alias_method :_reset_callbacks, :_update_callbacks
|
||||
|
||||
#
|
||||
def set_callback(name, *filters, &block)
|
||||
_update_callbacks(name, filters, block) do |callbacks, type, filters, options|
|
||||
__update_callbacks(name, filters, block) do |callbacks, type, filters, options|
|
||||
filters.map! do |filter|
|
||||
# overrides parent class
|
||||
callbacks.delete_if {|c| c.matches?(type, filter) }
|
||||
Callback.new(filter, type, options.dup, self)
|
||||
Callback.new(name, filter, type, options.merge(callbacks.config), self)
|
||||
end
|
||||
|
||||
options[:prepend] ? callbacks.unshift(*filters) : callbacks.push(*filters)
|
||||
@@ -456,10 +463,9 @@ module ActiveSupport
|
||||
end
|
||||
|
||||
def skip_callback(name, *filters, &block)
|
||||
_update_callbacks(name, filters, block) do |callbacks, type, filters, options|
|
||||
__update_callbacks(name, filters, block) do |callbacks, type, filters, options|
|
||||
filters.each do |filter|
|
||||
callbacks = send("_#{name}_callbacks=", callbacks.clone(self))
|
||||
|
||||
filter = callbacks.find {|c| c.matches?(type, filter) }
|
||||
|
||||
if filter && options.any?
|
||||
@@ -471,16 +477,19 @@ module ActiveSupport
|
||||
end
|
||||
end
|
||||
|
||||
def define_callbacks(*symbols)
|
||||
terminator = symbols.pop if symbols.last.is_a?(String)
|
||||
symbols.each do |symbol|
|
||||
extlib_inheritable_accessor("_#{symbol}_terminator") { terminator }
|
||||
def reset_callbacks(symbol)
|
||||
send("_#{symbol}_callbacks").clear
|
||||
__define_runner(symbol)
|
||||
end
|
||||
|
||||
def define_callbacks(*symbols)
|
||||
config = symbols.last.is_a?(Hash) ? symbols.pop : {}
|
||||
symbols.each do |symbol|
|
||||
extlib_inheritable_accessor("_#{symbol}_callbacks") do
|
||||
CallbackChain.new(symbol)
|
||||
CallbackChain.new(symbol, config)
|
||||
end
|
||||
|
||||
_define_runner(symbol)
|
||||
__define_runner(symbol)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,6 +8,9 @@ $:.unshift "#{File.dirname(__FILE__)}/../lib"
|
||||
require 'active_support'
|
||||
require 'active_support/test_case'
|
||||
|
||||
# Include shims until we get off 1.8.6
|
||||
require 'active_support/ruby/shim'
|
||||
|
||||
def uses_memcached(test_name)
|
||||
require 'memcache'
|
||||
begin
|
||||
|
||||
@@ -180,6 +180,10 @@ module NewCallbacksTest
|
||||
end
|
||||
end
|
||||
|
||||
class CleanPerson < ConditionalPerson
|
||||
reset_callbacks :save
|
||||
end
|
||||
|
||||
class MySuper
|
||||
include ActiveSupport::NewCallbacks
|
||||
define_callbacks :save
|
||||
@@ -349,10 +353,18 @@ module NewCallbacksTest
|
||||
end
|
||||
end
|
||||
|
||||
class ResetCallbackTest < Test::Unit::TestCase
|
||||
def test_save_conditional_person
|
||||
person = CleanPerson.new
|
||||
person.save
|
||||
assert_equal [], person.history
|
||||
end
|
||||
end
|
||||
|
||||
class CallbackTerminator
|
||||
include ActiveSupport::NewCallbacks
|
||||
|
||||
define_callbacks :save, "result == :halt"
|
||||
define_callbacks :save, :terminator => "result == :halt"
|
||||
|
||||
set_callback :save, :before, :first
|
||||
set_callback :save, :before, :second
|
||||
@@ -400,7 +412,11 @@ module NewCallbacksTest
|
||||
def before(caller)
|
||||
caller.record << "before"
|
||||
end
|
||||
|
||||
|
||||
def before_save(caller)
|
||||
caller.record << "before save"
|
||||
end
|
||||
|
||||
def around(caller)
|
||||
caller.record << "around before"
|
||||
yield
|
||||
@@ -410,15 +426,15 @@ module NewCallbacksTest
|
||||
|
||||
class UsingObjectBefore
|
||||
include ActiveSupport::NewCallbacks
|
||||
|
||||
|
||||
define_callbacks :save
|
||||
set_callback :save, :before, CallbackObject.new
|
||||
|
||||
|
||||
attr_accessor :record
|
||||
def initialize
|
||||
@record = []
|
||||
end
|
||||
|
||||
|
||||
def save
|
||||
_run_save_callbacks do
|
||||
@record << "yielded"
|
||||
@@ -443,19 +459,49 @@ module NewCallbacksTest
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class CustomScopeObject
|
||||
include ActiveSupport::NewCallbacks
|
||||
|
||||
define_callbacks :save, :scope => [:kind, :name]
|
||||
set_callback :save, :before, CallbackObject.new
|
||||
|
||||
attr_accessor :record
|
||||
def initialize
|
||||
@record = []
|
||||
end
|
||||
|
||||
def save
|
||||
_run_save_callbacks do
|
||||
@record << "yielded"
|
||||
"CallbackResult"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class UsingObjectTest < Test::Unit::TestCase
|
||||
def test_before_object
|
||||
u = UsingObjectBefore.new
|
||||
u.save
|
||||
assert_equal ["before", "yielded"], u.record
|
||||
end
|
||||
|
||||
|
||||
def test_around_object
|
||||
u = UsingObjectAround.new
|
||||
u.save
|
||||
assert_equal ["around before", "yielded", "around after"], u.record
|
||||
end
|
||||
end
|
||||
|
||||
def test_customized_object
|
||||
u = CustomScopeObject.new
|
||||
u.save
|
||||
assert_equal ["before save", "yielded"], u.record
|
||||
end
|
||||
|
||||
def test_block_result_is_returned
|
||||
u = CustomScopeObject.new
|
||||
assert_equal "CallbackResult", u.save
|
||||
end
|
||||
end
|
||||
|
||||
class CallbackTerminatorTest < Test::Unit::TestCase
|
||||
@@ -469,7 +515,7 @@ module NewCallbacksTest
|
||||
obj = CallbackTerminator.new
|
||||
obj.save
|
||||
assert !obj.saved
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class HyphenatedKeyTest < Test::Unit::TestCase
|
||||
@@ -477,6 +523,6 @@ module NewCallbacksTest
|
||||
obj = HyphenatedCallbacks.new
|
||||
obj.save
|
||||
assert_equal obj.stuff, "OMG"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -21,6 +21,7 @@ cd "#{root_dir}/activesupport" do
|
||||
puts "[CruiseControl] Building ActiveSupport"
|
||||
puts
|
||||
build_results[:activesupport] = system 'rake'
|
||||
build_results[:activesupport_isolated] = system 'rake isolated_test'
|
||||
end
|
||||
|
||||
rm_f "#{root_dir}/activerecord/debug.log"
|
||||
|
||||
Reference in New Issue
Block a user