Rails Scope返回all而不是nil [英] Rails Scope returns all instead of nil

查看:67
本文介绍了Rails Scope返回all而不是nil的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到了一个奇怪的问题,即创建一个示波器并使用 first 查找程序。似乎在范围中使用 first 作为查询的一部分会使它返回所有结果(如果未找到结果)。如果找到任何结果,它将正确返回第一个结果。

I'm running into a strange issue creating a scope and using the first finder. It seems as though using first as part of the query in a scope will make it return all results if no results are found. If any results are found, it will correctly return the first result.

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

I have setup a very simple test to demonstrate this:

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

此测试的注意事项,我设置了条件来匹配记录或不匹配。实际上,我是根据实际条件查询的,并且得到相同的奇怪行为。

Note for this test, I have set where conditions to match records or not. In reality, I am querying based on real conditions, and getting the same strange behavior.

这里是失败范围的结果。如您所见,它会进行正确的查询,没有结果,因此它将查询所有匹配的记录并返回该记录:

Here are the results from the failing scope. As you can see, it makes the correct query, which has no results, so it then queries for all matching records and returns that instead:

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>

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

If I perform this same logic outside of a scope, I get the expected results:

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中的错误,除非我没有意识到某些未知的行为期望。

Am I missing something here? This seems like a bug in Rails/ActiveRecord/Scopes to me unless there is some unknown behavior expectations I am unaware of.

推荐答案

经过一些研究后,我发现它不是故意设计的。这不是错误或怪异。

This is not a bug or weirdness, after some research i've found its designed on purpose.

首先,


  1. 范围返回 ActiveRecord :: Relation

如果记录为零,则将其编程为返回所有记录
,而这又是 ActiveRecord: :关系而不是 nil

If there are zero records its programmed to return all records which is again an ActiveRecord::Relation instead of nil

其背后的想法是使范围可链接(即)是范围类方法

The idea behind this is to make scopes chainable (i.e) one of the key difference between scope and class methods

示例:

让我们使用以下情况:用户将能够按状态,最新更新的顺序对帖子进行过滤。足够简单,让我们为它写范围:

Lets use the following scenario: users will be able to filter posts by statuses, ordering by most recent updated ones. Simple enough, lets write scopes for that:

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

我们可以称之为像这样免费:

And we can call them freely like this:

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

或使用用户提供的参数:

Or with a user provided param:

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

到目前为止,很好。现在,将它们移到类方法中,只是为了进行比较:

So far, so good. Now lets move them to class methods, just for the sake of comparing:

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或空白,会发生什么?

Besides using a few extra lines, no big improvements. But now what happens if the :status parameter is nil or blank?

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, I don’t think we wanted to allow these queries, did we? With scopes, we can easily fix that by adding a presence condition to our scope:

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

太棒了。现在,让我们尝试使用我们钟爱的类方法做同样的事情:

Awesome. Now lets try to do the same with our beloved class method:

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

和:bomb:。区别在于,范围将始终返回关系,而我们的简单类方法实现则不会。类方法应该看起来像这样:

And :bomb:. The difference is that a scope will always return a relation, whereas our simple class method implementation will not. The class method should look like this instead:

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

注意,我将返回所有nil / blank情况,在Rails 4中返回一个关系(它先前从数据库返回了Array of Items)。在Rails 3.2.x中,应该在此处使用范围。然后就可以了:

Notice that I’m returning all for the nil/blank case, which in Rails 4 returns a relation (it previously returned the Array of items from the database). In Rails 3.2.x, you should use scoped there instead. And there we go:

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

所以这里的建议是:永远不要从类中返回nil应该像作用域一样工作的方法,否则您将打破作用域所隐含的可链接性条件,该条件总是返回一个关系。

So the advice here is: never return nil from a class method that should work like a scope, otherwise you’re breaking the chainability condition implied by scopes, that always return a relation.

长篇小说简短:

无论如何,作用域旨在返回 ActiveRecord :: Relation 以使其可链接。如果您期望第一最后查找,应该使用类方法

No matter what, scopes are intended to return ActiveRecord::Relation to make it chainable. If you are expecting first, last or find results you should use class methods

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

这篇关于Rails Scope返回all而不是nil的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆