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

Code before:
    end

    # Returns a new relation, which is the logical intersection of this relation and the one passed
    # as an argument.
    #
    # The two relations must be structurally compatible: they must be scoping the same model, and
    # they must differ only by #where (if no #group has been defined) or #having (if a #group is
    # present).
    #
    #    Post.where(id: [1, 2]).and(Post.where(id: [2, 3]))
    #    # SELECT `posts`.* FROM `posts` WHERE `posts`.`id` IN (1, 2) AND `posts`.`id` IN (2, 3)
    #
    def and(other)
      if other.is_a?(Relation)
        spawn.and!(other)
      else
        raise ArgumentError, "You have passed #{other.class.name} object to #and. Pass an ActiveRecord::Relation object instead."
      end
    end

    def and!(other) # :nodoc:
      incompatible_values = structurally_incompatible_values_for(other)

      unless incompatible_values.empty?
        raise ArgumentError, "Relation passed to #and must be structurally compatible. Incompatible values: #{incompatible_values}"
      end

      self.where_clause |= other.where_clause
      self.having_clause |= other.having_clause
      self.references_values |= other.references_values

      self
    end

    # Returns a new relation, which is the logical union of this relation and the one passed as an
    # argument.
    #
    # The two relations must be structurally compatible: they must be scoping the same model, and
    # they must differ only by #where (if no #group has been defined) or #having (if a #group is
    # present).
    #
    #    Post.where("id = 1").or(Post.where("author_id = 3"))
    #    # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
    #
    def or(other)
      if other.is_a?(Relation)
        spawn.or!(other)
      else
        raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
      end
    end

Review comment (suggestion):
```suggestion
      scope = spawn
      scope.where_clause = where_clause.invert
      scope
```

We don't change relation inline for public API methods in Relation.

Code after:
    end

    # Allows you to invert an entire where clause instead of manually applying conditions.
    #
    #   class User
    #     scope :active, -> { where(accepted: true, locked: false) }
    #   end
    #
    #   User.where(accepted: true)
    #   # WHERE `accepted` = 1
    #
    #   User.where(accepted: true).invert_where
    #   # WHERE `accepted` != 1
    #
    #   User.active
    #   # WHERE `accepted` = 1 AND `locked` = 0
    #
    #   User.active.invert_where
    #   # WHERE NOT (`accepted` = 1 AND `locked` = 0)
    def invert_where
      spawn.invert_where!
    end

    def invert_where! # :nodoc:
      self.where_clause = where_clause.invert
      self
    end

    # Returns a new relation, which is the logical intersection of this relation and the one passed
    # as an argument.
    #
    # The two relations must be structurally compatible: they must be scoping the same model, and
    # they must differ only by #where (if no #group has been defined) or #having (if a #group is
    # present).
    #
    #    Post.where(id: [1, 2]).and(Post.where(id: [2, 3]))
    #    # SELECT `posts`.* FROM `posts` WHERE `posts`.`id` IN (1, 2) AND `posts`.`id` IN (2, 3)
    #
    def and(other)
      if other.is_a?(Relation)
        spawn.and!(other)
      else
        raise ArgumentError, "You have passed #{other.class.name} object to #and. Pass an ActiveRecord::Relation object instead."
      end
    end

    def and!(other) # :nodoc:
      incompatible_values = structurally_incompatible_values_for(other)

      unless incompatible_values.empty?
        raise ArgumentError, "Relation passed to #and must be structurally compatible. Incompatible values: #{incompatible_values}"