aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwycats <wycats@gmail.com>2010-05-09 02:06:05 +0300
committerwycats <wycats@gmail.com>2010-05-09 02:37:52 +0300
commitd916c62cfc7c59ab6411407a05b946d3dd7535e9 (patch)
tree653ac4017ea6af4520dd9e6f60d846e19d5e3074
parent636ffa1f089a51c98fce616191846eaba93d7b87 (diff)
downloadrails-d916c62cfc7c59ab6411407a05b946d3dd7535e9.tar.gz
rails-d916c62cfc7c59ab6411407a05b946d3dd7535e9.tar.bz2
rails-d916c62cfc7c59ab6411407a05b946d3dd7535e9.zip
eliminate alias_method_chain from ActiveRecord
-rw-r--r--activerecord/lib/active_record.rb2
-rw-r--r--activerecord/lib/active_record/aggregations.rb1
-rwxr-xr-xactiverecord/lib/active_record/associations.rb16
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb11
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb100
-rw-r--r--activerecord/lib/active_record/autosave_association.rb6
-rwxr-xr-xactiverecord/lib/active_record/base.rb331
-rw-r--r--activerecord/lib/active_record/callbacks.rb48
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb27
-rw-r--r--activerecord/lib/active_record/counter_cache.rb107
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb27
-rw-r--r--activerecord/lib/active_record/persistence.rb230
-rw-r--r--activerecord/lib/active_record/timestamp.rb48
-rw-r--r--activerecord/lib/active_record/transactions.rb22
-rw-r--r--activerecord/lib/active_record/validations.rb71
-rw-r--r--activerecord/test/cases/locking_test.rb9
16 files changed, 514 insertions, 542 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 6a6485f35e..e2f2508ae8 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -61,6 +61,7 @@ module ActiveRecord
autoload :Base
autoload :Callbacks
+ autoload :CounterCache
autoload :DynamicFinderMatch
autoload :DynamicScopeMatch
autoload :Migration
@@ -68,6 +69,7 @@ module ActiveRecord
autoload :NamedScope
autoload :NestedAttributes
autoload :Observer
+ autoload :Persistence
autoload :QueryCache
autoload :Reflection
autoload :Schema
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 08389907ef..45aaea062d 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -253,6 +253,7 @@ module ActiveRecord
raise ArgumentError, 'Converter must be a symbol denoting the converter method to call or a Proc to be invoked.'
end
end
+
mapping.each { |pair| self[pair.first] = part.send(pair.last) }
instance_variable_set("@#{name}", part.freeze)
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 6c64210c92..0a3c7c6a60 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1304,14 +1304,14 @@ module ActiveRecord
# Don't use a before_destroy callback since users' before_destroy
# callbacks will be executed after the association is wiped out.
- old_method = "destroy_without_habtm_shim_for_#{reflection.name}"
- class_eval <<-end_eval unless method_defined?(old_method)
- alias_method :#{old_method}, :destroy_without_callbacks # alias_method :destroy_without_habtm_shim_for_posts, :destroy_without_callbacks
- def destroy_without_callbacks # def destroy_without_callbacks
- #{reflection.name}.clear # posts.clear
- #{old_method} # destroy_without_habtm_shim_for_posts
- end # end
- end_eval
+ include Module.new {
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def destroy # def destroy
+ super # super
+ #{reflection.name}.clear # posts.clear
+ end # end
+ RUBY
+ }
add_association_callbacks(reflection.name, options)
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 3a9a67e3a2..c117271c71 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -18,10 +18,19 @@ module ActiveRecord
def instance_method_already_implemented?(method_name)
method_name = method_name.to_s
@_defined_class_methods ||= ancestors.first(ancestors.index(ActiveRecord::Base)).sum([]) { |m| m.public_instance_methods(false) | m.private_instance_methods(false) | m.protected_instance_methods(false) }.map {|m| m.to_s }.to_set
- @@_defined_activerecord_methods ||= (ActiveRecord::Base.public_instance_methods(false) | ActiveRecord::Base.private_instance_methods(false) | ActiveRecord::Base.protected_instance_methods(false)).map{|m| m.to_s }.to_set
+ @@_defined_activerecord_methods ||= defined_activerecord_methods
raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name)
@_defined_class_methods.include?(method_name)
end
+
+ def defined_activerecord_methods
+ active_record = ActiveRecord::Base
+ super_klass = ActiveRecord::Base.superclass
+ methods = active_record.public_instance_methods - super_klass.public_instance_methods
+ methods += active_record.private_instance_methods - super_klass.private_instance_methods
+ methods += active_record.protected_instance_methods - super_klass.protected_instance_methods
+ methods.map {|m| m.to_s }.to_set
+ end
end
def method_missing(method_id, *args, &block)
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 36f2a9777c..dd44bd8d51 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -5,20 +5,20 @@ module ActiveRecord
module Dirty
extend ActiveSupport::Concern
include ActiveModel::Dirty
+ include AttributeMethods::Write
included do
- alias_method_chain :save, :dirty
- alias_method_chain :save!, :dirty
- alias_method_chain :update, :dirty
- alias_method_chain :reload, :dirty
+ if self < Timestamp
+ raise "You cannot include Dirty after Timestamp"
+ end
superclass_delegating_accessor :partial_updates
self.partial_updates = true
end
# Attempts to +save+ the record and clears changed attributes if successful.
- def save_with_dirty(*args) #:nodoc:
- if status = save_without_dirty(*args)
+ def save(*) #:nodoc:
+ if status = super
@previously_changed = changes
@changed_attributes.clear
end
@@ -26,70 +26,70 @@ module ActiveRecord
end
# Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
- def save_with_dirty!(*args) #:nodoc:
- save_without_dirty!(*args).tap do
+ def save!(*) #:nodoc:
+ super.tap do
@previously_changed = changes
@changed_attributes.clear
end
end
# <tt>reload</tt> the record and clears changed attributes.
- def reload_with_dirty(*args) #:nodoc:
- reload_without_dirty(*args).tap do
+ def reload(*) #:nodoc:
+ super.tap do
@previously_changed.clear
@changed_attributes.clear
end
end
- private
- # Wrap write_attribute to remember original attribute value.
- def write_attribute(attr, value)
- attr = attr.to_s
+ private
+ # Wrap write_attribute to remember original attribute value.
+ def write_attribute(attr, value)
+ attr = attr.to_s
- # The attribute already has an unsaved change.
- if attribute_changed?(attr)
- old = @changed_attributes[attr]
- @changed_attributes.delete(attr) unless field_changed?(attr, old, value)
- else
- old = clone_attribute_value(:read_attribute, attr)
- # Save Time objects as TimeWithZone if time_zone_aware_attributes == true
- old = old.in_time_zone if clone_with_time_zone_conversion_attribute?(attr, old)
- @changed_attributes[attr] = old if field_changed?(attr, old, value)
- end
+ # The attribute already has an unsaved change.
+ if attribute_changed?(attr)
+ old = @changed_attributes[attr]
+ @changed_attributes.delete(attr) unless field_changed?(attr, old, value)
+ else
+ old = clone_attribute_value(:read_attribute, attr)
+ # Save Time objects as TimeWithZone if time_zone_aware_attributes == true
+ old = old.in_time_zone if clone_with_time_zone_conversion_attribute?(attr, old)
+ @changed_attributes[attr] = old if field_changed?(attr, old, value)
+ end
- # Carry on.
- super(attr, value)
+ # Carry on.
+ super(attr, value)
+ end
+
+ def update(*)
+ if partial_updates?
+ # Serialized attributes should always be written in case they've been
+ # changed in place.
+ super(changed | (attributes.keys & self.class.serialized_attributes.keys))
+ else
+ super
end
+ end
- def update_with_dirty
- if partial_updates?
- # Serialized attributes should always be written in case they've been
- # changed in place.
- update_without_dirty(changed | (attributes.keys & self.class.serialized_attributes.keys))
+ def field_changed?(attr, old, value)
+ if column = column_for_attribute(attr)
+ if column.number? && column.null && (old.nil? || old == 0) && value.blank?
+ # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
+ # Hence we don't record it as a change if the value changes from nil to ''.
+ # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
+ # be typecast back to 0 (''.to_i => 0)
+ value = nil
else
- update_without_dirty
+ value = column.type_cast(value)
end
end
- def field_changed?(attr, old, value)
- if column = column_for_attribute(attr)
- if column.number? && column.null && (old.nil? || old == 0) && value.blank?
- # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
- # Hence we don't record it as a change if the value changes from nil to ''.
- # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
- # be typecast back to 0 (''.to_i => 0)
- value = nil
- else
- value = column.type_cast(value)
- end
- end
-
- old != value
- end
+ old != value
+ end
- def clone_with_time_zone_conversion_attribute?(attr, old)
- old.class.name == "Time" && time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(attr.to_sym)
- end
+ def clone_with_time_zone_conversion_attribute?(attr, old)
+ old.class.name == "Time" && time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(attr.to_sym)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 325a8aa7ec..fd1082a268 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -130,8 +130,6 @@ module ActiveRecord
ASSOCIATION_TYPES = %w{ has_one belongs_to has_many has_and_belongs_to_many }
included do
- alias_method_chain :reload, :autosave_associations
-
ASSOCIATION_TYPES.each do |type|
send("valid_keys_for_#{type}_association") << :autosave
end
@@ -196,9 +194,9 @@ module ActiveRecord
end
# Reloads the attributes of the object as usual and removes a mark for destruction.
- def reload_with_autosave_associations(options = nil)
+ def reload(options = nil)
@marked_for_destruction = false
- reload_without_autosave_associations(options)
+ super
end
# Marks this record to be destroyed as part of the parents save transaction.
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 9ed53cc4af..650a91b385 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -480,110 +480,6 @@ module ActiveRecord #:nodoc:
connection.select_value(sql, "#{name} Count").to_i
end
- # Resets one or more counter caches to their correct value using an SQL
- # count query. This is useful when adding new counter caches, or if the
- # counter has been corrupted or modified directly by SQL.
- #
- # ==== Parameters
- #
- # * +id+ - The id of the object you wish to reset a counter on.
- # * +counters+ - One or more counter names to reset
- #
- # ==== Examples
- #
- # # For Post with id #1 records reset the comments_count
- # Post.reset_counters(1, :comments)
- def reset_counters(id, *counters)
- object = find(id)
- counters.each do |association|
- child_class = reflect_on_association(association).klass
- counter_name = child_class.reflect_on_association(self.name.downcase.to_sym).counter_cache_column
-
- connection.update("UPDATE #{quoted_table_name} SET #{connection.quote_column_name(counter_name)} = #{object.send(association).count} WHERE #{connection.quote_column_name(primary_key)} = #{quote_value(object.id)}", "#{name} UPDATE")
- end
- end
-
- # A generic "counter updater" implementation, intended primarily to be
- # used by increment_counter and decrement_counter, but which may also
- # be useful on its own. It simply does a direct SQL update for the record
- # with the given ID, altering the given hash of counters by the amount
- # given by the corresponding value:
- #
- # ==== Parameters
- #
- # * +id+ - The id of the object you wish to update a counter on or an Array of ids.
- # * +counters+ - An Array of Hashes containing the names of the fields
- # to update as keys and the amount to update the field by as values.
- #
- # ==== Examples
- #
- # # For the Post with id of 5, decrement the comment_count by 1, and
- # # increment the action_count by 1
- # Post.update_counters 5, :comment_count => -1, :action_count => 1
- # # Executes the following SQL:
- # # UPDATE posts
- # # SET comment_count = comment_count - 1,
- # # action_count = action_count + 1
- # # WHERE id = 5
- #
- # # For the Posts with id of 10 and 15, increment the comment_count by 1
- # Post.update_counters [10, 15], :comment_count => 1
- # # Executes the following SQL:
- # # UPDATE posts
- # # SET comment_count = comment_count + 1,
- # # WHERE id IN (10, 15)
- def update_counters(id, counters)
- updates = counters.inject([]) { |list, (counter_name, increment)|
- sign = increment < 0 ? "-" : "+"
- list << "#{connection.quote_column_name(counter_name)} = COALESCE(#{connection.quote_column_name(counter_name)}, 0) #{sign} #{increment.abs}"
- }.join(", ")
-
- if id.is_a?(Array)
- ids_list = id.map {|i| quote_value(i)}.join(', ')
- condition = "IN (#{ids_list})"
- else
- condition = "= #{quote_value(id)}"
- end
-
- update_all(updates, "#{connection.quote_column_name(primary_key)} #{condition}")
- end
-
- # Increment a number field by one, usually representing a count.
- #
- # This is used for caching aggregate values, so that they don't need to be computed every time.
- # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
- # shown it would have to run an SQL query to find how many posts and comments there are.
- #
- # ==== Parameters
- #
- # * +counter_name+ - The name of the field that should be incremented.
- # * +id+ - The id of the object that should be incremented.
- #
- # ==== Examples
- #
- # # Increment the post_count column for the record with an id of 5
- # DiscussionBoard.increment_counter(:post_count, 5)
- def increment_counter(counter_name, id)
- update_counters(id, counter_name => 1)
- end
-
- # Decrement a number field by one, usually representing a count.
- #
- # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
- #
- # ==== Parameters
- #
- # * +counter_name+ - The name of the field that should be decremented.
- # * +id+ - The id of the object that should be decremented.
- #
- # ==== Examples
- #
- # # Decrement the post_count column for the record with an id of 5
- # DiscussionBoard.decrement_counter(:post_count, 5)
- def decrement_counter(counter_name, id)
- update_counters(id, counter_name => -1)
- end
-
# Attributes named in this macro are protected from mass-assignment,
# such as <tt>new(attributes)</tt>,
# <tt>update_attributes(attributes)</tt>, or
@@ -1623,186 +1519,6 @@ module ActiveRecord #:nodoc:
quote_value(id, column_for_attribute(self.class.primary_key))
end
- # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false.
- def new_record?
- @new_record
- end
-
- # Returns true if this object has been destroyed, otherwise returns false.
- def destroyed?
- @destroyed
- end
-
- # Returns if the record is persisted, i.e. it's not a new record and it was not destroyed.
- def persisted?
- !(new_record? || destroyed?)
- end
-
- # :call-seq:
- # save(options)
- #
- # Saves the model.
- #
- # If the model is new a record gets created in the database, otherwise
- # the existing record gets updated.
- #
- # By default, save always run validations. If any of them fail the action
- # is cancelled and +save+ returns +false+. However, if you supply
- # :validate => false, validations are bypassed altogether. See
- # ActiveRecord::Validations for more information.
- #
- # There's a series of callbacks associated with +save+. If any of the
- # <tt>before_*</tt> callbacks return +false+ the action is cancelled and
- # +save+ returns +false+. See ActiveRecord::Callbacks for further
- # details.
- def save
- create_or_update
- end
-
- # Saves the model.
- #
- # If the model is new a record gets created in the database, otherwise
- # the existing record gets updated.
- #
- # With <tt>save!</tt> validations always run. If any of them fail
- # ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations
- # for more information.
- #
- # There's a series of callbacks associated with <tt>save!</tt>. If any of
- # the <tt>before_*</tt> callbacks return +false+ the action is cancelled
- # and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
- # ActiveRecord::Callbacks for further details.
- def save!
- create_or_update || raise(RecordNotSaved)
- end
-
- # Deletes the record in the database and freezes this instance to
- # reflect that no changes should be made (since they can't be
- # persisted). Returns the frozen instance.
- #
- # The row is simply removed with a SQL +DELETE+ statement on the
- # record's primary key, and no callbacks are executed.
- #
- # To enforce the object's +before_destroy+ and +after_destroy+
- # callbacks, Observer methods, or any <tt>:dependent</tt> association
- # options, use <tt>#destroy</tt>.
- def delete
- self.class.delete(id) if persisted?
- @destroyed = true
- freeze
- end
-
- # Deletes the record in the database and freezes this instance to reflect that no changes should
- # be made (since they can't be persisted).
- def destroy
- if persisted?
- self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all
- end
-
- @destroyed = true
- freeze
- end
-
- # Returns an instance of the specified +klass+ with the attributes of the current record. This is mostly useful in relation to
- # single-table inheritance structures where you want a subclass to appear as the superclass. This can be used along with record
- # identification in Action Pack to allow, say, <tt>Client < Company</tt> to do something like render <tt>:partial => @client.becomes(Company)</tt>
- # to render that instance using the companies/company partial instead of clients/client.
- #
- # Note: The new instance will share a link to the same attributes as the original class. So any change to the attributes in either
- # instance will affect the other.
- def becomes(klass)
- became = klass.new
- became.instance_variable_set("@attributes", @attributes)
- became.instance_variable_set("@attributes_cache", @attributes_cache)
- became.instance_variable_set("@new_record", new_record?)
- became.instance_variable_set("@destroyed", destroyed?)
- became
- end
-
- # Updates a single attribute and saves the record without going through the normal validation procedure.
- # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method
- # in Base is replaced with this when the validations module is mixed in, which it is by default.
- def update_attribute(name, value)
- send("#{name}=", value)
- save(:validate => false)
- end
-
- # Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will
- # fail and false will be returned.
- def update_attributes(attributes)
- self.attributes = attributes
- save
- end
-
- # Updates an object just like Base.update_attributes but calls save! instead of save so an exception is raised if the record is invalid.
- def update_attributes!(attributes)
- self.attributes = attributes
- save!
- end
-
- # Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).
- # The increment is performed directly on the underlying attribute, no setter is invoked.
- # Only makes sense for number-based attributes. Returns +self+.
- def increment(attribute, by = 1)
- self[attribute] ||= 0
- self[attribute] += by
- self
- end
-
- # Wrapper around +increment+ that saves the record. This method differs from
- # its non-bang version in that it passes through the attribute setter.
- # Saving is not subjected to validation checks. Returns +true+ if the
- # record could be saved.
- def increment!(attribute, by = 1)
- increment(attribute, by).update_attribute(attribute, self[attribute])
- end
-
- # Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1).
- # The decrement is performed directly on the underlying attribute, no setter is invoked.
- # Only makes sense for number-based attributes. Returns +self+.
- def decrement(attribute, by = 1)
- self[attribute] ||= 0
- self[attribute] -= by
- self
- end
-
- # Wrapper around +decrement+ that saves the record. This method differs from
- # its non-bang version in that it passes through the attribute setter.
- # Saving is not subjected to validation checks. Returns +true+ if the
- # record could be saved.
- def decrement!(attribute, by = 1)
- decrement(attribute, by).update_attribute(attribute, self[attribute])
- end
-
- # Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
- # if the predicate returns +true+ the attribute will become +false+. This
- # method toggles directly the underlying value without calling any setter.
- # Returns +self+.
- def toggle(attribute)
- self[attribute] = !send("#{attribute}?")
- self
- end
-
- # Wrapper around +toggle+ that saves the record. This method differs from
- # its non-bang version in that it passes through the attribute setter.
- # Saving is not subjected to validation checks. Returns +true+ if the
- # record could be saved.
- def toggle!(attribute)
- toggle(attribute).update_attribute(attribute, self[attribute])
- end
-
- # Reloads the attributes of this object from the database.
- # The optional options argument is passed to find when reloading so you
- # may do e.g. record.reload(:lock => true) to reload the same record with
- # an exclusive row lock.
- def reload(options = nil)
- clear_aggregation_cache
- clear_association_cache
- @attributes.update(self.class.send(:with_exclusive_scope) { self.class.find(self.id, options) }.instance_variable_get('@attributes'))
- @attributes_cache = {}
- self
- end
-
# Returns true if the given attribute is in the attributes hash
def has_attribute?(attr_name)
@attributes.has_key?(attr_name.to_s)
@@ -1980,40 +1696,6 @@ module ActiveRecord #:nodoc:
end
private
- def create_or_update
- raise ReadOnlyRecord if readonly?
- result = new_record? ? create : update
- result != false
- end
-
- # Updates the associated record with values matching those of the instance attributes.
- # Returns the number of affected rows.
- def update(attribute_names = @attributes.keys)
- attributes_with_values = arel_attributes_values(false, false, attribute_names)
- return 0 if attributes_with_values.empty?
- self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values)
- end
-
- # Creates a record with values matching those of the instance attributes
- # and returns its id.
- def create
- if self.id.nil? && connection.prefetch_primary_key?(self.class.table_name)
- self.id = connection.next_sequence_value(self.class.sequence_name)
- end
-
- attributes_values = arel_attributes_values
-
- new_id = if attributes_values.empty?
- self.class.unscoped.insert connection.empty_insert_statement_value
- else
- self.class.unscoped.insert attributes_values
- end
-
- self.id ||= new_id
-
- @new_record = false
- id
- end
# Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord::Base descendant.
# Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to do Reply.new without having to
@@ -2099,17 +1781,6 @@ module ActiveRecord #:nodoc:
instance_eval("%@#{sql.gsub('@', '\@')}@")
end
- # Initializes the attributes array with keys matching the columns from the linked table and
- # the values matching the corresponding default value of that column, so
- # that a new instance, or one populated from a passed-in Hash, still has all the attributes
- # that instances loaded from the database would.
- def attributes_from_column_definition
- self.class.columns.inject({}) do |attributes, column|
- attributes[column.name] = column.default unless column.name == self.class.primary_key
- attributes
- end
- end
-
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
@@ -2225,12 +1896,14 @@ module ActiveRecord #:nodoc:
end
Base.class_eval do
+ include ActiveRecord::Persistence
extend ActiveModel::Naming
extend QueryCache::ClassMethods
extend ActiveSupport::Benchmarkable
include ActiveModel::Conversion
include Validations
+ extend CounterCache
include Locking::Optimistic, Locking::Pessimistic
include AttributeMethods
include AttributeMethods::Read, AttributeMethods::Write, AttributeMethods::BeforeTypeCast, AttributeMethods::Query
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 7ebeb6079e..498836aca4 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -233,10 +233,6 @@ module ActiveRecord
]
included do
- [:create_or_update, :valid?, :create, :update, :destroy].each do |method|
- alias_method_chain method, :callbacks
- end
-
extend ActiveModel::Callbacks
define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
@@ -273,45 +269,33 @@ module ActiveRecord
end
end
- def create_or_update_with_callbacks #:nodoc:
- _run_save_callbacks do
- create_or_update_without_callbacks
- end
+ def valid?(*) #:nodoc:
+ @_on_validate = new_record? ? :create : :update
+ _run_validation_callbacks { super }
end
- private :create_or_update_with_callbacks
- def create_with_callbacks #:nodoc:
- _run_create_callbacks do
- create_without_callbacks
- end
+ def destroy #:nodoc:
+ _run_destroy_callbacks { super }
end
- private :create_with_callbacks
- def update_with_callbacks(*args) #:nodoc:
- _run_update_callbacks do
- update_without_callbacks(*args)
+ def deprecated_callback_method(symbol) #:nodoc:
+ if respond_to?(symbol, true)
+ ActiveSupport::Deprecation.warn("Overwriting #{symbol} in your models has been deprecated, please use Base##{symbol} :method_name instead")
+ send(symbol)
end
end
- private :update_with_callbacks
- def valid_with_callbacks? #:nodoc:
- @_on_validate = new_record? ? :create : :update
- _run_validation_callbacks do
- valid_without_callbacks?
- end
+ private
+ def create_or_update #:nodoc:
+ _run_save_callbacks { super }
end
- def destroy_with_callbacks #:nodoc:
- _run_destroy_callbacks do
- destroy_without_callbacks
- end
+ def create #:nodoc:
+ _run_create_callbacks { super }
end
- def deprecated_callback_method(symbol) #:nodoc:
- if respond_to?(symbol, true)
- ActiveSupport::Deprecation.warn("Overwriting #{symbol} in your models has been deprecated, please use Base##{symbol} :method_name instead")
- send(symbol)
- end
+ def update(*) #:nodoc:
+ _run_update_callbacks { super }
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 533a7bb8e6..78fffaff6e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -5,23 +5,16 @@ module ActiveRecord
module QueryCache
class << self
def included(base)
- base.class_eval do
- alias_method_chain :columns, :query_cache
- alias_method_chain :select_all, :query_cache
- end
-
dirties_query_cache base, :insert, :update, :delete
end
def dirties_query_cache(base, *method_names)
method_names.each do |method_name|
base.class_eval <<-end_code, __FILE__, __LINE__ + 1
- def #{method_name}_with_query_dirty(*args) # def update_with_query_dirty(*args)
- clear_query_cache if @query_cache_enabled # clear_query_cache if @query_cache_enabled
- #{method_name}_without_query_dirty(*args) # update_without_query_dirty(*args)
- end # end
- #
- alias_method_chain :#{method_name}, :query_dirty # alias_method_chain :update, :query_dirty
+ def #{method_name}(*) # def update_with_query_dirty(*args)
+ clear_query_cache if @query_cache_enabled # clear_query_cache if @query_cache_enabled
+ super # update_without_query_dirty(*args)
+ end # end
end_code
end
end
@@ -56,19 +49,19 @@ module ActiveRecord
@query_cache.clear
end
- def select_all_with_query_cache(*args)
+ def select_all(*args)
if @query_cache_enabled
- cache_sql(args.first) { select_all_without_query_cache(*args) }
+ cache_sql(args.first) { super }
else
- select_all_without_query_cache(*args)
+ super
end
end
- def columns_with_query_cache(*args)
+ def columns(*)
if @query_cache_enabled
- @query_cache["SHOW FIELDS FROM #{args.first}"] ||= columns_without_query_cache(*args)
+ @query_cache["SHOW FIELDS FROM #{args.first}"] ||= super
else
- columns_without_query_cache(*args)
+ super
end
end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
new file mode 100644
index 0000000000..cbebded995
--- /dev/null
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -0,0 +1,107 @@
+module ActiveRecord
+ module CounterCache
+ # Resets one or more counter caches to their correct value using an SQL
+ # count query. This is useful when adding new counter caches, or if the
+ # counter has been corrupted or modified directly by SQL.
+ #
+ # ==== Parameters
+ #
+ # * +id+ - The id of the object you wish to reset a counter on.
+ # * +counters+ - One or more counter names to reset
+ #
+ # ==== Examples
+ #
+ # # For Post with id #1 records reset the comments_count
+ # Post.reset_counters(1, :comments)
+ def reset_counters(id, *counters)
+ object = find(id)
+ counters.each do |association|
+ child_class = reflect_on_association(association).klass
+ counter_name = child_class.reflect_on_association(self.name.downcase.to_sym).counter_cache_column
+
+ connection.update("UPDATE #{quoted_table_name} SET #{connection.quote_column_name(counter_name)} = #{object.send(association).count} WHERE #{connection.quote_column_name(primary_key)} = #{quote_value(object.id)}", "#{name} UPDATE")
+ end
+ end
+
+ # A generic "counter updater" implementation, intended primarily to be
+ # used by increment_counter and decrement_counter, but which may also
+ # be useful on its own. It simply does a direct SQL update for the record
+ # with the given ID, altering the given hash of counters by the amount
+ # given by the corresponding value:
+ #
+ # ==== Parameters
+ #
+ # * +id+ - The id of the object you wish to update a counter on or an Array of ids.
+ # * +counters+ - An Array of Hashes containing the names of the fields
+ # to update as keys and the amount to update the field by as values.
+ #
+ # ==== Examples
+ #
+ # # For the Post with id of 5, decrement the comment_count by 1, and
+ # # increment the action_count by 1
+ # Post.update_counters 5, :comment_count => -1, :action_count => 1
+ # # Executes the following SQL:
+ # # UPDATE posts
+ # # SET comment_count = comment_count - 1,
+ # # action_count = action_count + 1
+ # # WHERE id = 5
+ #
+ # # For the Posts with id of 10 and 15, increment the comment_count by 1
+ # Post.update_counters [10, 15], :comment_count => 1
+ # # Executes the following SQL:
+ # # UPDATE posts
+ # # SET comment_count = comment_count + 1,
+ # # WHERE id IN (10, 15)
+ def update_counters(id, counters)
+ updates = counters.inject([]) { |list, (counter_name, increment)|
+ sign = increment < 0 ? "-" : "+"
+ list << "#{connection.quote_column_name(counter_name)} = COALESCE(#{connection.quote_column_name(counter_name)}, 0) #{sign} #{increment.abs}"
+ }.join(", ")
+
+ if id.is_a?(Array)
+ ids_list = id.map {|i| quote_value(i)}.join(', ')
+ condition = "IN (#{ids_list})"
+ else
+ condition = "= #{quote_value(id)}"
+ end
+
+ update_all(updates, "#{connection.quote_column_name(primary_key)} #{condition}")
+ end
+
+ # Increment a number field by one, usually representing a count.
+ #
+ # This is used for caching aggregate values, so that they don't need to be computed every time.
+ # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
+ # shown it would have to run an SQL query to find how many posts and comments there are.
+ #
+ # ==== Parameters
+ #
+ # * +counter_name+ - The name of the field that should be incremented.
+ # * +id+ - The id of the object that should be incremented.
+ #
+ # ==== Examples
+ #
+ # # Increment the post_count column for the record with an id of 5
+ # DiscussionBoard.increment_counter(:post_count, 5)
+ def increment_counter(counter_name, id)
+ update_counters(id, counter_name => 1)
+ end
+
+ # Decrement a number field by one, usually representing a count.
+ #
+ # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
+ #
+ # ==== Parameters
+ #
+ # * +counter_name+ - The name of the field that should be decremented.
+ # * +id+ - The id of the object that should be decremented.
+ #
+ # ==== Examples
+ #
+ # # Decrement the post_count column for the record with an id of 5
+ # DiscussionBoard.decrement_counter(:post_count, 5)
+ def decrement_counter(counter_name, id)
+ update_counters(id, counter_name => -1)
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 60ad23f38c..71057efa15 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -48,10 +48,6 @@ module ActiveRecord
cattr_accessor :lock_optimistically, :instance_writer => false
self.lock_optimistically = true
- alias_method_chain :update, :lock
- alias_method_chain :destroy, :lock
- alias_method_chain :attributes_from_column_definition, :lock
-
class << self
alias_method :locking_column=, :set_locking_column
end
@@ -62,8 +58,8 @@ module ActiveRecord
end
private
- def attributes_from_column_definition_with_lock
- result = attributes_from_column_definition_without_lock
+ def attributes_from_column_definition
+ result = super
# If the locking column has no default value set,
# start the lock version at zero. Note we can't use
@@ -77,8 +73,8 @@ module ActiveRecord
return result
end
- def update_with_lock(attribute_names = @attributes.keys) #:nodoc:
- return update_without_lock(attribute_names) unless locking_enabled?
+ def update(attribute_names = @attributes.keys) #:nodoc:
+ return super unless locking_enabled?
return 0 if attribute_names.empty?
lock_col = self.class.locking_column
@@ -97,7 +93,6 @@ module ActiveRecord
)
).arel.update(arel_attributes_values(false, false, attribute_names))
-
unless affected_rows == 1
raise ActiveRecord::StaleObjectError, "Attempted to update a stale object: #{self.class.name}"
end
@@ -111,8 +106,8 @@ module ActiveRecord
end
end
- def destroy_with_lock #:nodoc:
- return destroy_without_lock unless locking_enabled?
+ def destroy #:nodoc:
+ return super unless locking_enabled?
unless new_record?
lock_col = self.class.locking_column
@@ -136,12 +131,6 @@ module ActiveRecord
module ClassMethods
DEFAULT_LOCKING_COLUMN = 'lock_version'
- def self.extended(base)
- class <<base
- alias_method_chain :update_counters, :lock
- end
- end
-
# Is optimistic locking enabled for this table? Returns true if the
# +lock_optimistically+ flag is set to true (which it is, by default)
# and the table includes the +locking_column+ column (defaults to
@@ -173,9 +162,9 @@ module ActiveRecord
# Make sure the lock version column gets updated when counters are
# updated.
- def update_counters_with_lock(id, counters)
+ def update_counters(id, counters)
counters = counters.merge(locking_column => 1) if locking_enabled?
- update_counters_without_lock(id, counters)
+ super
end
end
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
new file mode 100644
index 0000000000..10788630a5
--- /dev/null
+++ b/activerecord/lib/active_record/persistence.rb
@@ -0,0 +1,230 @@
+module ActiveRecord
+ module Persistence
+ # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false.
+ def new_record?
+ @new_record
+ end
+
+ # Returns true if this object has been destroyed, otherwise returns false.
+ def destroyed?
+ @destroyed
+ end
+
+ # Returns if the record is persisted, i.e. it's not a new record and it was not destroyed.
+ def persisted?
+ !(new_record? || destroyed?)
+ end
+
+ # :call-seq:
+ # save(options)
+ #
+ # Saves the model.
+ #
+ # If the model is new a record gets created in the database, otherwise
+ # the existing record gets updated.
+ #
+ # By default, save always run validations. If any of them fail the action
+ # is cancelled and +save+ returns +false+. However, if you supply
+ # :validate => false, validations are bypassed altogether. See
+ # ActiveRecord::Validations for more information.
+ #
+ # There's a series of callbacks associated with +save+. If any of the
+ # <tt>before_*</tt> callbacks return +false+ the action is cancelled and
+ # +save+ returns +false+. See ActiveRecord::Callbacks for further
+ # details.
+ def save(*)
+ create_or_update
+ end
+
+ # Saves the model.
+ #
+ # If the model is new a record gets created in the database, otherwise
+ # the existing record gets updated.
+ #
+ # With <tt>save!</tt> validations always run. If any of them fail
+ # ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations
+ # for more information.
+ #
+ # There's a series of callbacks associated with <tt>save!</tt>. If any of
+ # the <tt>before_*</tt> callbacks return +false+ the action is cancelled
+ # and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
+ # ActiveRecord::Callbacks for further details.
+ def save!(*)
+ create_or_update || raise(RecordNotSaved)
+ end
+
+ # Deletes the record in the database and freezes this instance to
+ # reflect that no changes should be made (since they can't be
+ # persisted). Returns the frozen instance.
+ #
+ # The row is simply removed with a SQL +DELETE+ statement on the
+ # record's primary key, and no callbacks are executed.
+ #
+ # To enforce the object's +before_destroy+ and +after_destroy+
+ # callbacks, Observer methods, or any <tt>:dependent</tt> association
+ # options, use <tt>#destroy</tt>.
+ def delete
+ self.class.delete(id) if persisted?
+ @destroyed = true
+ freeze
+ end
+
+ # Deletes the record in the database and freezes this instance to reflect that no changes should
+ # be made (since they can't be persisted).
+ def destroy
+ if persisted?
+ self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all
+ end
+
+ @destroyed = true
+ freeze
+ end
+
+ # Returns an instance of the specified +klass+ with the attributes of the current record. This is mostly useful in relation to
+ # single-table inheritance structures where you want a subclass to appear as the superclass. This can be used along with record
+ # identification in Action Pack to allow, say, <tt>Client < Company</tt> to do something like render <tt>:partial => @client.becomes(Company)</tt>
+ # to render that instance using the companies/company partial instead of clients/client.
+ #
+ # Note: The new instance will share a link to the same attributes as the original class. So any change to the attributes in either
+ # instance will affect the other.
+ def becomes(klass)
+ became = klass.new
+ became.instance_variable_set("@attributes", @attributes)
+ became.instance_variable_set("@attributes_cache", @attributes_cache)
+ became.instance_variable_set("@new_record", new_record?)
+ became.instance_variable_set("@destroyed", destroyed?)
+ became
+ end
+
+ # Updates a single attribute and saves the record without going through the normal validation procedure.
+ # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method
+ # in Base is replaced with this when the validations module is mixed in, which it is by default.
+ def update_attribute(name, value)
+ send("#{name}=", value)
+ save(:validate => false)
+ end
+
+ # Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will
+ # fail and false will be returned.
+ def update_attributes(attributes)
+ self.attributes = attributes
+ save
+ end
+
+ # Updates an object just like Base.update_attributes but calls save! instead of save so an exception is raised if the record is invalid.
+ def update_attributes!(attributes)
+ self.attributes = attributes
+ save!
+ end
+
+ # Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).
+ # The increment is performed directly on the underlying attribute, no setter is invoked.
+ # Only makes sense for number-based attributes. Returns +self+.
+ def increment(attribute, by = 1)
+ self[attribute] ||= 0
+ self[attribute] += by
+ self
+ end
+
+ # Wrapper around +increment+ that saves the record. This method differs from
+ # its non-bang version in that it passes through the attribute setter.
+ # Saving is not subjected to validation checks. Returns +true+ if the
+ # record could be saved.
+ def increment!(attribute, by = 1)
+ increment(attribute, by).update_attribute(attribute, self[attribute])
+ end
+
+ # Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1).
+ # The decrement is performed directly on the underlying attribute, no setter is invoked.
+ # Only makes sense for number-based attributes. Returns +self+.
+ def decrement(attribute, by = 1)
+ self[attribute] ||= 0
+ self[attribute] -= by
+ self
+ end
+
+ # Wrapper around +decrement+ that saves the record. This method differs from
+ # its non-bang version in that it passes through the attribute setter.
+ # Saving is not subjected to validation checks. Returns +true+ if the
+ # record could be saved.
+ def decrement!(attribute, by = 1)
+ decrement(attribute, by).update_attribute(attribute, self[attribute])
+ end
+
+ # Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
+ # if the predicate returns +true+ the attribute will become +false+. This
+ # method toggles directly the underlying value without calling any setter.
+ # Returns +self+.
+ def toggle(attribute)
+ self[attribute] = !send("#{attribute}?")
+ self
+ end
+
+ # Wrapper around +toggle+ that saves the record. This method differs from
+ # its non-bang version in that it passes through the attribute setter.
+ # Saving is not subjected to validation checks. Returns +true+ if the
+ # record could be saved.
+ def toggle!(attribute)
+ toggle(attribute).update_attribute(attribute, self[attribute])
+ end
+
+ # Reloads the attributes of this object from the database.
+ # The optional options argument is passed to find when reloading so you
+ # may do e.g. record.reload(:lock => true) to reload the same record with
+ # an exclusive row lock.
+ def reload(options = nil)
+ clear_aggregation_cache
+ clear_association_cache
+ @attributes.update(self.class.send(:with_exclusive_scope) { self.class.find(self.id, options) }.instance_variable_get('@attributes'))
+ @attributes_cache = {}
+ self
+ end
+
+ private
+ def create_or_update
+ raise ReadOnlyRecord if readonly?
+ result = new_record? ? create : update
+ result != false
+ end
+
+ # Updates the associated record with values matching those of the instance attributes.
+ # Returns the number of affected rows.
+ def update(attribute_names = @attributes.keys)
+ attributes_with_values = arel_attributes_values(false, false, attribute_names)
+ return 0 if attributes_with_values.empty?
+ self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values)
+ end
+
+ # Creates a record with values matching those of the instance attributes
+ # and returns its id.
+ def create
+ if self.id.nil? && connection.prefetch_primary_key?(self.class.table_name)
+ self.id = connection.next_sequence_value(self.class.sequence_name)
+ end
+
+ attributes_values = arel_attributes_values
+
+ new_id = if attributes_values.empty?
+ self.class.unscoped.insert connection.empty_insert_statement_value
+ else
+ self.class.unscoped.insert attributes_values
+ end
+
+ self.id ||= new_id
+
+ @new_record = false
+ id
+ end
+
+ # Initializes the attributes array with keys matching the columns from the linked table and
+ # the values matching the corresponding default value of that column, so
+ # that a new instance, or one populated from a passed-in Hash, still has all the attributes
+ # that instances loaded from the database would.
+ def attributes_from_column_definition
+ self.class.columns.inject({}) do |attributes, column|
+ attributes[column.name] = column.default unless column.name == self.class.primary_key
+ attributes
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index da075dabd3..9fba8f0aca 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -11,9 +11,6 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- alias_method_chain :create, :timestamps
- alias_method_chain :update, :timestamps
-
class_inheritable_accessor :record_timestamps, :instance_writer => false
self.record_timestamps = true
end
@@ -39,35 +36,34 @@ module ActiveRecord
save!
end
+ private
+ def create #:nodoc:
+ if record_timestamps
+ current_time = current_time_from_proper_timezone
- private
- def create_with_timestamps #:nodoc:
- if record_timestamps
- current_time = current_time_from_proper_timezone
-
- write_attribute('created_at', current_time) if respond_to?(:created_at) && created_at.nil?
- write_attribute('created_on', current_time) if respond_to?(:created_on) && created_on.nil?
-
- write_attribute('updated_at', current_time) if respond_to?(:updated_at) && updated_at.nil?
- write_attribute('updated_on', current_time) if respond_to?(:updated_on) && updated_on.nil?
- end
+ write_attribute('created_at', current_time) if respond_to?(:created_at) && created_at.nil?
+ write_attribute('created_on', current_time) if respond_to?(:created_on) && created_on.nil?
- create_without_timestamps
+ write_attribute('updated_at', current_time) if respond_to?(:updated_at) && updated_at.nil?
+ write_attribute('updated_on', current_time) if respond_to?(:updated_on) && updated_on.nil?
end
- def update_with_timestamps(*args) #:nodoc:
- if record_timestamps && (!partial_updates? || changed?)
- current_time = current_time_from_proper_timezone
+ super
+ end
- write_attribute('updated_at', current_time) if respond_to?(:updated_at)
- write_attribute('updated_on', current_time) if respond_to?(:updated_on)
- end
+ def update(*args) #:nodoc:
+ if record_timestamps && (!partial_updates? || changed?)
+ current_time = current_time_from_proper_timezone
- update_without_timestamps(*args)
- end
-
- def current_time_from_proper_timezone
- self.class.default_timezone == :utc ? Time.now.utc : Time.now
+ write_attribute('updated_at', current_time) if respond_to?(:updated_at)
+ write_attribute('updated_on', current_time) if respond_to?(:updated_on)
end
+
+ super
+ end
+
+ def current_time_from_proper_timezone
+ self.class.default_timezone == :utc ? Time.now.utc : Time.now
+ end
end
end \ No newline at end of file
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 1a195fbb81..5a8e2ce880 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -9,10 +9,6 @@ module ActiveRecord
end
included do
- [:destroy, :save, :save!].each do |method|
- alias_method_chain method, :transactions
- end
-
define_model_callbacks :commit, :commit_on_update, :commit_on_create, :commit_on_destroy, :only => :after
define_model_callbacks :rollback, :rollback_on_update, :rollback_on_create, :rollback_on_destroy
end
@@ -213,16 +209,18 @@ module ActiveRecord
self.class.transaction(&block)
end
- def destroy_with_transactions #:nodoc:
- with_transaction_returning_status(:destroy_without_transactions)
+ def destroy #:nodoc:
+ with_transaction_returning_status { super }
end
- def save_with_transactions(*args) #:nodoc:
- rollback_active_record_state! { with_transaction_returning_status(:save_without_transactions, *args) }
+ def save(*) #:nodoc:
+ rollback_active_record_state! do
+ with_transaction_returning_status { super }
+ end
end
- def save_with_transactions! #:nodoc:
- with_transaction_returning_status(:save_without_transactions!)
+ def save!(*) #:nodoc:
+ with_transaction_returning_status { super }
end
# Reset id and @new_record if the transaction rolls back.
@@ -279,11 +277,11 @@ module ActiveRecord
#
# This method is available within the context of an ActiveRecord::Base
# instance.
- def with_transaction_returning_status(method, *args)
+ def with_transaction_returning_status
status = nil
self.class.transaction do
add_to_transaction
- status = send(method, *args)
+ status = yield
raise ActiveRecord::Rollback unless status
end
status
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index b2ee51fa51..55c4236874 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -19,11 +19,6 @@ module ActiveRecord
extend ActiveSupport::Concern
include ActiveModel::Validations
- included do
- alias_method_chain :save, :validation
- alias_method_chain :save!, :validation
- end
-
module ClassMethods
# Creates an object just like Base.create but calls save! instead of save
# so an exception is raised if the record is invalid.
@@ -39,39 +34,37 @@ module ActiveRecord
end
end
- module InstanceMethods
- # The validation process on save can be skipped by passing false. The regular Base#save method is
- # replaced with this when the validations module is mixed in, which it is by default.
- def save_with_validation(options=nil)
- perform_validation = case options
- when NilClass
- true
- when Hash
- options[:validate] != false
- else
- ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller
- options
- end
+ # The validation process on save can be skipped by passing false. The regular Base#save method is
+ # replaced with this when the validations module is mixed in, which it is by default.
+ def save(options=nil)
+ return super if valid?(options)
+ false
+ end
- if perform_validation && valid? || !perform_validation
- save_without_validation
- else
- false
- end
- end
+ def save_without_validation!
+ save!(:validate => false)
+ end
+
+ # Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false
+ # if the record is not valid.
+ def save!(options = nil)
+ return super if valid?(options)
+ raise RecordInvalid.new(self)
+ end
- # Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false
- # if the record is not valid.
- def save_with_validation!
- if valid?
- save_without_validation!
+ # Runs all the specified validations and returns true if no errors were added otherwise false.
+ def valid?(options = nil)
+ perform_validation = case options
+ when NilClass
+ true
+ when Hash
+ options[:validate] != false
else
- raise RecordInvalid.new(self)
- end
+ ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller
+ options
end
- # Runs all the specified validations and returns true if no errors were added otherwise false.
- def valid?
+ if perform_validation
errors.clear
self.validation_context = new_record? ? :create : :update
@@ -86,16 +79,12 @@ module ActiveRecord
end
errors.empty?
- end
-
- def invalid?
- !valid?
+ else
+ true
end
end
end
end
-Dir[File.dirname(__FILE__) + "/validations/*.rb"].sort.each do |path|
- filename = File.basename(path)
- require "active_record/validations/#{filename}"
-end
+require "active_record/validations/associated"
+require "active_record/validations/uniqueness"
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index aa2d9527f9..66874cdad1 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -195,7 +195,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::RecordNotFound) { Person.find(p1.id) }
assert_raises(ActiveRecord::RecordNotFound) { LegacyThing.find(t.id) }
end
-
+
def test_quote_table_name
ref = references(:michael_magician)
ref.favourite = !ref.favourite
@@ -206,8 +206,11 @@ class OptimisticLockingTest < ActiveRecord::TestCase
# is nothing else being updated.
def test_update_without_attributes_does_not_only_update_lock_version
assert_nothing_raised do
- p1 = Person.new(:first_name => 'anika')
- p1.send(:update_with_lock, [])
+ p1 = Person.create!(:first_name => 'anika')
+ lock_version = p1.lock_version
+ p1.save
+ p1.reload
+ assert_equal lock_version, p1.lock_version
end
end