Is there a straight-forward way to negate an entire where
expression using ActiveRecord/ARel? It seems that where.not()
, when given a hash of arguments, negates each expression individually, as opposed to negating the entire thing with a single SQL NOT
.
Rails Example
Thing.where.not(attribute1: [1,2], attribute2: [3,4], attribute3: [5,6])
Would produce SQL:
select * from things where attribute1 NOT IN (1,2) AND attribute2 NOT IN (3,4) AND attribute3 NOT IN (5,6)
This isn't what I'm trying to do though. I want to negate the entire where clause with a single NOT
.
select * from things where NOT(attribute1 IN (1,2) AND attribute2 IN (3,4) AND attribute3 IN (5,6))
In boolean notation, Rails seems to favor negating each component of the WHERE clause like this:
!(a) && !(b) && !(c)
But I want to negate the entire expression:
! [ (a) && (b) && (c) ]
Using DeMorgan's Law, I could write my query as !a || !b || !c
, but that will result in some rather long and ugly code (less long with Rails 5 and or
, but still ugly). I was hoping there is some syntactic sugar I'm missing using ActiveRecord or ARel?
Background Story
I'm writing a Ransack Equality Scope (e.g. _eq) to search for a condition and its condition's opposite.
scope :can_find_things_eq, ->(boolean = true) {
case boolean
when true, 'true'
where(conditions_for_things)
when false, 'false'
where.not(conditions_for_things)
end
}
def self.ransackable_scopes(_auth_object = nil)
%i[can_find_things_eq]
end
If I use my Rails 5 suggestion above and my example I started out with, I can get my negation query to work...but the code is long and ugly.
where.not(attribute1: [1,2]).or(where.not(attribute2:
[3,4)).or(where.not(attribute3: [5,6))
Chaining Ors and WhereNots works, but its not very readable. Is there a better way to negate this where
other than having to negate it manually/logically using DeMorgan's Law?
Thanks!
![(a) && (b) && (c)]
would become equivalent to the test(a)*(b)*(c) == 0
. – Hypotaxis