I have a slightly complicated scope on a model
class Contact < ActiveRecord::Base
scope :active, -> { where(inactive: false) }
scope :groups, -> { where(contact_type: 2308) }
scope :group_search, -> (query) do
active.groups.where("last_name LIKE '%' + ? + '%'", query)
end
end
For testing purposes, I want to make sure that all Contacts
not returned by group_search
are excluded for the right reasons.
But to get that list, I have to load
Contact.all - Contact.group_search('query')
which runs two queries, returns an Array
instead of a Relation
, and is slower than I'd like.
And since I'm testing the group_search
scope, writing another scope that is its negative would kind of spoil the point. I'd rather just do something like:
Contact.merge.not(Contact.group_search('query'))
to generate the following SQL query:
SELECT *
FROM contacts
WHERE NOT (contact_type = 2308 AND inactive = 0 AND last_name LIKE '%' + ? + '%')
Is there any way of doing this?
To negate an scope you can use:
This is not the same as using
pluck
(proposed in one of the comments):Without the
pluck
, it produces one query (with two selects):With the
pluck
, it produces two independent queries:When querying many records, the first one is way more efficient. Of course
Contact.where.not(group_search: 'query')
is more efficient as it produces one query with one select (but this may be not possible in some cases):I think what you are looking for is called negating the scope, you can use
where_values
(orwhere_values_hash
in Rails >= 5):For this solution to work in Rails 4.x, you should provide values in the scope as arrays:
I'v also found a neat general implementation for negating the scopes, you may also find it interesting.