我在创建范围和使用first finder时遇到了一个奇怪的问题.似乎在一个作用域中使用first作为查询的一部分,如果找不到结果,它将返回所有结果.如果找到任何结果,它将正确返回第一个结果.

我设置了一个非常简单的测试来证明这一点:

class Activity::MediaGroup < ActiveRecord::Base
  scope :test_fail, -> { where('1 = 0').first }
  scope :test_pass, -> { where('1 = 1').first }
end

注意:对于这个测试,我已经设置了匹配记录与否的条件.实际上,我是在根据真实情况进行查询,并得到相同的奇怪行为.

以下是失败范围的结果.如您所见,它进行了正确的查询,但没有结果,因此它会查询所有匹配的记录,并返回以下结果:

irb(main):001:0> Activity::MediaGroup.test_fail
  Activity::MediaGroup Load (0.0ms)  SELECT "activity_media_groups".* FROM "activity_media_groups" WHERE (1 = 0) ORDER BY "activity_media_groups"."id" ASC LIMIT 1
  Activity::MediaGroup Load (0.0ms)  SELECT "activity_media_groups".* FROM "activity_media_groups"
=> #<ActiveRecord::Relation [#<Activity::MediaGroup id: 1, created_at: "2014-01-06 01:00:06", updated_at: "2014-01-06 01:00:06", user_id: 1>, #<Activity::MediaGroup id: 2, created_at: "2014-01-06 01:11:06", updated_at: "2014-01-06 01:11:06", user_id: 1>, #<Activity::MediaGroup id: 3, created_at: "2014-01-06 01:26:41", updated_at: "2014-01-06 01:26:41", user_id: 1>, #<Activity::MediaGroup id: 4, created_at: "2014-01-06 01:28:58", updated_at: "2014-01-06 01:28:58", user_id: 1>]>

另一个范围按预期运行:

irb(main):002:0> Activity::MediaGroup.test_pass
  Activity::MediaGroup Load (1.0ms)  SELECT "activity_media_groups".* FROM "activity_media_groups" WHERE (1 = 1) ORDER BY "activity_media_groups"."id" ASC LIMIT 1
=> #<Activity::MediaGroup id: 1, created_at: "2014-01-06 01:00:06", updated_at: "2014-01-06 01:00:06", user_id: 1>

如果我在范围之外执行相同的逻辑,我会得到预期的结果:

irb(main):003:0> Activity::MediaGroup.where('1=0').first
  Activity::MediaGroup Load (0.0ms)  SELECT "activity_media_groups".* FROM "activity_media_groups" WHERE (1=0) ORDER BY "activity_media_groups"."id" ASC LIMIT 1
=> nil

我错过什么了吗?在我看来,这似乎是Rails/ActiveRecord/Scopes中的一个bug,除非我不知道有一些未知的行为预期.

推荐答案

这不是一个错误或奇怪,经过一些研究,我发现它有designed on purpose个.

首先,

  1. scope返回ActiveRecord::Relation

  2. 如果没有记录,它将返回所有记录

这背后的 idea 是让scopes chainable(即)成为scopeclass methods之间的key difference中的一个

Example:

让我们使用以下场景:用户将能够按状态筛选帖子,按最新更新的帖子排序.很简单,让我们为其编写范围:

class Post < ActiveRecord::Base
  scope :by_status, -> status { where(status: status) }
  scope :recent, -> { order("posts.updated_at DESC") }
end

我们可以这样自由地称呼他们:

Post.by_status('published').recent
# SELECT "posts".* FROM "posts" WHERE "posts"."status" = 'published' 
#   ORDER BY posts.updated_at DESC

或者使用用户提供的参数:

Post.by_status(params[:status]).recent
# SELECT "posts".* FROM "posts" WHERE "posts"."status" = 'published' 
#   ORDER BY posts.updated_at DESC

到目前为止,一切顺利.现在让我们把它们转移到类方法,只是为了比较:

class Post < ActiveRecord::Base
  def self.by_status(status)
    where(status: status)
  end

  def self.recent
    order("posts.updated_at DESC")
  end
end

除了使用一些额外的线路,没有大的改进.但是现在如果:status参数为nil或空白会发生什么?

Post.by_status(nil).recent
# SELECT "posts".* FROM "posts" WHERE "posts"."status" IS NULL 
#   ORDER BY posts.updated_at DESC

Post.by_status('').recent
# SELECT "posts".* FROM "posts" WHERE "posts"."status" = '' 
#   ORDER BY posts.updated_at DESC

哦,我觉得我们不想允许这些查询,是吗?有了作用域,我们可以通过在作用域中添加存在条件来轻松解决这一问题:

scope :by_status, -> status { where(status: status) if status.present? }

好了:

Post.by_status(nil).recent
# SELECT "posts".* FROM "posts" ORDER BY posts.updated_at DESC

Post.by_status('').recent
# SELECT "posts".* FROM "posts" ORDER BY posts.updated_at DESC

令人惊叹的现在让我们试着用我们喜爱的类方法做同样的事情:

class Post < ActiveRecord::Base
  def self.by_status(status)
    where(status: status) if status.present?
  end
end

运行以下命令:

Post.by_status('').recent
NoMethodError: undefined method `recent' for nil:NilClass

和:炸弹:.不同之处在于,作用域总是会返回一个关系,而我们的简单类方法实现则不会.类方法应该是这样的:

def self.by_status(status)
  if status.present?
    where(status: status)
  else
    all
  end
end

注意,我将返回nil/blank大小写的all,它在Rails 4中返回一个关系(它之前从数据库返回了项目数组).在Rails 3.2中.x、 你应该在那里使用scoped.然后我们开始:

Post.by_status('').recent
# SELECT "posts".* FROM "posts" ORDER BY posts.updated_at DESC

这里的建议是:不要从一个应该像作用域一样工作的类方法中返回nil,否则你就 destruct 了作用域隐含的可链接性条件,它总是返回一个关系.

Long Story Short:

不管怎样,scopes都打算返回ActiveRecord::Relation以使其可链接.如果您希望得到firstlastfind个结果,则应使用class methods

资料来源:http://blog.plataformatec.com.br/2013/02/active-record-scopes-vs-class-methods/

Ruby-on-rails相关问答推荐

Rails 7.1解决冲突的Zeitwerk Inflection规则

未捕获语法错误:try 编辑TRIX时,请求的模块未在Rails 7.1中提供名为默认的导出(在youtube.js:1:8)

用至少一个缺省值初始化Ruby数组的最简洁方法是什么?

在bash中匹配带有空格字符的字符串

如何在 IRB/Rails 控制台中 suppress 返回值的输出?

Rails:用于创建固定长度 char(12) 列的迁移

Rails 是否带有未授权异常?

有没有办法在 Rails 3.1 中检测用户代理

骨干model.destroy()即使工作正常也会调用错误回调函数?

如何在 Ruby on Rails 中创建一个锚点并重定向到这个特定的锚点

使用 RSpec 和 Capybara (Rails) 测试重定向

使用回形针进行简单裁剪

Rails Mailer Net::OpenTimeout: execution expired 仅在生产服务器上出现异常

适合mysql比较的Ruby datetime

未定义的方法 attr_accessible

奇怪的406 不可接受错误

Rails 5:重命名表迁移

如何在rails中正确使用单选按钮?

资源和资源方法之间的区别

如何测试也定义为辅助方法的 ApplicationController 方法?