有没有一种方法可以反转ActiveRecord::Relation查询?

13

假设我们有以下内容:

irb> Post.where(:hidden => true).to_sql
=> "SELECT `posts`.* FROM `posts` WHERE posts.hidden = 1"

我们是否可以从中得到一个反转的SQL查询?

我正在寻找的,可能应该像这样:

irb> Post.where(:hidden => true).invert.to_sql
=> "SELECT `posts`.* FROM `posts` WHERE NOT (posts.hidden = 1)"

8
有人在评论中不良点赞了 where(:hidden => false) 这段代码。但这段代码不会生成 OP 寻找的 SQL 类型。 - Zabba
6个回答

18

使用不同的语法,是可以的。例如:

posts = Post.scoped.table # or Arel::Table.new("posts")
posts.where(posts[:hidden].eq(true).not).to_sql
# => SELECT  FROM `posts` WHERE NOT ((`posts`.`hidden` = 1))

不是我想要的答案,但仍然是一个非常好的回答。 - Kostas
你有没有找到其他方法来做这件事?也许有更好的语法?这个答案对你有用吗? - Zabba
其实我认为这是不可能的,因为我的实际要求是反转一个 ActiveRecord::Relation 对象,该对象可能具有多个连接和包含项,这会使问题变得过于复杂(我们应该反转哪些 WHERE 子句,哪些不应该反转?)。我想我会一直保持开放状态,直到有答案出现。 - Kostas

7
在Rails 4中,此目的没有后缀:not
Post.where.not(hidden: true).to_sql
# => SELECT FROM `posts` WHERE `posts`.`hidden` != 1

在Rails 3中,您可以使用squeel gem。它提供了许多有用的功能。使用它,您可以编写以下内容:
Post.where{ hidden != true }.to_sql
# => SELECT FROM `posts` WHERE `posts`.`hidden` != 1

5
我们可以通过将反转的查询传回ActiveRecord来进一步采用Zabba的答案:

Zabba的答案

table = Post.arel_table
query = table[:hidden].eq(true).not # the inverted query, still ARel
Post.where(query) # plug it back into ActiveRecord

这将返回ActiveRecord对象,就像您通常期望的那样。

5

invert_where(Rails 7+)

从 Rails 7 开始,有一个新的 invert_where 方法。

根据文档,它:

允许你反转整个 where 条件语句,而不是手动应用条件。

class User
  scope :active, -> { where(accepted: true, locked: false) }
end

User.where(accepted: true)
# WHERE `accepted` = 1

User.where(accepted: true).invert_where
# WHERE `accepted` != 1

User.active
# WHERE `accepted` = 1 AND `locked` = 0

User.active.invert_where
# WHERE NOT (`accepted` = 1 AND `locked` = 0)

请注意,这会在反转“invert_where”调用之前反转所有条件。
class User
  scope :active, -> { where(accepted: true, locked: false) }
  scope :inactive, -> { active.invert_where } # Do not attempt it
end

# It also inverts `where(role: 'admin')` unexpectedly.
User.where(role: 'admin').inactive
# WHERE NOT (`role` = 'admin' AND `accepted` = 1 AND `locked` = 0)

来源:


1
嗨,我刚刚在谷歌上搜索了一个逆向解决方案,你昨天回答了这个问题。这是迁移到Rails 7的另一个原因 :) - Alter Lagos

0

最后,我们在Rails 7中拥有了invert_where方法。

irb> Post.where(:hidden => true).invert_where.to_sql
"SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"hidden\" != 1"

请查看此提交参考以获取更多详细信息。

0

当我寻找具有“非真”条件(例如false或nil)的记录时,我所做的是:

Post.where(["(hidden IS NULL) OR (hidden = ?)", false])

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接