mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
eliminate alias_method_chain from ActiveRecord
This commit is contained in:
parent
636ffa1f08
commit
d916c62cfc
16 changed files with 519 additions and 547 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
# 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)
|
||||
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 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
|
||||
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)
|
||||
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))
|
||||
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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,38 +269,13 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
def create_or_update_with_callbacks #:nodoc:
|
||||
_run_save_callbacks do
|
||||
create_or_update_without_callbacks
|
||||
end
|
||||
end
|
||||
private :create_or_update_with_callbacks
|
||||
|
||||
def create_with_callbacks #:nodoc:
|
||||
_run_create_callbacks do
|
||||
create_without_callbacks
|
||||
end
|
||||
end
|
||||
private :create_with_callbacks
|
||||
|
||||
def update_with_callbacks(*args) #:nodoc:
|
||||
_run_update_callbacks do
|
||||
update_without_callbacks(*args)
|
||||
end
|
||||
end
|
||||
private :update_with_callbacks
|
||||
|
||||
def valid_with_callbacks? #:nodoc:
|
||||
def valid?(*) #:nodoc:
|
||||
@_on_validate = new_record? ? :create : :update
|
||||
_run_validation_callbacks do
|
||||
valid_without_callbacks?
|
||||
end
|
||||
_run_validation_callbacks { super }
|
||||
end
|
||||
|
||||
def destroy_with_callbacks #:nodoc:
|
||||
_run_destroy_callbacks do
|
||||
destroy_without_callbacks
|
||||
end
|
||||
def destroy #:nodoc:
|
||||
_run_destroy_callbacks { super }
|
||||
end
|
||||
|
||||
def deprecated_callback_method(symbol) #:nodoc:
|
||||
|
@ -313,5 +284,18 @@ module ActiveRecord
|
|||
send(symbol)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def create_or_update #:nodoc:
|
||||
_run_save_callbacks { super }
|
||||
end
|
||||
|
||||
def create #:nodoc:
|
||||
_run_create_callbacks { super }
|
||||
end
|
||||
|
||||
def update(*) #:nodoc:
|
||||
_run_update_callbacks { super }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
107
activerecord/lib/active_record/counter_cache.rb
Normal file
107
activerecord/lib/active_record/counter_cache.rb
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
230
activerecord/lib/active_record/persistence.rb
Normal file
230
activerecord/lib/active_record/persistence.rb
Normal file
|
@ -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
|
|
@ -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('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
|
||||
|
||||
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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
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
|
||||
|
||||
# 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
|
||||
false
|
||||
end
|
||||
ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller
|
||||
options
|
||||
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!
|
||||
else
|
||||
raise RecordInvalid.new(self)
|
||||
end
|
||||
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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue