我在模型上有一个稍微复杂的范围
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
出于测试目的,我想确保Contacts
返回的所有group_search
被排除在正确的原因之外。
但要获得该列表,我必须加载
Contact.all - Contact.group_search('query')
运行两个查询,返回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 '%' + ? + '%')
有没有办法做到这一点?
答案 0 :(得分:8)
我认为您所寻找的内容被称为否定范围,您可以使用where_values
(或Rails中的where_values_hash
&gt; = 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,你也可能觉得它很有趣。
答案 1 :(得分:8)
要否定范围,可以使用:
Contact.where.not(id: Contact.group_search('query'))
这与使用pluck
(在其中一项注释中提出)不同:
Contact.where.not(id: Contact.group_search('query').pluck(:id))
没有pluck
,它会产生一个查询(有两个选择):
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 `contacts`.`id` FROM `contacts` WHERE `contacts`.`group_search` != 'query'
答案 2 :(得分:2)
在Rails 6中,将负范围添加到枚举值上。
您可以像这样使用它:
Contact.not_active
Contact.not_groups
Contact.not_group_search
相关的pull request