Repository: rails/rails
File: activerecord/lib/active_record/persistence.rb (Ruby)

Code before:
    # * If attributes names are passed, they are updated along with update_at/on attributes.
    #
    # ==== Examples
    #   # Update a single attribute
    #   user.update_columns(last_request_at: Time.current)
    #   # Update with touch option
    #   user.update_columns(last_request_at: Time.current, touch: true)

    def update_columns(attributes)
      raise ActiveRecordError, "cannot update a new record" if new_record?
      raise ActiveRecordError, "cannot update a destroyed record" if destroyed?
      _raise_readonly_record_error if readonly?

      attributes = attributes.transform_keys do |key|
        name = key.to_s
        name = self.class.attribute_aliases[name] || name
        verify_readonly_attribute(name) || name
      end

      touch = attributes.delete("touch")
      if touch
        names = touch if touch != true
        names = Array.wrap(names)
        options = names.extract_options!
        touch_updates = self.class.touch_attributes_with_time(*names, **options)
        attributes.reverse_merge!(touch_updates) if touch_updates.any?
      end

      update_constraints = _query_constraints_hash
      attributes = attributes.each_with_object({}) do |(k, v), h|
        h[k] = @attributes.write_cast_value(k, v)
        clear_attribute_change(k)
      end

      affected_rows = self.class._update_record(
        attributes,
        update_constraints
      )

      affected_rows == 1
    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


Review comment (suggestion):
```suggestion
        attributes.reverse_merge!(touch_updates) unless touch_updates.empty?
```

Code after:
        end

        @previously_new_record = false

        yield(self) if block_given?

        affected_rows
      end

      # Creates a record with values matching those of the instance attributes
      # and returns its id.
      def _create_record(attribute_names = self.attribute_names)
        attribute_names = attributes_for_create(attribute_names)

        self.class.with_connection do |connection|
          returning_columns = self.class._returning_columns_for_insert(connection)

          returning_values = self.class._insert_record(
            connection,
            attributes_with_values(attribute_names),
            returning_columns
          )

          returning_columns.zip(returning_values).each do |column, value|
            _write_attribute(column, type_for_attribute(column).deserialize(value)) if !_read_attribute(column)
          end if returning_values
        end

        @new_record = false
        @previously_new_record = true

        yield(self) if block_given?

        id
      end

      def verify_readonly_attribute(name)
        raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attribute?(name)
      end

      def _raise_record_not_destroyed
        @_association_destroy_exception ||= nil
        key = self.class.primary_key
        raise @_association_destroy_exception || RecordNotDestroyed.new("Failed to destroy #{self.class} with #{key}=#{id}", self)
      ensure
        @_association_destroy_exception = nil
      end

      def _raise_readonly_record_error
        raise ReadOnlyRecord, "#{self.class} is marked as readonly"
      end