Logic of `where.not` with multiple attributes

Updated . Posted . Visible to the public. Repeats.

When using where.not with a Hash of multiple attributes, Rails applies logical NAND (NOT (A AND B)).

This contrasts with logical NOR (NOT A AND NOT B), which is achieved by chaining multiple where.not calls.

The difference in logic alters the scope of excluded records:

  • NAND: Excludes records only if they match all attributes simultaneously.
  • NOR: Excludes records if they match any of the attributes.
NAND NOR
NAND NOR

Example

The semantic difference can be illustrated with a newsletter requirement:

  • NAND: "Don't send newsletters to trashed admins."
  • NOR: "Don't send newsletters neither to admins nor to trashed users!"

In the illustration above, assume the left circle is "admins" and the right circle is "trashed users". The red area corresponds to the records in the resulting scope.

# NAND: Default behavior for Hash with multiple attributes
# Excludes only users who are BOTH admins AND trashed.
User.where.not(role: 'admin', trashed: true)
=> "SELECT "users".* FROM "users" WHERE NOT ("users"."role" = 'admin' AND "users"."trashed" = TRUE)"

# NOR: Achieved by chaining
# Excludes admins, and also excludes trashed users.
User.where.not(role: 'admin').where.not(trashed: true)
=> "SELECT "users".* FROM "users" WHERE "users"."role" != 'admin' AND "users"."trashed" != TRUE"

Exception: Array arguments

When a single key points to an Array of values, Rails applies standard SQL NOT IN logic. This behaves effectively as a NOR condition relative to the values in the array (neither A nor B).

User.where.not(email: [nil, ''])
=> "SELECT "users".* FROM "users" WHERE NOT (("users"."email" = '' OR "users"."email" IS NULL))"

# Equivalent to this NOR query:
=> "SELECT "users".* FROM "users" WHERE "users"."email" != '' AND "users"."email" IS NOT NULL"

Notes

  • The Rubocop rule Rails/WhereNotWithMultipleConditions Show archive.org snapshot can be enabled to flag the hash syntax. This encourages the use of explicit chaining to avoid ambiguity regarding the intended logic.
  • In Rails versions prior to 6.1 (released in 2020), the hash syntax behavior was implementation of NOR. It was changed to NAND to align strictly with SQL negation semantics.
Profile picture of Julian
Julian
Last edit
Michael Leimstädtner
Attachments
License
Source code in this card is licensed under the MIT License.
Posted by Julian to makandra dev (2021-12-15 15:14)