Read more

Rails 6.1: where.not changes behaviour from NOR to NAND

Julian
December 15, 2021Software engineer at makandra GmbH

Since Rails 6.1, if we use where.not with multiple attributes, it applies logical NAND (NOT(A) OR NOT(B)) instead of NOR (NOT(A) AND NOT(B)). If you do not take care, this change will increase the matched set.

Examples

Illustration online protection

Rails Long Term Support

Rails LTS provides security patches for old versions of Ruby on Rails (2.3, 3.2, 4.2 and 5.2)

  • Prevents you from data breaches and liability risks
  • Upgrade at your own pace
  • Works with modern Rubies
Read more Show archive.org snapshot

"Don't send newsletters neither to admins nor to trashed users!" becomes "Don't send newsletters to trashed admins".

User.where.not(role: 'admin', trashed: true)
# Before Rails 6.1, with NOR
=> "SELECT "users".* FROM "users" WHERE "users"."role" != 'admin' AND "users"."trashed" != TRUE"
# Equivalent:
=> "SELECT "users".* FROM "users" WHERE NOT ("users"."role" = 'admin' OR "users"."trashed" = TRUE")

# From Rails 6.1, with NAND
=> "SELECT "users".* FROM "users" WHERE NOT ("users"."role" = 'admin' AND "users"."trashed" = TRUE)"

# Use this query to keep applying NOR
User.where.not(role: 'admin').where.not(trashed: true)
=> "SELECT "users".* FROM "users" WHERE "users"."role" != 'admin' AND "users"."trashed" != TRUE"

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

NOR NAND (Rails 6.1+)
NOR NAND

Using an Array as an argument is still working as expected

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

# This is equivalent to the following query. So here we are still using NOR.
=> "SELECT "users".* FROM "users" WHERE "users"."email" != '' AND "users"."email" IS NOT NULL"
Posted by Julian to makandra dev (2021-12-15 16:14)