否定 ActiveRecord 查询范围

Negate ActiveRecord query scope

我的模型范围有点复杂

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

出于测试目的,我想确保 all Contacts notgroup_search 返回出于正当理由被排除在外。

但要获取该列表,我必须加载

Contact.all - Contact.group_search('query')

它运行两个查询,returns 一个 Array 而不是 Relation,并且比我想要的要慢。

并且因为我正在测试 group_search 范围,所以写 另一个 范围是它的负值会有点破坏重点。我宁愿做类似的事情:

Contact.merge.not(Contact.group_search('query'))

生成以下 SQL 查询:

SELECT * 
FROM contacts 
WHERE NOT (contact_type = 2308 AND inactive = 0 AND last_name LIKE '%' + ? + '%')

有什么办法吗?

我认为你要找的是取反范围,你可以使用where_values(或where_values_hash in Rails >= 5):

conditions = Contact.group_search('query').where_values
@contacts = Contact.where.not(conditions.reduce(:and))

为了在 Rails 4.x 中使用此解决方案,您应该在范围内提供数组形式的值:

scope :groups, -> { where(contact_type: [2308]) }

我也找到了一个整洁的general implementation for negating the scopes,你可能也会觉得有趣。

要否定范围,您可以使用:

Contact.where.not(id: Contact.group_search('query'))

这与使用pluck不一样(在其中一条评论中提出):

Contact.where.not(id: Contact.group_search('query').pluck(:id)) 

如果没有 pluck,它会生成一个查询(有两个 select):

SELECT  `contacts`.* FROM `contacts` WHERE `contacts`.`id` NOT IN (SELECT `contacts`.`id` FROM `contacts` WHERE `contacts`.`group_search` = 'query')

对于 pluck,它会产生两个独立的查询:

SELECT `contacts`.`id` FROM `contacts` WHERE `contacts`.`group_search` = 'query'
SELECT  `contacts`.* FROM `contacts` WHERE `contacts`.`id` NOT IN (1, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361)

查询多条记录时,第一个效率更高。当然 Contact.where.not(group_search: 'query') 效率更高,因为它用一个 select 生成一个查询(但在某些情况下这可能是不可能的):

SELECT `contacts`.`id` FROM `contacts` WHERE `contacts`.`group_search` != 'query'

在Rails6中,在枚举值上添加了负范围。

你可以这样使用它:

Contact.not_active
Contact.not_groups
Contact.not_group_search

相关pull request