eliminate alias_method_chain from ActiveRecord
This commit is contained in:
parent
636ffa1f08
commit
d916c62cfc
|
@ -61,6 +61,7 @@ module ActiveRecord
|
||||||
|
|
||||||
autoload :Base
|
autoload :Base
|
||||||
autoload :Callbacks
|
autoload :Callbacks
|
||||||
|
autoload :CounterCache
|
||||||
autoload :DynamicFinderMatch
|
autoload :DynamicFinderMatch
|
||||||
autoload :DynamicScopeMatch
|
autoload :DynamicScopeMatch
|
||||||
autoload :Migration
|
autoload :Migration
|
||||||
|
@ -68,6 +69,7 @@ module ActiveRecord
|
||||||
autoload :NamedScope
|
autoload :NamedScope
|
||||||
autoload :NestedAttributes
|
autoload :NestedAttributes
|
||||||
autoload :Observer
|
autoload :Observer
|
||||||
|
autoload :Persistence
|
||||||
autoload :QueryCache
|
autoload :QueryCache
|
||||||
autoload :Reflection
|
autoload :Reflection
|
||||||
autoload :Schema
|
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.'
|
raise ArgumentError, 'Converter must be a symbol denoting the converter method to call or a Proc to be invoked.'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
mapping.each { |pair| self[pair.first] = part.send(pair.last) }
|
mapping.each { |pair| self[pair.first] = part.send(pair.last) }
|
||||||
instance_variable_set("@#{name}", part.freeze)
|
instance_variable_set("@#{name}", part.freeze)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1304,14 +1304,14 @@ module ActiveRecord
|
||||||
|
|
||||||
# Don't use a before_destroy callback since users' before_destroy
|
# Don't use a before_destroy callback since users' before_destroy
|
||||||
# callbacks will be executed after the association is wiped out.
|
# callbacks will be executed after the association is wiped out.
|
||||||
old_method = "destroy_without_habtm_shim_for_#{reflection.name}"
|
include Module.new {
|
||||||
class_eval <<-end_eval unless method_defined?(old_method)
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||||
alias_method :#{old_method}, :destroy_without_callbacks # alias_method :destroy_without_habtm_shim_for_posts, :destroy_without_callbacks
|
def destroy # def destroy
|
||||||
def destroy_without_callbacks # def destroy_without_callbacks
|
super # super
|
||||||
#{reflection.name}.clear # posts.clear
|
#{reflection.name}.clear # posts.clear
|
||||||
#{old_method} # destroy_without_habtm_shim_for_posts
|
end # end
|
||||||
end # end
|
RUBY
|
||||||
end_eval
|
}
|
||||||
|
|
||||||
add_association_callbacks(reflection.name, options)
|
add_association_callbacks(reflection.name, options)
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,10 +18,19 @@ module ActiveRecord
|
||||||
def instance_method_already_implemented?(method_name)
|
def instance_method_already_implemented?(method_name)
|
||||||
method_name = method_name.to_s
|
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_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)
|
raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name)
|
||||||
@_defined_class_methods.include?(method_name)
|
@_defined_class_methods.include?(method_name)
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
def method_missing(method_id, *args, &block)
|
def method_missing(method_id, *args, &block)
|
||||||
|
|
|
@ -5,20 +5,20 @@ module ActiveRecord
|
||||||
module Dirty
|
module Dirty
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
include ActiveModel::Dirty
|
include ActiveModel::Dirty
|
||||||
|
include AttributeMethods::Write
|
||||||
|
|
||||||
included do
|
included do
|
||||||
alias_method_chain :save, :dirty
|
if self < Timestamp
|
||||||
alias_method_chain :save!, :dirty
|
raise "You cannot include Dirty after Timestamp"
|
||||||
alias_method_chain :update, :dirty
|
end
|
||||||
alias_method_chain :reload, :dirty
|
|
||||||
|
|
||||||
superclass_delegating_accessor :partial_updates
|
superclass_delegating_accessor :partial_updates
|
||||||
self.partial_updates = true
|
self.partial_updates = true
|
||||||
end
|
end
|
||||||
|
|
||||||
# Attempts to +save+ the record and clears changed attributes if successful.
|
# Attempts to +save+ the record and clears changed attributes if successful.
|
||||||
def save_with_dirty(*args) #:nodoc:
|
def save(*) #:nodoc:
|
||||||
if status = save_without_dirty(*args)
|
if status = super
|
||||||
@previously_changed = changes
|
@previously_changed = changes
|
||||||
@changed_attributes.clear
|
@changed_attributes.clear
|
||||||
end
|
end
|
||||||
|
@ -26,70 +26,70 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
# Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
|
# Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
|
||||||
def save_with_dirty!(*args) #:nodoc:
|
def save!(*) #:nodoc:
|
||||||
save_without_dirty!(*args).tap do
|
super.tap do
|
||||||
@previously_changed = changes
|
@previously_changed = changes
|
||||||
@changed_attributes.clear
|
@changed_attributes.clear
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# <tt>reload</tt> the record and clears changed attributes.
|
# <tt>reload</tt> the record and clears changed attributes.
|
||||||
def reload_with_dirty(*args) #:nodoc:
|
def reload(*) #:nodoc:
|
||||||
reload_without_dirty(*args).tap do
|
super.tap do
|
||||||
@previously_changed.clear
|
@previously_changed.clear
|
||||||
@changed_attributes.clear
|
@changed_attributes.clear
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# Wrap write_attribute to remember original attribute value.
|
# Wrap write_attribute to remember original attribute value.
|
||||||
def write_attribute(attr, value)
|
def write_attribute(attr, value)
|
||||||
attr = attr.to_s
|
attr = attr.to_s
|
||||||
|
|
||||||
# The attribute already has an unsaved change.
|
# The attribute already has an unsaved change.
|
||||||
if attribute_changed?(attr)
|
if attribute_changed?(attr)
|
||||||
old = @changed_attributes[attr]
|
old = @changed_attributes[attr]
|
||||||
@changed_attributes.delete(attr) unless field_changed?(attr, old, value)
|
@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
|
else
|
||||||
old = clone_attribute_value(:read_attribute, attr)
|
value = column.type_cast(value)
|
||||||
# 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
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def field_changed?(attr, old, value)
|
old != value
|
||||||
if column = column_for_attribute(attr)
|
end
|
||||||
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
|
def clone_with_time_zone_conversion_attribute?(attr, old)
|
||||||
end
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -130,8 +130,6 @@ module ActiveRecord
|
||||||
ASSOCIATION_TYPES = %w{ has_one belongs_to has_many has_and_belongs_to_many }
|
ASSOCIATION_TYPES = %w{ has_one belongs_to has_many has_and_belongs_to_many }
|
||||||
|
|
||||||
included do
|
included do
|
||||||
alias_method_chain :reload, :autosave_associations
|
|
||||||
|
|
||||||
ASSOCIATION_TYPES.each do |type|
|
ASSOCIATION_TYPES.each do |type|
|
||||||
send("valid_keys_for_#{type}_association") << :autosave
|
send("valid_keys_for_#{type}_association") << :autosave
|
||||||
end
|
end
|
||||||
|
@ -196,9 +194,9 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
# Reloads the attributes of the object as usual and removes a mark for destruction.
|
# 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
|
@marked_for_destruction = false
|
||||||
reload_without_autosave_associations(options)
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
# Marks this record to be destroyed as part of the parents save transaction.
|
# 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
|
connection.select_value(sql, "#{name} Count").to_i
|
||||||
end
|
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,
|
# Attributes named in this macro are protected from mass-assignment,
|
||||||
# such as <tt>new(attributes)</tt>,
|
# such as <tt>new(attributes)</tt>,
|
||||||
# <tt>update_attributes(attributes)</tt>, or
|
# <tt>update_attributes(attributes)</tt>, or
|
||||||
|
@ -1623,186 +1519,6 @@ module ActiveRecord #:nodoc:
|
||||||
quote_value(id, column_for_attribute(self.class.primary_key))
|
quote_value(id, column_for_attribute(self.class.primary_key))
|
||||||
end
|
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
|
# Returns true if the given attribute is in the attributes hash
|
||||||
def has_attribute?(attr_name)
|
def has_attribute?(attr_name)
|
||||||
@attributes.has_key?(attr_name.to_s)
|
@attributes.has_key?(attr_name.to_s)
|
||||||
|
@ -1980,40 +1696,6 @@ module ActiveRecord #:nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
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.
|
# 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
|
# 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('@', '\@')}@")
|
instance_eval("%@#{sql.gsub('@', '\@')}@")
|
||||||
end
|
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
|
# 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.
|
# 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
|
# 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
|
end
|
||||||
|
|
||||||
Base.class_eval do
|
Base.class_eval do
|
||||||
|
include ActiveRecord::Persistence
|
||||||
extend ActiveModel::Naming
|
extend ActiveModel::Naming
|
||||||
extend QueryCache::ClassMethods
|
extend QueryCache::ClassMethods
|
||||||
extend ActiveSupport::Benchmarkable
|
extend ActiveSupport::Benchmarkable
|
||||||
|
|
||||||
include ActiveModel::Conversion
|
include ActiveModel::Conversion
|
||||||
include Validations
|
include Validations
|
||||||
|
extend CounterCache
|
||||||
include Locking::Optimistic, Locking::Pessimistic
|
include Locking::Optimistic, Locking::Pessimistic
|
||||||
include AttributeMethods
|
include AttributeMethods
|
||||||
include AttributeMethods::Read, AttributeMethods::Write, AttributeMethods::BeforeTypeCast, AttributeMethods::Query
|
include AttributeMethods::Read, AttributeMethods::Write, AttributeMethods::BeforeTypeCast, AttributeMethods::Query
|
||||||
|
|
|
@ -233,10 +233,6 @@ module ActiveRecord
|
||||||
]
|
]
|
||||||
|
|
||||||
included do
|
included do
|
||||||
[:create_or_update, :valid?, :create, :update, :destroy].each do |method|
|
|
||||||
alias_method_chain method, :callbacks
|
|
||||||
end
|
|
||||||
|
|
||||||
extend ActiveModel::Callbacks
|
extend ActiveModel::Callbacks
|
||||||
|
|
||||||
define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
|
define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
|
||||||
|
@ -273,38 +269,13 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_or_update_with_callbacks #:nodoc:
|
def valid?(*) #: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:
|
|
||||||
@_on_validate = new_record? ? :create : :update
|
@_on_validate = new_record? ? :create : :update
|
||||||
_run_validation_callbacks do
|
_run_validation_callbacks { super }
|
||||||
valid_without_callbacks?
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy_with_callbacks #:nodoc:
|
def destroy #:nodoc:
|
||||||
_run_destroy_callbacks do
|
_run_destroy_callbacks { super }
|
||||||
destroy_without_callbacks
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def deprecated_callback_method(symbol) #:nodoc:
|
def deprecated_callback_method(symbol) #:nodoc:
|
||||||
|
@ -313,5 +284,18 @@ module ActiveRecord
|
||||||
send(symbol)
|
send(symbol)
|
||||||
end
|
end
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,23 +5,16 @@ module ActiveRecord
|
||||||
module QueryCache
|
module QueryCache
|
||||||
class << self
|
class << self
|
||||||
def included(base)
|
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
|
dirties_query_cache base, :insert, :update, :delete
|
||||||
end
|
end
|
||||||
|
|
||||||
def dirties_query_cache(base, *method_names)
|
def dirties_query_cache(base, *method_names)
|
||||||
method_names.each do |method_name|
|
method_names.each do |method_name|
|
||||||
base.class_eval <<-end_code, __FILE__, __LINE__ + 1
|
base.class_eval <<-end_code, __FILE__, __LINE__ + 1
|
||||||
def #{method_name}_with_query_dirty(*args) # def update_with_query_dirty(*args)
|
def #{method_name}(*) # def update_with_query_dirty(*args)
|
||||||
clear_query_cache if @query_cache_enabled # clear_query_cache if @query_cache_enabled
|
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)
|
super # update_without_query_dirty(*args)
|
||||||
end # end
|
end # end
|
||||||
#
|
|
||||||
alias_method_chain :#{method_name}, :query_dirty # alias_method_chain :update, :query_dirty
|
|
||||||
end_code
|
end_code
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -56,19 +49,19 @@ module ActiveRecord
|
||||||
@query_cache.clear
|
@query_cache.clear
|
||||||
end
|
end
|
||||||
|
|
||||||
def select_all_with_query_cache(*args)
|
def select_all(*args)
|
||||||
if @query_cache_enabled
|
if @query_cache_enabled
|
||||||
cache_sql(args.first) { select_all_without_query_cache(*args) }
|
cache_sql(args.first) { super }
|
||||||
else
|
else
|
||||||
select_all_without_query_cache(*args)
|
super
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def columns_with_query_cache(*args)
|
def columns(*)
|
||||||
if @query_cache_enabled
|
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
|
else
|
||||||
columns_without_query_cache(*args)
|
super
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -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
|
cattr_accessor :lock_optimistically, :instance_writer => false
|
||||||
self.lock_optimistically = true
|
self.lock_optimistically = true
|
||||||
|
|
||||||
alias_method_chain :update, :lock
|
|
||||||
alias_method_chain :destroy, :lock
|
|
||||||
alias_method_chain :attributes_from_column_definition, :lock
|
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
alias_method :locking_column=, :set_locking_column
|
alias_method :locking_column=, :set_locking_column
|
||||||
end
|
end
|
||||||
|
@ -62,8 +58,8 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def attributes_from_column_definition_with_lock
|
def attributes_from_column_definition
|
||||||
result = attributes_from_column_definition_without_lock
|
result = super
|
||||||
|
|
||||||
# If the locking column has no default value set,
|
# If the locking column has no default value set,
|
||||||
# start the lock version at zero. Note we can't use
|
# start the lock version at zero. Note we can't use
|
||||||
|
@ -77,8 +73,8 @@ module ActiveRecord
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_with_lock(attribute_names = @attributes.keys) #:nodoc:
|
def update(attribute_names = @attributes.keys) #:nodoc:
|
||||||
return update_without_lock(attribute_names) unless locking_enabled?
|
return super unless locking_enabled?
|
||||||
return 0 if attribute_names.empty?
|
return 0 if attribute_names.empty?
|
||||||
|
|
||||||
lock_col = self.class.locking_column
|
lock_col = self.class.locking_column
|
||||||
|
@ -97,7 +93,6 @@ module ActiveRecord
|
||||||
)
|
)
|
||||||
).arel.update(arel_attributes_values(false, false, attribute_names))
|
).arel.update(arel_attributes_values(false, false, attribute_names))
|
||||||
|
|
||||||
|
|
||||||
unless affected_rows == 1
|
unless affected_rows == 1
|
||||||
raise ActiveRecord::StaleObjectError, "Attempted to update a stale object: #{self.class.name}"
|
raise ActiveRecord::StaleObjectError, "Attempted to update a stale object: #{self.class.name}"
|
||||||
end
|
end
|
||||||
|
@ -111,8 +106,8 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy_with_lock #:nodoc:
|
def destroy #:nodoc:
|
||||||
return destroy_without_lock unless locking_enabled?
|
return super unless locking_enabled?
|
||||||
|
|
||||||
unless new_record?
|
unless new_record?
|
||||||
lock_col = self.class.locking_column
|
lock_col = self.class.locking_column
|
||||||
|
@ -136,12 +131,6 @@ module ActiveRecord
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
DEFAULT_LOCKING_COLUMN = 'lock_version'
|
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
|
# Is optimistic locking enabled for this table? Returns true if the
|
||||||
# +lock_optimistically+ flag is set to true (which it is, by default)
|
# +lock_optimistically+ flag is set to true (which it is, by default)
|
||||||
# and the table includes the +locking_column+ column (defaults to
|
# 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
|
# Make sure the lock version column gets updated when counters are
|
||||||
# updated.
|
# updated.
|
||||||
def update_counters_with_lock(id, counters)
|
def update_counters(id, counters)
|
||||||
counters = counters.merge(locking_column => 1) if locking_enabled?
|
counters = counters.merge(locking_column => 1) if locking_enabled?
|
||||||
update_counters_without_lock(id, counters)
|
super
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
included do
|
included do
|
||||||
alias_method_chain :create, :timestamps
|
|
||||||
alias_method_chain :update, :timestamps
|
|
||||||
|
|
||||||
class_inheritable_accessor :record_timestamps, :instance_writer => false
|
class_inheritable_accessor :record_timestamps, :instance_writer => false
|
||||||
self.record_timestamps = true
|
self.record_timestamps = true
|
||||||
end
|
end
|
||||||
|
@ -39,35 +36,34 @@ module ActiveRecord
|
||||||
save!
|
save!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def create #:nodoc:
|
||||||
|
if record_timestamps
|
||||||
|
current_time = current_time_from_proper_timezone
|
||||||
|
|
||||||
private
|
write_attribute('created_at', current_time) if respond_to?(:created_at) && created_at.nil?
|
||||||
def create_with_timestamps #:nodoc:
|
write_attribute('created_on', current_time) if respond_to?(:created_on) && created_on.nil?
|
||||||
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('updated_at', current_time) if respond_to?(:updated_at) && updated_at.nil?
|
||||||
write_attribute('created_on', current_time) if respond_to?(:created_on) && created_on.nil?
|
write_attribute('updated_on', current_time) if respond_to?(:updated_on) && updated_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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_with_timestamps(*args) #:nodoc:
|
super
|
||||||
if record_timestamps && (!partial_updates? || changed?)
|
end
|
||||||
current_time = current_time_from_proper_timezone
|
|
||||||
|
|
||||||
write_attribute('updated_at', current_time) if respond_to?(:updated_at)
|
def update(*args) #:nodoc:
|
||||||
write_attribute('updated_on', current_time) if respond_to?(:updated_on)
|
if record_timestamps && (!partial_updates? || changed?)
|
||||||
end
|
current_time = current_time_from_proper_timezone
|
||||||
|
|
||||||
update_without_timestamps(*args)
|
write_attribute('updated_at', current_time) if respond_to?(:updated_at)
|
||||||
end
|
write_attribute('updated_on', current_time) if respond_to?(:updated_on)
|
||||||
|
|
||||||
def current_time_from_proper_timezone
|
|
||||||
self.class.default_timezone == :utc ? Time.now.utc : Time.now
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_time_from_proper_timezone
|
||||||
|
self.class.default_timezone == :utc ? Time.now.utc : Time.now
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -9,10 +9,6 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
included do
|
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 :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
|
define_model_callbacks :rollback, :rollback_on_update, :rollback_on_create, :rollback_on_destroy
|
||||||
end
|
end
|
||||||
|
@ -213,16 +209,18 @@ module ActiveRecord
|
||||||
self.class.transaction(&block)
|
self.class.transaction(&block)
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy_with_transactions #:nodoc:
|
def destroy #:nodoc:
|
||||||
with_transaction_returning_status(:destroy_without_transactions)
|
with_transaction_returning_status { super }
|
||||||
end
|
end
|
||||||
|
|
||||||
def save_with_transactions(*args) #:nodoc:
|
def save(*) #:nodoc:
|
||||||
rollback_active_record_state! { with_transaction_returning_status(:save_without_transactions, *args) }
|
rollback_active_record_state! do
|
||||||
|
with_transaction_returning_status { super }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def save_with_transactions! #:nodoc:
|
def save!(*) #:nodoc:
|
||||||
with_transaction_returning_status(:save_without_transactions!)
|
with_transaction_returning_status { super }
|
||||||
end
|
end
|
||||||
|
|
||||||
# Reset id and @new_record if the transaction rolls back.
|
# 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
|
# This method is available within the context of an ActiveRecord::Base
|
||||||
# instance.
|
# instance.
|
||||||
def with_transaction_returning_status(method, *args)
|
def with_transaction_returning_status
|
||||||
status = nil
|
status = nil
|
||||||
self.class.transaction do
|
self.class.transaction do
|
||||||
add_to_transaction
|
add_to_transaction
|
||||||
status = send(method, *args)
|
status = yield
|
||||||
raise ActiveRecord::Rollback unless status
|
raise ActiveRecord::Rollback unless status
|
||||||
end
|
end
|
||||||
status
|
status
|
||||||
|
|
|
@ -19,11 +19,6 @@ module ActiveRecord
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
include ActiveModel::Validations
|
include ActiveModel::Validations
|
||||||
|
|
||||||
included do
|
|
||||||
alias_method_chain :save, :validation
|
|
||||||
alias_method_chain :save!, :validation
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
# Creates an object just like Base.create but calls save! instead of save
|
# Creates an object just like Base.create but calls save! instead of save
|
||||||
# so an exception is raised if the record is invalid.
|
# so an exception is raised if the record is invalid.
|
||||||
|
@ -39,39 +34,37 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module InstanceMethods
|
# The validation process on save can be skipped by passing false. The regular Base#save method is
|
||||||
# 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.
|
||||||
# replaced with this when the validations module is mixed in, which it is by default.
|
def save(options=nil)
|
||||||
def save_with_validation(options=nil)
|
return super if valid?(options)
|
||||||
perform_validation = case options
|
false
|
||||||
when NilClass
|
end
|
||||||
true
|
|
||||||
when Hash
|
|
||||||
options[:validate] != false
|
|
||||||
else
|
|
||||||
ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller
|
|
||||||
options
|
|
||||||
end
|
|
||||||
|
|
||||||
if perform_validation && valid? || !perform_validation
|
def save_without_validation!
|
||||||
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
|
else
|
||||||
false
|
ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller
|
||||||
end
|
options
|
||||||
end
|
end
|
||||||
|
|
||||||
# Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false
|
if perform_validation
|
||||||
# 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?
|
|
||||||
errors.clear
|
errors.clear
|
||||||
|
|
||||||
self.validation_context = new_record? ? :create : :update
|
self.validation_context = new_record? ? :create : :update
|
||||||
|
@ -86,16 +79,12 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
errors.empty?
|
errors.empty?
|
||||||
end
|
else
|
||||||
|
true
|
||||||
def invalid?
|
|
||||||
!valid?
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Dir[File.dirname(__FILE__) + "/validations/*.rb"].sort.each do |path|
|
require "active_record/validations/associated"
|
||||||
filename = File.basename(path)
|
require "active_record/validations/uniqueness"
|
||||||
require "active_record/validations/#{filename}"
|
|
||||||
end
|
|
||||||
|
|
|
@ -195,7 +195,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase
|
||||||
assert_raises(ActiveRecord::RecordNotFound) { Person.find(p1.id) }
|
assert_raises(ActiveRecord::RecordNotFound) { Person.find(p1.id) }
|
||||||
assert_raises(ActiveRecord::RecordNotFound) { LegacyThing.find(t.id) }
|
assert_raises(ActiveRecord::RecordNotFound) { LegacyThing.find(t.id) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_quote_table_name
|
def test_quote_table_name
|
||||||
ref = references(:michael_magician)
|
ref = references(:michael_magician)
|
||||||
ref.favourite = !ref.favourite
|
ref.favourite = !ref.favourite
|
||||||
|
@ -206,8 +206,11 @@ class OptimisticLockingTest < ActiveRecord::TestCase
|
||||||
# is nothing else being updated.
|
# is nothing else being updated.
|
||||||
def test_update_without_attributes_does_not_only_update_lock_version
|
def test_update_without_attributes_does_not_only_update_lock_version
|
||||||
assert_nothing_raised do
|
assert_nothing_raised do
|
||||||
p1 = Person.new(:first_name => 'anika')
|
p1 = Person.create!(:first_name => 'anika')
|
||||||
p1.send(:update_with_lock, [])
|
lock_version = p1.lock_version
|
||||||
|
p1.save
|
||||||
|
p1.reload
|
||||||
|
assert_equal lock_version, p1.lock_version
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue