diff options
author | Gonçalo Silva <goncalossilva@gmail.com> | 2011-03-24 17:21:17 +0000 |
---|---|---|
committer | Gonçalo Silva <goncalossilva@gmail.com> | 2011-03-24 17:21:17 +0000 |
commit | 9887f238871bb2dd73de6ce8855615bcc5d8d079 (patch) | |
tree | 74fa9ff9524a51701cfa23f708b3f777c65b7fe5 /activerecord/lib/active_record/persistence.rb | |
parent | aff821508a16245ebc03510ba29c70379718dfb7 (diff) | |
parent | 5214e73850916de3c9127d35a4ecee0424d364a3 (diff) | |
download | rails-9887f238871bb2dd73de6ce8855615bcc5d8d079.tar.gz rails-9887f238871bb2dd73de6ce8855615bcc5d8d079.tar.bz2 rails-9887f238871bb2dd73de6ce8855615bcc5d8d079.zip |
Merge branch 'master' of https://github.com/rails/rails
Diffstat (limited to 'activerecord/lib/active_record/persistence.rb')
-rw-r--r-- | activerecord/lib/active_record/persistence.rb | 123 |
1 files changed, 68 insertions, 55 deletions
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 71b46beaef..17a64b6e86 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -1,7 +1,7 @@ module ActiveRecord # = Active Record Persistence module Persistence - # Returns true if this object hasn't been saved yet -- that is, a record + # Returns true if this object hasn't been saved yet -- that is, a record # for the object doesn't exist in the data store yet; otherwise, returns false. def new_record? @new_record @@ -18,9 +18,6 @@ module ActiveRecord !(new_record? || destroyed?) end - # :call-seq: - # save(options) - # # Saves the model. # # If the model is new a record gets created in the database, otherwise @@ -67,15 +64,19 @@ module ActiveRecord # callbacks, Observer methods, or any <tt>:dependent</tt> association # options, use <tt>#destroy</tt>. def delete - self.class.delete(id) if persisted? + if persisted? + self.class.delete(id) + IdentityMap.remove(self) if IdentityMap.enabled? + end @destroyed = true freeze end - # Deletes the record in the database and freezes this instance to reflect + # 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? + IdentityMap.remove(self) if IdentityMap.enabled? self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all end @@ -83,15 +84,15 @@ module ActiveRecord 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 + # 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. + # 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 @@ -99,37 +100,23 @@ module ActiveRecord became.instance_variable_set("@attributes_cache", @attributes_cache) became.instance_variable_set("@new_record", new_record?) became.instance_variable_set("@destroyed", destroyed?) + became.type = klass.name unless self.class.descends_from_active_record? became end - # Updates a single attribute and saves the record. + # Updates a single attribute and saves the record. # This is especially useful for boolean flags on existing records. Also note that # - # * The attribute being updated must be a column name. # * Validation is skipped. - # * No callbacks are invoked. + # * Callbacks are invoked. # * updated_at/updated_on column is updated if that column is available. - # * Does not work on associations. - # * Does not work on attr_accessor attributes. - # * Does not work on new record. <tt>record.new_record?</tt> should return false for this method to work. - # * Updates only the attribute that is input to the method. If there are other changed attributes then - # those attributes are left alone. In that case even after this method has done its work <tt>record.changed?</tt> - # will return true. + # * Updates all the attributes that are dirty in this object. # def update_attribute(name, value) - raise ActiveRecordError, "#{name.to_s} is marked as readonly" if self.class.readonly_attributes.include? name.to_s - - changes = record_update_timestamps || {} - - if name - name = name.to_s - send("#{name}=", value) - changes[name] = read_attribute(name) - end - - @changed_attributes.except!(*changes.keys) - primary_key = self.class.primary_key - self.class.update_all(changes, { primary_key => self[primary_key] }) == 1 + name = name.to_s + raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name) + send("#{name}=", value) + save(:validate => false) end # Updates the attributes of the model from the passed-in hash and saves the @@ -213,22 +200,54 @@ module ActiveRecord def reload(options = nil) clear_aggregation_cache clear_association_cache - @attributes.update(self.class.unscoped { self.class.find(self.id, options) }.instance_variable_get('@attributes')) + + IdentityMap.without do + fresh_object = self.class.unscoped { self.class.find(self.id, options) } + @attributes.update(fresh_object.instance_variable_get('@attributes')) + end + @attributes_cache = {} self end # Saves the record with the updated_at/on attributes set to the current time. # Please note that no validation is performed and no callbacks are executed. - # If an attribute name is passed, that attribute is updated along with + # If an attribute name is passed, that attribute is updated along with # updated_at/on attributes. # - # Examples: - # # product.touch # updates updated_at/on # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on - def touch(attribute = nil) - update_attribute(attribute, current_time_from_proper_timezone) + # + # If used along with +belongs_to+ then +touch+ will invoke +touch+ method on associated object. + # + # class Brake < ActiveRecord::Base + # belongs_to :car, :touch => true + # end + # + # class Car < ActiveRecord::Base + # belongs_to :corporation, :touch => true + # end + # + # # triggers @brake.car.touch and @brake.car.corporation.touch + # @brake.touch + def touch(name = nil) + attributes = timestamp_attributes_for_update_in_model + attributes << name if name + + unless attributes.empty? + current_time = current_time_from_proper_timezone + changes = {} + + attributes.each do |column| + changes[column.to_s] = write_attribute(column.to_s, current_time) + end + + changes[self.class.locking_column] = increment_lock if locking_enabled? + + @changed_attributes.except!(*changes.keys) + primary_key = self.class.primary_key + self.class.update_all(changes, { primary_key => self[primary_key] }) == 1 + end end private @@ -243,26 +262,21 @@ module ActiveRecord 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) + klass = self.class + stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(attributes_with_values) + klass.connection.update stmt.to_sql 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(!id.nil?) - 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 + new_id = self.class.unscoped.insert attributes_values self.id ||= new_id + IdentityMap.add(self) if IdentityMap.enabled? @new_record = false id end @@ -272,10 +286,9 @@ module ActiveRecord # 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 + Hash[self.class.columns.map do |column| + [column.name, column.default] + end] end end end |