From 6b9448cdd227ef3adbe2f31ecaf64bc7ef062103 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Mon, 31 Mar 2008 01:10:04 +0000 Subject: Partial updates include only unsaved attributes. Off by default; set YourClass.partial_updates = true to enable. git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@9157 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- activerecord/lib/active_record/aggregations.rb | 4 +- activerecord/lib/active_record/base.rb | 8 ++-- activerecord/lib/active_record/callbacks.rb | 4 +- activerecord/lib/active_record/dirty.rb | 52 +++++++++++++++------- .../lib/active_record/locking/optimistic.rb | 9 ++-- activerecord/lib/active_record/timestamp.rb | 4 +- 6 files changed, 52 insertions(+), 29 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index cad8304bcf..61446cde36 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -174,11 +174,11 @@ module ActiveRecord module_eval do define_method("#{name}=") do |part| if part.nil? && allow_nil - mapping.each { |pair| @attributes[pair.first] = nil } + mapping.each { |pair| self[pair.first] = nil } instance_variable_set("@#{name}", nil) else part = conversion.call(part) unless part.is_a?(class_name.constantize) || conversion.nil? - mapping.each { |pair| @attributes[pair.first] = part.send(pair.last) } + mapping.each { |pair| self[pair.first] = part.send(pair.last) } instance_variable_set("@#{name}", part.freeze) end end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index af480a0797..fe38454226 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2407,8 +2407,8 @@ module ActiveRecord #:nodoc: # Updates the associated record with values matching those of the instance attributes. # Returns the number of affected rows. - def update - quoted_attributes = attributes_with_quotes(false, false) + def update(attribute_names = @attributes.keys) + quoted_attributes = attributes_with_quotes(false, false, attribute_names) return 0 if quoted_attributes.empty? connection.update( "UPDATE #{self.class.quoted_table_name} " + @@ -2500,10 +2500,10 @@ module ActiveRecord #:nodoc: # Returns a copy of the attributes hash where all the values have been safely quoted for use in # an SQL statement. - def attributes_with_quotes(include_primary_key = true, include_readonly_attributes = true) + def attributes_with_quotes(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys) quoted = {} connection = self.class.connection - @attributes.each_pair do |name, value| + attribute_names.each do |name| if column = column_for_attribute(name) quoted[name] = connection.quote(read_attribute(name), column) unless !include_primary_key && column.primary end diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 67a4117d20..a469af682b 100755 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -229,9 +229,9 @@ module ActiveRecord # Is called _after_ Base.save on existing objects that have a record. def after_update() end - def update_with_callbacks #:nodoc: + def update_with_callbacks(*args) #:nodoc: return false if callback(:before_update) == false - result = update_without_callbacks + result = update_without_callbacks(*args) callback(:after_update) result end diff --git a/activerecord/lib/active_record/dirty.rb b/activerecord/lib/active_record/dirty.rb index 4b65545851..6530500e56 100644 --- a/activerecord/lib/active_record/dirty.rb +++ b/activerecord/lib/active_record/dirty.rb @@ -28,12 +28,21 @@ module ActiveRecord # person.name = 'bob' # person.changed # => ['name'] # person.changes # => { 'name' => ['Bill', 'bob'] } + # + # Before modifying an attribute in-place: + # person.name_will_change! + # person.name << 'by' + # person.name_change # => ['uncle bob', 'uncle bobby'] module Dirty def self.included(base) - base.attribute_method_suffix '_changed?', '_change', '_was' + base.attribute_method_suffix '_changed?', '_change', '_will_change!', '_was' base.alias_method_chain :write_attribute, :dirty base.alias_method_chain :save, :dirty base.alias_method_chain :save!, :dirty + base.alias_method_chain :update, :dirty + + base.superclass_delegating_accessor :partial_updates + base.partial_updates = true end # Do any attributes have unsaved changes? @@ -81,6 +90,25 @@ module ActiveRecord @changed_attributes ||= {} end + # Handle *_changed? for method_missing. + def attribute_changed?(attr) + changed_attributes.include?(attr) + end + + # Handle *_change for method_missing. + def attribute_change(attr) + [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr) + end + + # Handle *_was for method_missing. + def attribute_was(attr) + attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr) + end + + # Handle *_will_change! for method_missing. + def attribute_will_change!(attr) + changed_attributes[attr] = clone_attribute_value(:read_attribute, attr) + end # Wrap write_attribute to remember original attribute value. def write_attribute_with_dirty(attr, value) @@ -88,7 +116,7 @@ module ActiveRecord # The attribute already has an unsaved change. unless changed_attributes.include?(attr) - old = read_attribute(attr) + old = clone_attribute_value(:read_attribute, attr) # Remember the original value if it's different. typecasted = if column = column_for_attribute(attr) @@ -103,20 +131,12 @@ module ActiveRecord write_attribute_without_dirty(attr, value) end - - # Handle *_changed? for method_missing. - def attribute_changed?(attr) - changed_attributes.include?(attr) - end - - # Handle *_change for method_missing. - def attribute_change(attr) - [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr) - end - - # Handle *_was for method_missing. - def attribute_was(attr) - attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr) + def update_with_dirty + if partial_updates? + update_without_dirty(changed) + else + update_without_dirty + end end end end diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 799309c17b..f2c2c5f070 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -66,17 +66,20 @@ module ActiveRecord return result end - def update_with_lock #:nodoc: - return update_without_lock unless locking_enabled? + def update_with_lock(attribute_names = @attributes.keys) #:nodoc: + return update_without_lock(attribute_names) unless locking_enabled? lock_col = self.class.locking_column previous_value = send(lock_col).to_i send(lock_col + '=', previous_value + 1) + attribute_names += [lock_col] + attribute_names.uniq! + begin affected_rows = connection.update(<<-end_sql, "#{self.class.name} Update with optimistic locking") UPDATE #{self.class.table_name} - SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false, false))} + SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false, false, attribute_names))} WHERE #{self.class.primary_key} = #{quote_value(id)} AND #{self.class.quoted_locking_column} = #{quote_value(previous_value)} end_sql diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 95e29847cb..dc95d2aabb 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -29,13 +29,13 @@ module ActiveRecord create_without_timestamps end - def update_with_timestamps #:nodoc: + def update_with_timestamps(*args) #:nodoc: if record_timestamps t = self.class.default_timezone == :utc ? Time.now.utc : Time.now write_attribute('updated_at', t) if respond_to?(:updated_at) write_attribute('updated_on', t) if respond_to?(:updated_on) end - update_without_timestamps + update_without_timestamps(*args) end end end -- cgit v1.2.3