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

Code before:

      attributes = attributes.transform_keys do |key|
        name = key.to_s
        name = self.class.attribute_aliases[name] || name
        verify_readonly_attribute(name) || name
      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

    # Wrapper around #increment that writes the update to the database.
    # Only +attribute+ is updated; the record itself is not saved.
    # This means that any other modified attributes will still be dirty.
    # Validations and callbacks are skipped. Supports the +touch+ option from
    # +update_counters+, see that for more.
    #
    # This method raises an ActiveRecord::ActiveRecordError when called on new
    # objects, or when at least one of the attributes is marked as readonly.
    #
    # Returns +self+.
    def increment!(attribute, by = 1, touch: nil)
      raise ActiveRecordError, "cannot update a new record" if new_record?
      raise ActiveRecordError, "cannot update a destroyed record" if destroyed?

      increment(attribute, by)
      change = public_send(attribute) - (public_send(:"#{attribute}_in_database") || 0)
      self.class.update_counters(id, attribute => change, touch: touch)
      public_send(:"clear_#{attribute}_change")
      self
    end


Review comment (refactor):
Why `extract_options!` here? I don't see any option being passed in the tests.

Code after:
    # * If attribute names are passed, they are updated along with +updated_at+/+updated_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.with_defaults!(touch_updates) unless touch_updates.empty?
      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