aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/persistence.rb
diff options
context:
space:
mode:
authorwycats <wycats@gmail.com>2010-05-09 02:06:05 +0300
committerwycats <wycats@gmail.com>2010-05-09 02:37:52 +0300
commitd916c62cfc7c59ab6411407a05b946d3dd7535e9 (patch)
tree653ac4017ea6af4520dd9e6f60d846e19d5e3074 /activerecord/lib/active_record/persistence.rb
parent636ffa1f089a51c98fce616191846eaba93d7b87 (diff)
downloadrails-d916c62cfc7c59ab6411407a05b946d3dd7535e9.tar.gz
rails-d916c62cfc7c59ab6411407a05b946d3dd7535e9.tar.bz2
rails-d916c62cfc7c59ab6411407a05b946d3dd7535e9.zip
eliminate alias_method_chain from ActiveRecord
Diffstat (limited to 'activerecord/lib/active_record/persistence.rb')
-rw-r--r--activerecord/lib/active_record/persistence.rb230
1 files changed, 230 insertions, 0 deletions
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
new file mode 100644
index 0000000000..10788630a5
--- /dev/null
+++ b/activerecord/lib/active_record/persistence.rb
@@ -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 \ No newline at end of file