Updated: ActiveRecord: Passing an empty array into NOT IN will return no records

Posted . Visible to the public. Auto-destruct in 56 days

Rewritten.

Changes

  • -Be careful with the Active Record `where` method. When you accidentally pass an empty array to the `where` method using `NOT IN`, you probably will not get what you expected:
  • +Caution when using `.where` to exclude records from a scope like this:
  • +```rb
  • +# Fragile - avoid
  • +User.where("id NOT IN (?)", excluded_ids)
  • +```
  • +When the exclusion list is empty, you would expect this to return all records. However, this is not what happens:
  • ```rb
  • -User.where("id NOT IN (?)", [])
  • +# Broken example
  • +User.where("id NOT IN (?)", []).to_sql
  • => SELECT `users`.* FROM `users` WHERE (id NOT IN (NULL))
  • ```
  • -
  • -Even though you might expect this to return all records, this actually results none.
  • -
  • -**Never use the expression `id NOT IN (?)` without taking care of this case**! See below some workarounds.
  • +**Passing an empty exclusion list returns no records at all!** See below for better implementations.
  • +## Rails 4+
  • -## Rails >= 4
  • -
  • -If you use the same expression as above in Rails >= 4, it is still broken. But you can use the `.not` method to work around this issue.
  • +Use the `.not` method to let Rails do the logic
  • ```rb
  • +# Good
  • User.where.not(id: []).to_sql
  • => SELECT "users".* FROM "users" WHERE (1=1)
  • User.where.not(id: [1]).to_sql
  • => SELECT "users".* FROM "users" WHERE ("users"."id" != 1)
  • -puts User.where.not(id: [1, 2]).to_sql
  • +User.where.not(id: [1, 2]).to_sql
  • => SELECT "users".* FROM "users" WHERE "users"."id" NOT IN (1, 2)
  • ```
  • -
  • ## Rails < 4
  • -Rails < 4 does not provide a pretty workaround.
  • -
  • +Before Rails 4, you needed to workaround this yourself:
  • ```rb
  • -ids = []
  • -
  • -if ids.present?
  • - User.where("id NOT IN (?)", ids)
  • -else
  • - User.all
  • -end
  • +# Good
  • +excluded_ids.blank? ? User.all : User.where("id NOT IN (?)", excluded_ids)
  • ```
Dominik Schöler
License
Source code in this card is licensed under the MIT License.
Posted by Dominik Schöler to makandra dev (2025-02-17 09:08)