Rails 7 introduced invert_where
. This method inverts all scope conditions that it’s applied to, simplifying the process of defining the opposite of a where
clause in ActiveRecord queries. It also comes with a few caveats that should be considered before using it.
What is invert_where
?
invert_where
is a Rails method that can fetch all the data that is excluded by an existing query.
Let’s take this example: finding recent claims that have been raised on completed orders and have an amount of less than £100.
recent_claims = Claim.joins(:order)
.where(orders: { status: "completed" })
.where("claims.created_at >= ?", 10.days.ago)
.where("claims.claim_amount < ?", 100)
If you want to query data opposite to the query condition, you would need to write the opposite query like:
old_claims = Claim.joins(:order)
.where.not(orders: { status: "completed" })
.where("claims.created_at < ?", 10.days.ago)
.where("claims.claim_amount >= ?", 100)
Using invert_where
the opposite condition becomes as simple as
old_claims = recent_claims.invert_where
invert_where
Gotcha
Using invert_where
in models with default scopes can lead to unintended inversions. For instance:
class Claim < ApplicationRecord
default_scope { where(archived: false) }
scope :recent, -> { where("created_at >= ?", 10.days.ago) }
end
# Inverted scope
old_claims = Claim.recent.invert_where
In the above example, invert_where
will invert both the recent scope and the default_scope
, potentially returning archived claims, which is most likely not the desired result.
Conclusion
Prefer Using Explicit Conditions: Consider explicitly defining the inverse conditions using where.not instead of inverse_where
. This approach provides more clarity and control over the query’s behavior.
Isolate Inversions: If you choose to use invert_where
, apply it to isolated scopes or conditions to minimize unintended inversions.
Be Cautious with Default Scopes: Avoid using invert_where
in models with default scopes, or ensure that the default scope’s inversion aligns with your intended query results.
Unit test Inverted scopes: Since invert_where
inverts all conditions in a scope, it’s important to validate that the new scope behaves as expected with tests; that becomes more crucial when you have default scopes or complex queries.