[英]Ruby on Rails ActiveRecord scopes vs class methods
Rails 在内部将作用域转换为类方法,那么为什么我们不能使用类方法本身而不是使用作用域。
来自精美指南:
14个范围
[...]
要定义一个简单的范围,我们在类中使用scope
方法,传递我们希望在调用此范围时运行的查询:class Article < ActiveRecord::Base scope :published, -> { where(published: true) } end
这与定义类方法完全相同,您使用哪种方法取决于个人喜好:
class Article < ActiveRecord::Base def self.published where(published: true) end end
特别注意:
这和定义一个类方法完全一样,你使用哪个是个人喜好的问题
再进一步(Rails3 指南在这里说同样的事情顺便说一句):
14.1 传入参数
[...]
使用类方法是接受范围参数的首选方式。
所以你使用哪个是一个偏好问题,甚至建议你对带参数的范围使用类方法。
使用scope
主要是一个符号问题。 如果你说scope :whatever
那么你就明确表示whatever
是whatever
都是查询构建器; 如果说def self.whatever
那么你就没有暗示对意图的任何事情whatever
方法,你只是定义了一些类的方法,可能会或可能不会表现得像一个范围。
当然, 14.1建议在作用域接受参数时不要使用scope
,从而使这种符号区别变得一团糟。 还要记住,在 Rails3 中,你可以说:
scope :published, where(published: true)
所以一个无参数的作用域在视觉上是“干净的”和简洁的,但是添加一个 lambda 来处理参数会使它看起来更混乱:
scope :pancakes, ->(x) { where(things: x) }
但是 Rails4 即使对于无参数的作用域也需要 lambda,现在这种区分更没有意义了。
我怀疑此时的差异是历史性的。 Scopes 在以前可能是一些特别的东西,但在 Rails3 时代变成了普通的旧类方法,以减少重复并更好地与 Rails3 附带的新查询接口相结合。
因此,如果您愿意,您可以跳过scope
并直接转到类方法。 当您的范围需要参数时,您甚至被鼓励这样做。
Scopes
只是class methods.
Active Record 在内部将作用域转换为类方法。
“它们之间没有区别”或“这是品味问题”。 我倾向于同意这两个句子,但我想说明两者之间存在的一些细微差别。 这个博客很好地解释了差异。
这篇文章最适合我,它很容易解释
如果它只是类方法的语法糖,我为什么要使用范围?“ 所以这里有一些有趣的例子供你思考。
示波器始终可链接=>•••••••••••••••••••••••••••••••••••••••••••让我们使用以下场景:用户将能够按状态过滤帖子,按最新更新的顺序排序。 很简单,让我们写下范围:
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或空白会发生什么?
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? }
There we go:
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
Running this:
Post.by_status('').recent
NoMethodError: undefined method `recent' for nil:NilClass
And . 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 case,它在Rails 4中返回一个关系(它之前返回了数据库中的项目数组)。 在Rails 3.2.x中,您应该使用作用域。 然后我们去:
Post.by_status('').recent
# SELECT "posts".* FROM "posts" ORDER BY posts.updated_at DESC
所以这里的建议是:永远不要从类应该像作用域一样的类方法返回nil,否则你就会破坏作用域隐含的可链接性条件,它总是返回一个关系。
范围是可扩展的=>•••••••••••••••••••••••••••让我们作为下一个示例获取分页,我将使用kaminari宝石作为基础。 在对集合进行分页时,您需要做的最重要的事情是告诉您要获取哪个页面:
Post.page(2)
After doing that you might want to say how many records per page you want:
Post.page(2).per(15)
And you may to know the total number of pages, or whether you are in the first or last page:
posts = Post.page(2)
posts.total_pages # => 2
posts.first_page? # => false
posts.last_page? # => true
当我们按此顺序调用事物时,这一切都有意义,但是在没有分页的集合中调用这些方法没有任何意义,是吗? 编写范围时,可以添加仅在对象中可用的特定扩展(如果调用该范围)。 对于kaminari,它只将页面范围添加到Active Record模型,并依赖范围扩展功能在调用页面时添加所有其他功能。 从概念上讲,代码看起来像这样:
scope :page, -> num { # some limit + offset logic here for pagination } do
def per(num)
# more logic here
end
def total_pages
# some more here
end
def first_page?
# and a bit more
end
def last_page?
# and so on
end
end
范围扩展是我们工具链中强大而灵活的技术。 但是,当然,我们总是可以疯狂地通过类方法获得所有这些:
def self.page(num)
scope = # some limit + offset logic here for pagination
scope.extend PaginationExtensions
scope
end
module PaginationExtensions
def per(num)
# more logic here
end
def total_pages
# some more here
end
def first_page?
# and a bit more
end
def last_page?
# and so on
end
end
它比使用示波器更冗长,但它产生相同的结果。 这里的建议是:选择哪种方法更适合您,但确保在重新发明轮子之前知道框架提供了什么。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.