我遇到了一个奇怪的问题,即创建一个范围并使用first
查找程序。似乎在作用域中使用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中的一个错误,除非有一些我不知道的未知行为预期。
答案 0 :(得分:55)
这不是一个错误或怪异,经过一些研究后我发现它的是故意设计的。
首先,
scope
会返回ActiveRecord::Relation
如果有零记录,则编程返回所有记录
这又是ActiveRecord::Relation
而不是nil
这背后的想法是使范围可链接(即)scope
和class methods
示例:
让我们使用以下场景:用户将能够按状态过滤帖子,按最新更新的顺序排序。很简单,让我们写下范围:
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
Oooops,我认为我们不想允许这些查询,是吗?使用范围,我们可以通过向我们的范围添加状态条件来轻松解决这个问题:
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 case,它在Rails 4中返回一个关系(它先前从数据库返回了一个项目数组)。在Rails 3.2.x中,您应该使用作用域。我们去了:
Post.by_status('').recent
# SELECT "posts".* FROM "posts" ORDER BY posts.updated_at DESC
所以这里的建议是:永远不要从类应该像作用域那样工作的类方法返回nil,否则你就会破坏作用域隐含的可链接性条件,它总是返回一个关系。
长话短说:
无论如何,范围旨在返回ActiveRecord::Relation
以使其可链接。如果您期望first
,last
或find
结果,请使用class methods
来源:http://blog.plataformatec.com.br/2013/02/active-record-scopes-vs-class-methods/
答案 1 :(得分:0)
您可以使用limit
代替first
,因为-
当找不到数据时,first
返回nil
或first(<number>)
返回不可链接对象的数组。
limit
返回ActiveRecord::Relation
对象。
此帖子中的更多详细信息- https://sagarjunnarkar.github.io/blogs/2019/09/15/activerecord-scope/