如何返回一个空的 ActiveRecord 关系?
Posted
技术标签:
【中文标题】如何返回一个空的 ActiveRecord 关系?【英文标题】:How to return an empty ActiveRecord relation? 【发布时间】:2011-06-20 03:50:56 【问题描述】:如果我有一个带有 lambda 的范围并且它需要一个参数,根据参数的值,我可能知道不会有任何匹配,但我仍然想返回一个关系,而不是一个空数组:
scope :for_users, lambda |users| users.any? ? where("user_id IN (?)", users.map(&:id).join(',')) : []
我真正想要的是一个“none”方法,与“all”相反,它返回一个仍然可以链接的关系,但会导致查询被短路。
【问题讨论】:
如果你只是让查询,运行它会返回一个关系:User.where('id in (?)', []).class=> ActiveRecord::Relation.您是否试图完全避免查询? 正确。如果我知道不可能有任何匹配项,理想情况下,可以完全避免查询。我只是将它添加到 ActiveRecord::Base: "def self.none; where(:id => 0); end" 似乎可以满足我的需要。 > 您是否要完全避免查询?完全有道理,有点蹩脚,我们需要为此打 DB 【参考方案1】:Rails 4 现在有一个“正确”的机制:
>> Model.none
=> #<ActiveRecord::Relation []>
【讨论】:
到目前为止,这还没有被移植到 3.2 或更早版本。仅边缘 (4.0) 文档:api.rubyonrails.org/classes/ActiveRecord/… 刚刚尝试使用 Rails 4.0.5 并且它正在工作。此功能已在 Rails 4.0 版本中发布。 @AugustinRiedingerModel.scoped
可以满足您在 Rails 3 中的需求。
从 Rails 4.0.5 开始,Model.none
不起作用。您需要使用您的 real 模型名称之一,例如User.none
或你有什么。【参考方案2】:
一种更便携的解决方案,它不需要“id”列并且不假设不会有 id 为 0 的行:
scope :none, where("1 = 0")
我还在寻找更“正确”的方法。
【讨论】:
是的,我真的很惊讶这些答案是我们所拥有的最好的。我认为 ActiveRecord/Arel 一定还很不成熟。如果我必须通过遍历来在 Ruby 中创建一个空数组,我会非常恼火。基本上也是一样的。 虽然有点老套,但这是 Rails 3.2的正确方法。对于 Rails 4,请参阅@steveh7 的其他答案:***.com/a/10001043/307308scope :none, -> where("false")
【参考方案3】:
加入 Rails 4
在 Rails 4 中,可链接的 ActiveRecord::NullRelation
将从 Post.none
之类的调用中返回。
它和链式方法都不会生成对数据库的查询。
根据cmets:
返回的 ActiveRecord::NullRelation 继承自 关系并实现空对象模式。它是一个对象 定义空行为并始终返回一个空的记录数组 无需查询数据库。
请参阅source code。
【讨论】:
【参考方案4】:您可以添加一个名为“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。
【讨论】:
where('1=2')
可能更简洁一些
如果您不向下滚动到新的“正确”答案:Model.none
就是您这样做的方式。【参考方案5】:
scope :none, limit(0)
这是一个危险的解决方案,因为您的范围可能会被束缚。
User.none.first
将返回第一个用户。使用起来更安全
scope :none, where('1 = 0')
【讨论】:
这个是正确的 'scope :none, where('1 = 0')'。如果您有分页,另一个将失败【参考方案6】:与其他选项相比,我认为我更喜欢这种方式:
scope :none, limit(0)
导致这样的事情:
scope :users, lambda |ids| ids.present? ? where("user_id IN (?)", ids) : limit(0)
【讨论】:
我更喜欢这个。我不知道为什么where(false)
不能完成这项工作——它使范围保持不变。
注意limit(0)
将被覆盖,如果您在链的后面调用.first
或.last
,因为Rails 会将LIMIT 1
附加到该查询。
@aceofspades where(false) 不起作用(Rails 3.0)但 where('false') 起作用。并不是说你现在可能关心它是 2013 年 :)
谢谢@Ritchie,从那以后我想我们也有none
关系,如下所述。【参考方案7】:
使用作用域:
范围 :for_users, lambda |users| users.any? ? where("user_id IN (?)", users.map(&:id).join(',')) : scoped但是,您也可以通过以下方式简化代码:
范围 :for_users, lambda |users| where(:user_id => users.map(&:id)) 如果 users.any?如果你想要一个空的结果,使用这个(去掉 if 条件):
范围 :for_users, lambda |users|其中(:user_id => users.map(&:id))【讨论】:
返回“scoped”或nil并没有达到我想要的效果,也就是将结果限制为零。返回“作用域”或 nil 对作用域没有影响(这在某些情况下很有用,但在我的情况下没有用)。我想出了自己的答案(见上面的 cmets)。 我添加了一个简单的解决方案让您也返回一个空结果:)【参考方案8】:也有变种,但都是向db发出请求
where('false')
where('null')
【讨论】:
BTW 请注意,这些必须是字符串。where(false)
或 where(nil)
会被忽略。【参考方案9】:
这是可能的,所以是这样的:
scope :for_users, lambda |users| users.any? ? where("user_id IN (?)", users.map(&:id).join(',')) : User.none
http://apidock.com/rails/v4.0.2/ActiveRecord/QueryMethods/none
如果我错了,请纠正我。
【讨论】:
以上是关于如何返回一个空的 ActiveRecord 关系?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用has_many通过关系缩短三重连接ActiveRecord查询?