aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
authorPratik Naik <pratiknaik@gmail.com>2009-09-21 21:14:04 +0100
committerPratik Naik <pratiknaik@gmail.com>2009-09-21 21:14:04 +0100
commit340be9bddd8e5902e0218a0101a40a17a4afd558 (patch)
treeef4de25f3f8eb610dc2235f0762b01cb1d464efd /activerecord/lib
parentb31cdb55422226cd45a2234a4b54986f1f611151 (diff)
parent1bbb9b2db05730194edfd7d2cef9f5fcb9d79e50 (diff)
downloadrails-340be9bddd8e5902e0218a0101a40a17a4afd558.tar.gz
rails-340be9bddd8e5902e0218a0101a40a17a4afd558.tar.bz2
rails-340be9bddd8e5902e0218a0101a40a17a4afd558.zip
Merge commit 'mainstream/master'
Diffstat (limited to 'activerecord/lib')
-rwxr-xr-xactiverecord/lib/active_record/associations.rb62
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb4
-rw-r--r--activerecord/lib/active_record/autosave_association.rb52
-rwxr-xr-xactiverecord/lib/active_record/base.rb61
-rw-r--r--activerecord/lib/active_record/callbacks.rb222
-rwxr-xr-xactiverecord/lib/active_record/connection_adapters/abstract_adapter.rb22
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb8
-rw-r--r--activerecord/lib/active_record/locking/pessimistic.rb22
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb60
-rw-r--r--activerecord/lib/active_record/observer.rb20
-rw-r--r--activerecord/lib/active_record/validations.rb35
-rw-r--r--activerecord/lib/activerecord.rb1
12 files changed, 261 insertions, 308 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index f494e38e2f..266a52d612 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -280,9 +280,10 @@ module ActiveRecord
# You can manipulate objects and associations before they are saved to the database, but there is some special behavior you should be
# aware of, mostly involving the saving of associated objects.
#
- # Unless you enable the :autosave option on a <tt>has_one</tt>, <tt>belongs_to</tt>,
- # <tt>has_many</tt>, or <tt>has_and_belongs_to_many</tt> association,
- # in which case the members are always saved.
+ # Unless you set the :autosave option on a <tt>has_one</tt>, <tt>belongs_to</tt>,
+ # <tt>has_many</tt>, or <tt>has_and_belongs_to_many</tt> association. Setting it
+ # to +true+ will _always_ save the members, whereas setting it to +false+ will
+ # _never_ save the members.
#
# === One-to-one associations
#
@@ -1491,24 +1492,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
@@ -1656,7 +1676,7 @@ module ActiveRecord
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)
-
+
if reflection.association_foreign_key == reflection.primary_key_name
raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(reflection)
end
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index d91c555dad..417e2fdc0f 100644
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -24,8 +24,8 @@ module ActiveRecord
def has_primary_key?
return @has_primary_key unless @has_primary_key.nil?
- @has_primary_key = (ActiveRecord::Base.connection.supports_primary_key? &&
- ActiveRecord::Base.connection.primary_key(@reflection.options[:join_table]))
+ @has_primary_key = (@owner.connection.supports_primary_key? &&
+ @owner.connection.primary_key(@reflection.options[:join_table]))
end
protected
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index c1bc8423a9..8f37fcd515 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -158,7 +158,7 @@ module ActiveRecord
def add_autosave_association_callbacks(reflection)
save_method = "autosave_associated_records_for_#{reflection.name}"
validation_method = "validate_associated_records_for_#{reflection.name}"
- validate validation_method
+ force_validation = (reflection.options[:validate] == true || reflection.options[:autosave] == true)
case reflection.macro
when :has_many, :has_and_belongs_to_many
@@ -169,7 +169,10 @@ module ActiveRecord
after_create save_method
after_update save_method
- define_method(validation_method) { validate_collection_association(reflection) }
+ if force_validation || (reflection.macro == :has_many && reflection.options[:validate] != false)
+ define_method(validation_method) { validate_collection_association(reflection) }
+ validate validation_method
+ end
else
case reflection.macro
when :has_one
@@ -179,7 +182,11 @@ module ActiveRecord
define_method(save_method) { save_belongs_to_association(reflection) }
before_save save_method
end
- define_method(validation_method) { validate_single_association(reflection) }
+
+ if force_validation
+ define_method(validation_method) { validate_single_association(reflection) }
+ validate validation_method
+ end
end
end
end
@@ -223,10 +230,8 @@ module ActiveRecord
# Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
# turned on for the association specified by +reflection+.
def validate_single_association(reflection)
- if reflection.options[:validate] == true || reflection.options[:autosave] == true
- if (association = association_instance_get(reflection.name)) && !association.target.nil?
- association_valid?(reflection, association)
- end
+ if (association = association_instance_get(reflection.name)) && !association.target.nil?
+ association_valid?(reflection, association)
end
end
@@ -234,7 +239,7 @@ module ActiveRecord
# <tt>:autosave</tt> is turned on for the association specified by
# +reflection+.
def validate_collection_association(reflection)
- if reflection.options[:validate] != false && association = association_instance_get(reflection.name)
+ if association = association_instance_get(reflection.name)
if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
records.each { |record| association_valid?(reflection, record) }
end
@@ -243,15 +248,15 @@ module ActiveRecord
# Returns whether or not the association is valid and applies any errors to
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
- # enabled records if they're marked_for_destruction?.
+ # enabled records if they're marked_for_destruction? or destroyed.
def association_valid?(reflection, association)
+ return true if association.destroyed? || association.marked_for_destruction?
+
unless valid = association.valid?
if reflection.options[:autosave]
- unless association.marked_for_destruction?
- association.errors.each do |attribute, message|
- attribute = "#{reflection.name}_#{attribute}"
- errors[attribute] << message if errors[attribute].empty?
- end
+ association.errors.each do |attribute, message|
+ attribute = "#{reflection.name}_#{attribute}"
+ errors[attribute] << message if errors[attribute].empty?
end
else
errors.add(reflection.name)
@@ -281,9 +286,11 @@ module ActiveRecord
if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
records.each do |record|
+ next if record.destroyed?
+
if autosave && record.marked_for_destruction?
association.destroy(record)
- elsif @new_record_before_save || record.new_record?
+ elsif autosave != false && (@new_record_before_save || record.new_record?)
if autosave
association.send(:insert_record, record, false, false)
else
@@ -309,14 +316,17 @@ module ActiveRecord
# This all happens inside a transaction, _if_ the Transactions module is included into
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
def save_has_one_association(reflection)
- if (association = association_instance_get(reflection.name)) && !association.target.nil?
+ if (association = association_instance_get(reflection.name)) && !association.target.nil? && !association.destroyed?
autosave = reflection.options[:autosave]
if autosave && association.marked_for_destruction?
association.destroy
- elsif new_record? || association.new_record? || association[reflection.primary_key_name] != id || autosave
- association[reflection.primary_key_name] = id
- association.save(!autosave)
+ else
+ key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
+ if autosave != false && (new_record? || association.new_record? || association[reflection.primary_key_name] != key || autosave)
+ association[reflection.primary_key_name] = key
+ association.save(!autosave)
+ end
end
end
end
@@ -330,12 +340,12 @@ module ActiveRecord
# This all happens inside a transaction, _if_ the Transactions module is included into
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
def save_belongs_to_association(reflection)
- if association = association_instance_get(reflection.name)
+ if (association = association_instance_get(reflection.name)) && !association.destroyed?
autosave = reflection.options[:autosave]
if autosave && association.marked_for_destruction?
association.destroy
- else
+ elsif autosave != false
association.save(!autosave) if association.new_record? || autosave
if association.updated?
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index c5be561ea3..502fe0442e 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -1648,46 +1648,31 @@ module ActiveRecord #:nodoc:
# single-table inheritance model that makes it possible to create
# objects of different types from the same table.
def instantiate(record)
- object =
- if subclass_name = record[inheritance_column]
- # No type given.
- if subclass_name.empty?
- allocate
+ object = find_sti_class(record[inheritance_column]).allocate
- else
- # Ignore type if no column is present since it was probably
- # pulled in from a sloppy join.
- unless columns_hash.include?(inheritance_column)
- allocate
+ object.instance_variable_set(:'@attributes', record)
+ object.instance_variable_set(:'@attributes_cache', {})
- else
- begin
- compute_type(subclass_name).allocate
- rescue NameError
- raise SubclassNotFound,
- "The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
- "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
- "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
- "or overwrite #{self.to_s}.inheritance_column to use another column for that information."
- end
- end
- end
- else
- allocate
- end
+ object.send(:_run_find_callbacks)
+ object.send(:_run_initialize_callbacks)
- 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
+ object
+ end
- if object.respond_to_without_attributes?(:after_initialize)
- object.send(:callback, :after_initialize)
+ def find_sti_class(type_name)
+ if type_name.blank? || !columns_hash.include?(inheritance_column)
+ self
+ else
+ begin
+ compute_type(type_name)
+ rescue NameError
+ raise SubclassNotFound,
+ "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " +
+ "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
+ "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
+ "or overwrite #{name}.inheritance_column to use another column for that information."
+ end
end
-
- object
end
# Nest the type name in the same module as this class.
@@ -2438,7 +2423,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
@@ -2943,7 +2928,9 @@ module ActiveRecord #:nodoc:
end
def log_protected_attribute_removal(*attributes)
- logger.debug "WARNING: Can't mass-assign these protected attributes: #{attributes.join(', ')}"
+ if logger
+ logger.debug "WARNING: Can't mass-assign these protected attributes: #{attributes.join(', ')}"
+ end
end
# The primary key and inheritance column can never be set by mass-assignment for security reasons.
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 4a2ec5bf95..40a25811c4 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -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
+ 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
- # Is called after the object has been instantiated by a call to <tt>Base.new</tt>.
- #def after_initialize() end
+ def around_#{callback}(*args, &block)
+ set_callback(:#{callback}, :around, *args, &block)
+ end
- # Is called _before_ <tt>Base.save</tt> (regardless of whether it's a +create+ or +update+ save).
- def before_save() 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
- # 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)
+ _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
+ @_on_validate = new_record? ? :create : :update
+ _run_validation_callbacks do
+ valid_without_callbacks?
+ end
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
+ _run_destroy_callbacks do
+ destroy_without_callbacks
+ end
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
+ 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
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index fab70f34b9..78c7a4b697 100755
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -201,16 +201,12 @@ module ActiveRecord
protected
def log(sql, name)
- if block_given?
- result = nil
- ms = Benchmark.ms { result = yield }
- @runtime += ms
- log_info(sql, name, ms)
- result
- else
- log_info(sql, name, 0)
- nil
+ event = ActiveSupport::Orchestra.instrument(:sql, :sql => sql, :name => name) do
+ yield if block_given?
end
+ @runtime += event.duration
+ log_info(sql, name, event.duration)
+ event.result
rescue Exception => e
# Log message and raise exception.
# Set last_verification to 0, so that connection gets verified
@@ -221,10 +217,10 @@ module ActiveRecord
raise translate_exception(e, message)
end
- def translate_exception(e, message)
- # override in derived class
- ActiveRecord::StatementInvalid.new(message)
- end
+ def translate_exception(e, message)
+ # override in derived class
+ ActiveRecord::StatementInvalid.new(message)
+ end
def format_log_entry(message, dump = nil)
if ActiveRecord::Base.colorize_logging
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 4edb64c2c0..1072eb7ac1 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -8,7 +8,8 @@ module MysqlCompat #:nodoc:
raise 'Mysql not loaded' unless defined?(::Mysql)
target = defined?(Mysql::Result) ? Mysql::Result : MysqlRes
- return if target.instance_methods.include?('all_hashes')
+ return if target.instance_methods.include?('all_hashes') ||
+ target.instance_methods.include?(:all_hashes)
# Ruby driver has a version string and returns null values in each_hash
# C driver >= 2.7 returns null values in each_hash
@@ -64,12 +65,15 @@ module ActiveRecord
raise
end
end
+
MysqlCompat.define_all_hashes_method!
mysql = Mysql.init
mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslca] || config[:sslkey]
- ConnectionAdapters::MysqlAdapter.new(mysql, logger, [host, username, password, database, port, socket], config)
+ default_flags = Mysql.const_defined?(:CLIENT_MULTI_RESULTS) ? Mysql::CLIENT_MULTI_RESULTS : 0
+ options = [host, username, password, database, port, socket, default_flags]
+ ConnectionAdapters::MysqlAdapter.new(mysql, logger, options, config)
end
end
diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb
index 320659596f..fcc9ebb4af 100644
--- a/activerecord/lib/active_record/locking/pessimistic.rb
+++ b/activerecord/lib/active_record/locking/pessimistic.rb
@@ -1,25 +1,3 @@
-# Copyright (c) 2006 Shugo Maeda <shugo@ruby-lang.org>
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject
-# to the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
-# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
-# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-
module ActiveRecord
module Locking
# Locking::Pessimistic provides support for row-level locking using
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index de48b10c1c..cd3b182b09 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -66,10 +66,10 @@ module ActiveRecord
# accepts_nested_attributes_for :avatar, :allow_destroy => true
# end
#
- # Now, when you add the <tt>_delete</tt> key to the attributes hash, with a
+ # Now, when you add the <tt>_destroy</tt> key to the attributes hash, with a
# value that evaluates to +true+, you will destroy the associated model:
#
- # member.avatar_attributes = { :id => '2', :_delete => '1' }
+ # member.avatar_attributes = { :id => '2', :_destroy => '1' }
# member.avatar.marked_for_destruction? # => true
# member.save
# member.reload.avatar #=> nil
@@ -89,14 +89,14 @@ module ActiveRecord
# the attribute hash.
#
# For each hash that does _not_ have an <tt>id</tt> key a new record will
- # be instantiated, unless the hash also contains a <tt>_delete</tt> key
+ # be instantiated, unless the hash also contains a <tt>_destroy</tt> key
# that evaluates to +true+.
#
# params = { :member => {
# :name => 'joe', :posts_attributes => [
# { :title => 'Kari, the awesome Ruby documentation browser!' },
# { :title => 'The egalitarian assumption of the modern citizen' },
- # { :title => '', :_delete => '1' } # this will be ignored
+ # { :title => '', :_destroy => '1' } # this will be ignored
# ]
# }}
#
@@ -144,7 +144,7 @@ module ActiveRecord
# By default the associated records are protected from being destroyed. If
# you want to destroy any of the associated records through the attributes
# hash, you have to enable it first using the <tt>:allow_destroy</tt>
- # option. This will allow you to also use the <tt>_delete</tt> key to
+ # option. This will allow you to also use the <tt>_destroy</tt> key to
# destroy existing records:
#
# class Member < ActiveRecord::Base
@@ -153,7 +153,7 @@ module ActiveRecord
# end
#
# params = { :member => {
- # :posts_attributes => [{ :id => '2', :_delete => '1' }]
+ # :posts_attributes => [{ :id => '2', :_destroy => '1' }]
# }}
#
# member.attributes = params['member']
@@ -176,14 +176,14 @@ module ActiveRecord
# Supported options:
# [:allow_destroy]
# If true, destroys any members from the attributes hash with a
- # <tt>_delete</tt> key and a value that evaluates to +true+
+ # <tt>_destroy</tt> key and a value that evaluates to +true+
# (eg. 1, '1', true, or 'true'). This option is off by default.
# [:reject_if]
# Allows you to specify a Proc that checks whether a record should be
# built for a certain attribute hash. The hash is passed to the Proc
# and the Proc should return either +true+ or +false+. When no Proc
# is specified a record will be built for all attribute hashes that
- # do not have a <tt>_delete</tt> that evaluates to true.
+ # do not have a <tt>_destroy</tt> value that evaluates to true.
# Passing <tt>:all_blank</tt> instead of a Proc will create a proc
# that will reject a record where all the attributes are blank.
#
@@ -236,15 +236,25 @@ module ActiveRecord
# destruction of this association.
#
# See ActionView::Helpers::FormHelper::fields_for for more info.
- def _delete
+ def _destroy
marked_for_destruction?
end
+ # Deal with deprecated _delete.
+ #
+ def _delete #:nodoc:
+ ActiveSupport::Deprecation.warn "_delete is deprecated in nested attributes. Use _destroy instead."
+ _destroy
+ end
+
private
# Attribute hash keys that should not be assigned as normal attributes.
# These hash keys are nested attributes implementation details.
- UNASSIGNABLE_KEYS = %w{ id _delete }
+ #
+ # TODO Remove _delete from UNASSIGNABLE_KEYS when deprecation warning are
+ # removed.
+ UNASSIGNABLE_KEYS = %w( id _destroy _delete )
# Assigns the given attributes to the association.
#
@@ -253,14 +263,19 @@ module ActiveRecord
# record will be built.
#
# If the given attributes include a matching <tt>:id</tt> attribute _and_ a
- # <tt>:_delete</tt> key set to a truthy value, then the existing record
+ # <tt>:_destroy</tt> key set to a truthy value, then the existing record
# will be marked for destruction.
def assign_nested_attributes_for_one_to_one_association(association_name, attributes, allow_destroy)
attributes = attributes.stringify_keys
if attributes['id'].blank?
unless reject_new_record?(association_name, attributes)
- send("build_#{association_name}", attributes.except(*UNASSIGNABLE_KEYS))
+ method = "build_#{association_name}"
+ if respond_to?(method)
+ send(method, attributes.except(*UNASSIGNABLE_KEYS))
+ else
+ raise ArgumentError, "Cannot build association #{association_name}. Are you trying to build a polymorphic one-to-one association?"
+ end
end
elsif (existing_record = send(association_name)) && existing_record.id.to_s == attributes['id'].to_s
assign_to_or_mark_for_destruction(existing_record, attributes, allow_destroy)
@@ -272,7 +287,7 @@ module ActiveRecord
# Hashes with an <tt>:id</tt> value matching an existing associated record
# will update that record. Hashes without an <tt>:id</tt> value will build
# a new record for the association. Hashes with a matching <tt>:id</tt>
- # value and a <tt>:_delete</tt> key set to a truthy value will mark the
+ # value and a <tt>:_destroy</tt> key set to a truthy value will mark the
# matched record for destruction.
#
# For example:
@@ -280,7 +295,7 @@ module ActiveRecord
# assign_nested_attributes_for_collection_association(:people, {
# '1' => { :id => '1', :name => 'Peter' },
# '2' => { :name => 'John' },
- # '3' => { :id => '2', :_delete => true }
+ # '3' => { :id => '2', :_destroy => true }
# })
#
# Will update the name of the Person with ID 1, build a new associated
@@ -292,7 +307,7 @@ module ActiveRecord
# assign_nested_attributes_for_collection_association(:people, [
# { :id => '1', :name => 'Peter' },
# { :name => 'John' },
- # { :id => '2', :_delete => true }
+ # { :id => '2', :_destroy => true }
# ])
def assign_nested_attributes_for_collection_association(association_name, attributes_collection, allow_destroy)
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
@@ -317,25 +332,26 @@ module ActiveRecord
end
# Updates a record with the +attributes+ or marks it for destruction if
- # +allow_destroy+ is +true+ and has_delete_flag? returns +true+.
+ # +allow_destroy+ is +true+ and has_destroy_flag? returns +true+.
def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
- if has_delete_flag?(attributes) && allow_destroy
+ if has_destroy_flag?(attributes) && allow_destroy
record.mark_for_destruction
else
record.attributes = attributes.except(*UNASSIGNABLE_KEYS)
end
end
- # Determines if a hash contains a truthy _delete key.
- def has_delete_flag?(hash)
- ConnectionAdapters::Column.value_to_boolean hash['_delete']
+ # Determines if a hash contains a truthy _destroy key.
+ def has_destroy_flag?(hash)
+ ConnectionAdapters::Column.value_to_boolean(hash['_destroy']) ||
+ ConnectionAdapters::Column.value_to_boolean(hash['_delete']) # TODO Remove after deprecation.
end
# Determines if a new record should be build by checking for
- # has_delete_flag? or if a <tt>:reject_if</tt> proc exists for this
+ # has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
# association and evaluates to +true+.
def reject_new_record?(association_name, attributes)
- has_delete_flag?(attributes) ||
+ has_destroy_flag?(attributes) ||
self.class.reject_new_nested_attributes_procs[association_name].try(:call, attributes)
end
end
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
index a34ff4a47a..4e05b819b5 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -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
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 5fc41cf054..ab79b520a2 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -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?
diff --git a/activerecord/lib/activerecord.rb b/activerecord/lib/activerecord.rb
deleted file mode 100644
index cd62b2afdc..0000000000
--- a/activerecord/lib/activerecord.rb
+++ /dev/null
@@ -1 +0,0 @@
-require 'active_record'