如何返回一个空的ActiveRecord关系?


246

如果我有一个带lambda的作用域并且它接受一个参数,则取决于参数的值,我可能知道不会有任何匹配项,但是我仍然想返回一个关系,而不是一个空数组:

scope :for_users, lambda { |users| users.any? ? where("user_id IN (?)", users.map(&:id).join(',')) : [] }

我真正想要的是“无”方法,与“全部”相反,该方法返回仍然可以链接的关系,但导致查询短路。


如果仅让查询运行,它将返回一个关系:User.where('id in(?)',[])。class => ActiveRecord :: Relation。您是否试图完全避免查询?
Brian Deterling

1
正确。如果我知道不可能有任何匹配项,理想情况下,可以完全避免查询。我只是将其添加到ActiveRecord :: Base:“ def self.none; where(:id => 0); end”似乎可以满足我的需要。
dzajic 2011年

1
>您是否试图完全避免查询?完全有道理,我们需要为此打DB
dol脚

Answers:


478

Rails 4现在提供了一种“正确”的机制:

>> Model.none 
=> #<ActiveRecord::Relation []>

14
到目前为止,还没有回移植到3.2或更早的版本。唯一优势(4.0)
克里斯·布鲁姆


2
刚在Rails 4.0.5上尝试过,就可以了。此功能使其成为Rails 4.0版本。
2014年

3
@AugustinRiedinger可以满足Model.scoped您在轨道3中的需求。–
Tim Diggins

9
从Rails 4.0.5开始, Model.none它不起作用。您需要使用您的真实模型名称之一,例如,User.none或者您拥有什么。
Grant Birchmeier

77

一个更可移植的解决方案,不需要“ id”列,并且不假设不会有id为0的行:

scope :none, where("1 = 0")

我仍在寻找一种更“正确”的方法。


3
是的,我真的很惊讶这些答案是我们最好的。我认为ActiveRecord / Arel一定还很不成熟。如果我不得不经历遍历才能在Ruby中创建一个空数组,那我会很生气。基本上,这里也是一样。
Purplejacket 2011年

尽管有点骇人听闻,但这 Rails 3.2的正确方法。对于Rails 4,请参见@ steveh7的其他答案:stackoverflow.com/a/10001043/307308
scarver2 2014年

scope :none, -> { where("false") }
nroose

43

进入Rails 4

在Rails 4中,ActiveRecord::NullRelation像这样的调用将返回可链接的对象Post.none

它或链式方法都不会生成对数据库的查询。

根据评论:

返回的ActiveRecord :: NullRelation从Relation继承并实现Null Object模式。它是具有已定义的空行为的对象,并且始终返回空的记录数组而不查询数据库。

参见源代码


42

您可以添加一个名为“ none”的范围:

scope :none, where(:id => nil).where("id IS NOT ?", nil)

这将为您提供一个空的ActiveRecord :: Relation

您也可以将其添加到初始化程序的ActiveRecord :: Base中(如果需要):

class ActiveRecord::Base
 def self.none
   where(arel_table[:id].eq(nil).and(arel_table[:id].not_eq(nil)))
 end
end

有很多方法可以得到这样的东西,但肯定不是保存在代码库中的最佳方法。我在重构时使用了作用域:none,发现需要在短时间内保证一个空的ActiveRecord :: Relation。


14
where('1=2')可能会更简洁一点
马辛Raczkowski

10
如果您不向下滚动到新的“正确”答案:这Model.none是您的操作方式。
Joe Essey

26
scope :none, limit(0)

这是一个危险的解决方案,因为您的范围可能会被束缚。

User.none.first

将返回第一个用户。使用起来更安全

scope :none, where('1 = 0')

2
这是正确的一个'scope:none,where('1 = 0')'。如果您有分页,则另一个将失败
Federico

14

我认为与其他选项相比,我更喜欢这种外观:

scope :none, limit(0)

导致这样的事情:

scope :users, lambda { |ids| ids.present? ? where("user_id IN (?)", ids) : limit(0) }

我喜欢这个。我不确定为什么where(false)不做这项工作-它使范围保持不变。
aceofspades

4
请注意,limit(0)如果您在链中调用.first.last稍后调用,它将被覆盖,因为Rails将追加LIMIT 1到该查询。
zykadelic 2012年

2
@aceofspades where(false)不起作用(Rails 3.0),但是where('false')有效。并不是您现在可能在乎2013年:)
Ritchie

感谢@Ritchie,从那时起,我认为我们也有none下面提到的关系。
aceofspades 2013年

3

使用范围:

范围:for_users,lambda {| users | 用户吗??where(“ user_id IN(?)”,users.map(&:id).join(',')):作用域}

但是,您还可以使用以下方法简化代码:

范围:for_users,lambda {| users | where(:user_id => users.map(&:id))如果是users.any?}

如果您想要一个空结果,请使用此命令(删除if条件):

范围:for_users,lambda {| users | where(:user_id => users.map(&:id))}

返回“作用域”或nil并没有达到我想要的结果,即将结果限制为零。返回“作用域”或nil对范围没有影响(在某些情况下很有用,但在我的情况下不起作用)。我想出了自己的答案(请参阅上面的评论)。
dzajic 2011年

我还为您添加了一个简单的解决方案,以返回空结果:)
Pan Thomakos 2011年


1

也有变体,但是所有这些都向db发出请求

where('false')
where('null')

2
BTW请注意,这些必须是字符串。where(false)where(nil)只是被忽略。
mahemoff
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.