【问题标题】:How to return an empty ActiveRecord relation?如何返回一个空的 ActiveRecord 关系?
【发布时间】: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

标签: ruby-on-rails activerecord relation


【解决方案1】:

Rails 4 现在有一个“正确”的机制:

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

【讨论】:

  • 到目前为止,这还没有被移植到 3.2 或更早版本。仅边缘 (4.0)
  • 刚刚尝试使用 Rails 4.0.5 并且它正在工作。此功能已在 Rails 4.0 版本中发布。
  • @AugustinRiedinger Model.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 的其他答案:stackoverflow.com/a/10001043/307308
  • scope :none, -&gt; { 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

    如果我错了,请纠正我。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多