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

Code before:
      end
    end

    # Equivalent to <code>update_columns(name => value)</code>.
    def update_column(name, value, touch: nil)
      update_columns(name => value, :touch => touch)
    end

    # Updates the attributes directly in the database issuing an UPDATE SQL
    # statement and sets them in the receiver.
    #
    # This is the fastest way to update attributes because it goes straight to
    # the database, but take into account that in consequence the regular update
    # procedures are totally bypassed. In particular:
    #
    # * \Validations are skipped.
    # * \Callbacks are skipped.
    # * +updated_at+/+updated_on+ are updated if the +touch+ option is set to +true+. Otherwise, they're not updated.
    # * However, attributes are serialized with the same rules as ActiveRecord::Relation#update_all
    #
    # This method raises an ActiveRecord::ActiveRecordError when called on new
    # objects, or when at least one of the attributes is marked as readonly.
    #
    # ==== Parameters
    # * <tt>:touch</tt> option - Touch the timestamp columns when updating.
    # * 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?

Review comment (suggestion):
```suggestion
    # * If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
```

Code after:
      def _update_row(attribute_names, attempted_action = "update")
        self.class._update_record(
          attributes_with_values(attribute_names),
          _query_constraints_hash
        )
      end

      def create_or_update(**, &block)
        _raise_readonly_record_error if readonly?
        return false if destroyed?
        result = new_record? ? _create_record(&block) : _update_record(&block)
        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 = self.attribute_names)
        attribute_names = attributes_for_update(attribute_names)

        if attribute_names.empty?
          affected_rows = 0
          @_trigger_update_callback = true
        else
          affected_rows = _update_row(attribute_names)
          @_trigger_update_callback = affected_rows == 1
        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