diff options
Diffstat (limited to 'activerecord/lib/active_record/persistence.rb')
-rw-r--r-- | activerecord/lib/active_record/persistence.rb | 87 |
1 files changed, 54 insertions, 33 deletions
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 460fbdb3f8..8d84a3acae 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -36,8 +36,25 @@ module ActiveRecord end end + # Creates an object (or multiple objects) and saves it to the database, + # if validations pass. Raises a RecordInvalid error if validations fail, + # unlike Base#create. + # + # The +attributes+ parameter can be either a Hash or an Array of Hashes. + # These describe which attributes to be created on the object, or + # multiple objects when given an Array of Hashes. + def create!(attributes = nil, &block) + if attributes.is_a?(Array) + attributes.collect { |attr| create!(attr, &block) } + else + object = new(attributes, &block) + object.save! + object + end + end + # Given an attributes hash, +instantiate+ returns a new instance of - # the appropriate class. + # the appropriate class. Accepts only keys as strings. # # For example, +Post.all+ may return Comments, Messages, and Emails # by storing the record's subclass in a +type+ attribute. By calling @@ -46,10 +63,10 @@ module ActiveRecord # # See +ActiveRecord::Inheritance#discriminate_class_for_record+ to see # how this "single-table" inheritance mapping is implemented. - def instantiate(record, column_types = {}) - klass = discriminate_class_for_record(record) - column_types = klass.decorate_columns(column_types.dup) - klass.allocate.init_with('attributes' => record, 'column_types' => column_types) + def instantiate(attributes, column_types = {}) + klass = discriminate_class_for_record(attributes) + attributes = klass.attributes_builder.build_from_database(attributes, column_types) + klass.allocate.init_with('attributes' => attributes, 'new_record' => false) end private @@ -64,7 +81,7 @@ module ActiveRecord end # 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. + # for the object doesn't exist in the database yet; otherwise, returns false. def new_record? sync_with_transaction_state @new_record @@ -122,7 +139,7 @@ module ActiveRecord # Attributes marked as readonly are silently ignored if the record is # being updated. def save!(*) - create_or_update || raise(RecordNotSaved) + create_or_update || raise(RecordNotSaved, self) end # Deletes the record in the database and freezes this instance to @@ -149,7 +166,7 @@ module ActiveRecord # and <tt>destroy</tt> returns +false+. See # ActiveRecord::Callbacks for further details. def destroy - raise ReadOnlyRecord if readonly? + raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly? destroy_associations destroy_row if persisted? @destroyed = true @@ -164,7 +181,7 @@ module ActiveRecord # and <tt>destroy!</tt> raises ActiveRecord::RecordNotDestroyed. See # ActiveRecord::Callbacks for further details. def destroy! - destroy || raise(ActiveRecord::RecordNotDestroyed) + destroy || raise(ActiveRecord::RecordNotDestroyed, self) end # Returns an instance of the specified +klass+ with the attributes of the @@ -180,7 +197,6 @@ module ActiveRecord def becomes(klass) became = klass.new became.instance_variable_set("@attributes", @attributes) - became.instance_variable_set("@attributes_cache", @attributes_cache) became.instance_variable_set("@changed_attributes", @changed_attributes) if defined?(@changed_attributes) became.instance_variable_set("@new_record", new_record?) became.instance_variable_set("@destroyed", destroyed?) @@ -214,6 +230,8 @@ module ActiveRecord # # This method raises an +ActiveRecord::ActiveRecordError+ if the # attribute is marked as readonly. + # + # See also +update_column+. def update_attribute(name, value) name = name.to_s verify_readonly_attribute(name) @@ -269,7 +287,8 @@ module ActiveRecord # This method raises an +ActiveRecord::ActiveRecordError+ when called on new # objects, or when at least one of the attributes is marked as readonly. def update_columns(attributes) - raise ActiveRecordError, "cannot update on a new record object" unless persisted? + raise ActiveRecordError, "cannot update a new record" if new_record? + raise ActiveRecordError, "cannot update a destroyed record" if destroyed? attributes.each_key do |key| verify_readonly_attribute(key.to_s) @@ -389,29 +408,29 @@ module ActiveRecord fresh_object = if options && options[:lock] - self.class.unscoped { self.class.lock.find(id) } + self.class.unscoped { self.class.lock(options[:lock]).find(id) } else self.class.unscoped { self.class.find(id) } end - @attributes.update(fresh_object.instance_variable_get('@attributes')) - - @column_types = self.class.column_types - @column_types_override = fresh_object.instance_variable_get('@column_types_override') - @attributes_cache = {} + @attributes = fresh_object.instance_variable_get('@attributes') + @new_record = false self end # Saves the record with the updated_at/on attributes set to the current time. - # Please note that no validation is performed and only the +after_touch+ - # callback is executed. - # If an attribute name is passed, that attribute is updated along with - # updated_at/on attributes. + # Please note that no validation is performed and only the +after_touch+, + # +after_commit+ and +after_rollback+ callbacks are executed. + # + # If attribute names are passed, they are updated along with updated_at/on + # attributes. # - # product.touch # updates updated_at/on - # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on + # product.touch # updates updated_at/on + # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on + # product.touch(:started_at, :ended_at) # updates started_at, ended_at and updated_at/on attributes # - # If used along with +belongs_to+ then +touch+ will invoke +touch+ method on associated object. + # If used along with +belongs_to+ then +touch+ will invoke +touch+ method on + # associated object. # # class Brake < ActiveRecord::Base # belongs_to :car, touch: true @@ -430,11 +449,11 @@ module ActiveRecord # ball = Ball.new # ball.touch(:updated_at) # => raises ActiveRecordError # - def touch(name = nil) + def touch(*names) raise ActiveRecordError, "cannot touch on a new record object" unless persisted? attributes = timestamp_attributes_for_update_in_model - attributes << name if name + attributes.concat(names) unless attributes.empty? current_time = current_time_from_proper_timezone @@ -447,9 +466,11 @@ module ActiveRecord changes[self.class.locking_column] = increment_lock if locking_enabled? - changed_attributes.except!(*changes.keys) + clear_attribute_changes(changes.keys) primary_key = self.class.primary_key self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1 + else + true end end @@ -466,7 +487,7 @@ module ActiveRecord def relation_for_destroy pk = self.class.primary_key column = self.class.columns_hash[pk] - substitute = self.class.connection.substitute_at(column, 0) + substitute = self.class.connection.substitute_at(column) relation = self.class.unscoped.where( self.class.arel_table[pk].eq(substitute)) @@ -476,25 +497,25 @@ module ActiveRecord end def create_or_update - raise ReadOnlyRecord if readonly? - result = new_record? ? create_record : update_record + raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly? + result = new_record? ? _create_record : _update_record result != false end # Updates the associated record with values matching those of the instance attributes. # Returns the number of affected rows. - def update_record(attribute_names = @attributes.keys) + def _update_record(attribute_names = self.attribute_names) attributes_values = arel_attributes_with_values_for_update(attribute_names) if attributes_values.empty? 0 else - self.class.unscoped.update_record attributes_values, id, id_was + self.class.unscoped._update_record attributes_values, id, id_was end end # Creates a record with values matching those of the instance attributes # and returns its id. - def create_record(attribute_names = @attributes.keys) + def _create_record(attribute_names = self.attribute_names) attributes_values = arel_attributes_with_values_for_create(attribute_names) new_id = self.class.unscoped.insert attributes_values |