Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails Not query on entire Where clause

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!

like image 657
danielricecodes Avatar asked Dec 12 '17 16:12

danielricecodes


1 Answers

If you are using Rails 7, you can use invert_where. This will form the query exactly as you need

! [ (a) && (b) && (c) ]

like image 108
Vishal Jain Avatar answered Oct 14 '22 20:10

Vishal Jain