persistence.rb 12.2 KB
Newer Older
1
module ActiveRecord
R
Rizwan Reza 已提交
2
  # = Active Record Persistence
3
  module Persistence
4
    # Returns true if this object hasn't been saved yet -- that is, a record
R
Rizwan Reza 已提交
5
    # for the object doesn't exist in the data store yet; otherwise, returns false.
6
    def new_record?
7
      @new_record
8 9 10 11 12 13 14
    end

    # Returns true if this object has been destroyed, otherwise returns false.
    def destroyed?
      @destroyed
    end

R
Rizwan Reza 已提交
15 16
    # Returns if the record is persisted, i.e. it's not a new record and it was
    # not destroyed.
17
    def persisted?
18
      !(new_record? || destroyed?)
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
    end

    # 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.
    #
60
    # The row is simply removed with an SQL +DELETE+ statement on the
61 62 63 64 65 66
    # 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
67 68
      if persisted?
        self.class.delete(id)
69
        IdentityMap.remove(self) if IdentityMap.enabled?
70
      end
71 72 73 74
      @destroyed = true
      freeze
    end

75
    # Deletes the record in the database and freezes this instance to reflect
R
Rizwan Reza 已提交
76
    # that no changes should be made (since they can't be persisted).
77 78
    def destroy
      if persisted?
79
        IdentityMap.remove(self) if IdentityMap.enabled?
80 81 82 83 84 85 86
        self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all
      end

      @destroyed = true
      freeze
    end

87 88 89 90 91
    # 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
R
Rizwan Reza 已提交
92 93
    # like render <tt>:partial => @client.becomes(Company)</tt> to render that
    # instance using the companies/company partial instead of clients/client.
94
    #
95
    # Note: The new instance will share a link to the same attributes as the original class.
96
    # So any change to the attributes in either instance will affect the other.
97 98 99 100
    def becomes(klass)
      became = klass.new
      became.instance_variable_set("@attributes", @attributes)
      became.instance_variable_set("@attributes_cache", @attributes_cache)
101
      became.instance_variable_set("@new_record", new_record?)
102
      became.instance_variable_set("@destroyed", destroyed?)
103
      became.type = klass.name unless self.class.descends_from_active_record?
104 105 106
      became
    end

107 108
    # Updates a single attribute and saves the record.
    # This is especially useful for boolean flags on existing records. Also note that
109
    #
N
Neeraj Singh 已提交
110
    # * Validation is skipped.
111 112 113
    # * Callbacks are invoked.
    # * updated_at/updated_on column is updated if that column is available.
    # * Updates all the attributes that are dirty in this object.
114
    #
115
    def update_attribute(name, value)
116 117 118
      name = name.to_s
      raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
      send("#{name}=", value)
119
      save(:validate => false)
120 121
    end

122 123 124 125 126 127 128 129 130 131 132 133 134 135
    # Updates a single attribute of an object, without calling save.
    #
    # * Validation is skipped.
    # * Callbacks are skipped.
    # * updated_at/updated_on column is not updated if that column is available.
    #
    def update_column(name, value)
      name = name.to_s
      raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
      raise ActiveRecordError, "can not update on a new record object" unless persisted?
      raw_write_attribute(name, value)
      self.class.update_all({ name => value }, self.class.primary_key => id) == 1
    end

136 137 138
    # Updates the attributes of the model from the passed-in hash and saves the
    # record, all wrapped in a transaction. If the object is invalid, the saving
    # will fail and false will be returned.
139 140 141 142 143 144
    #
    # When updating model attributes, mass-assignment security protection is respected.
    # If no +:as+ option is supplied then the :default scope will be used.
    # If you want to bypass the protection given by +attr_protected+ and
    # +attr_accessible+ then you can do so using the +:without_protection+ option.
    def update_attributes(attributes, options = {})
145 146
      # The following transaction covers any possible database side-effects of the
      # attributes assignment. For example, setting the IDs of a child collection.
147
      with_transaction_returning_status do
148
        self.assign_attributes(attributes, options)
149 150
        save
      end
151 152
    end

153 154
    # Updates its receiver just like +update_attributes+ but calls <tt>save!</tt> instead
    # of +save+, so an exception is raised if the record is invalid.
155
    def update_attributes!(attributes, options = {})
156 157
      # The following transaction covers any possible database side-effects of the
      # attributes assignment. For example, setting the IDs of a child collection.
158
      with_transaction_returning_status do
159
        self.assign_attributes(attributes, options)
160 161
        save!
      end
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
    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)
178
      increment(attribute, by).update_attribute(attribute, self[attribute])
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
    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)
195
      decrement(attribute, by).update_attribute(attribute, self[attribute])
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
    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)
212
      toggle(attribute).update_attribute(attribute, self[attribute])
213 214 215 216 217 218 219 220 221
    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
222 223 224 225 226 227

      IdentityMap.without do
        fresh_object = self.class.unscoped { self.class.find(self.id, options) }
        @attributes.update(fresh_object.instance_variable_get('@attributes'))
      end

228 229 230 231
      @attributes_cache = {}
      self
    end

232 233
    # 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.
234
    # If an attribute name is passed, that attribute is updated along with
235 236 237 238
    # updated_at/on attributes.
    #
    #   product.touch               # updates updated_at/on
    #   product.touch(:designed_at) # updates the designed_at attribute and updated_at/on
239 240 241
    #
    # If used along with +belongs_to+ then +touch+ will invoke +touch+ method on associated object.
    #
X
Xavier Noria 已提交
242 243 244 245 246 247 248 249 250 251
    #   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
252 253
    def touch(name = nil)
      attributes = timestamp_attributes_for_update_in_model
254
      attributes << name if name
255

256
      unless attributes.empty?
257 258
        current_time = current_time_from_proper_timezone
        changes = {}
259

260 261 262
        attributes.each do |column|
          changes[column.to_s] = write_attribute(column.to_s, current_time)
        end
263

264
        changes[self.class.locking_column] = increment_lock if locking_enabled?
265

266 267 268 269
        @changed_attributes.except!(*changes.keys)
        primary_key = self.class.primary_key
        self.class.update_all(changes, { primary_key => self[primary_key] }) == 1
      end
270 271
    end

272 273 274
  private
    def create_or_update
      raise ReadOnlyRecord if readonly?
275
      result = new_record? ? create : update
276 277 278 279 280 281 282 283
      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?
284
      klass = self.class
285 286
      stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(attributes_with_values)
      klass.connection.update stmt.to_sql
287 288 289 290 291
    end

    # Creates a record with values matching those of the instance attributes
    # and returns its id.
    def create
292
      attributes_values = arel_attributes_values(!id.nil?)
293

294
      new_id = self.class.unscoped.insert attributes_values
295 296 297

      self.id ||= new_id

298
      IdentityMap.add(self) if IdentityMap.enabled?
299
      @new_record = false
300 301 302 303 304 305 306 307
      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
E
Emilio Tagua 已提交
308
      Hash[self.class.columns.map do |column|
309
        [column.name, column.default]
E
Emilio Tagua 已提交
310
      end]
311 312
    end
  end
313
end