简体   繁体   English

Ruby on Rails ActiveRecord 作用域与类方法

[英]Ruby on Rails ActiveRecord scopes vs class methods

Rails 在内部将作用域转换为类方法,那么为什么我们不能使用类方法本身而不是使用作用域。

From the fine guide :来自精美指南

14 Scopes 14个范围
[...] [...]
To define a simple scope, we use the scope method inside the class, passing the query that we'd like to run when this scope is called:要定义一个简单的范围,我们在类中使用scope方法,传递我们希望在调用此范围时运行的查询:

 class Article < ActiveRecord::Base scope :published, -> { where(published: true) } end

This is exactly the same as defining a class method, and which you use is a matter of personal preference:这与定义类方法完全相同,您使用哪种方法取决于个人喜好:

 class Article < ActiveRecord::Base def self.published where(published: true) end end

Note in particular:特别注意:

This is exactly the same as defining a class method, and which you use is a matter of personal preference这和定义一个类方法完全一样,你使用哪个是个人喜好的问题

And a little further (the Rails3 guide says the same thing here BTW): 再进一步(Rails3 指南在这里说同样的事情顺便说一句):

14.1 Passing in arguments 14.1 传入参数
[...] [...]
Using a class method is the preferred way to accept arguments for scopes.使用类方法是接受范围参数的首选方式。

So which you use is a matter of preference and it is even recommended that you use class methods for scopes that take arguments.所以你使用哪个是一个偏好问题,甚至建议你对带参数的范围使用类方法。

Using scope is mostly a notational issue.使用scope主要是一个符号问题。 If you say scope :whatever then you're explicitly saying that whatever is meant to be a query builder;如果你说scope :whatever那么你就明确表示whateverwhatever都是查询构建器; if you say def self.whatever then you're not implying anything about the intent of the whatever method, you're just defining some class method that may or may not behave like a scope.如果说def self.whatever那么你就没有暗示对意图的任何事情whatever方法,你只是定义了一些类的方法,可能会或可能不会表现得像一个范围。

Of course, 14.1 makes a mess of this notational distinction by recommending that you not use scope when your scope takes arguments.当然, 14.1建议在作用域接受参数时不要使用scope ,从而使这种符号区别变得一团糟。 Also keep in mind that in Rails3 you could say:还要记住,在 Rails3 中,你可以说:

scope :published, where(published: true)

so an argumentless scope was visually "clean" and terse but adding a lambda to handle arguments would make it look messier:所以一个无参数的作用域在视觉上是“干净的”和简洁的,但是添加一个 lambda 来处理参数会使它看起来更混乱:

scope :pancakes, ->(x) { where(things: x) }

But Rails4 wants lambdas even for argumentless scopes the distinction makes even less sense now.但是 Rails4 即使对于无参数的作用域也需要 lambda,现在这种区分更没有意义了。

I suspect that the difference is historical at this point.我怀疑此时的差异是历史性的。 Scopes were probably something special back in the before times but became plain old class methods in the Rails3 era to cut down on duplication and to better mesh with the new query interface that came with Rails3. Scopes 在以前可能是一些特别的东西,但在 Rails3 时代变成了普通的旧类方法,以减少重复并更好地与 Rails3 附带的新查询接口相结合。


So you can skip scope and go straight to class methods if you wish.因此,如果您愿意,您可以跳过scope并直接转到类方法。 You're even encouraged to do so when your scope takes arguments.当您的范围需要参数时,您甚至被鼓励这样做。

Scopes are just class methods. Scopes只是class methods. Internally Active Record converts a scope into a class method. Active Record 在内部将作用域转换为类方法。

"There is no difference between them" or “it is a matter of taste”. “它们之间没有区别”或“这是品味问题”。 I tend to agree with both sentences, but I'd like to show some slight differences that exist between both.我倾向于同意这两个句子,但我想说明两者之间存在的一些细微差别。 This blogs explains the difference very well.这个博客很好地解释了差异。

这篇文章最适合我,它很容易解释

Why should I use a scope if it is just syntax sugar for a class method?”. 如果它只是类方法的语法糖,我为什么要使用范围?“ So here are some interesting examples for you to think about. 所以这里有一些有趣的例子供你思考。

Scopes are always chainable=> •••••••••••••••••••••••••••••••••••••••••••• 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

Besides using a few extra lines, no big improvements. 除了使用一些额外的线路,没有大的改进。 But now what happens if the :status parameter is nil or blank? 但是现在如果: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

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). 请注意,我将返回所有nil / blank case,它在Rails 4中返回一个关系(它之前返回了数据库中的项目数组)。 In Rails 3.2.x, you should use scoped there instead. 在Rails 3.2.x中,您应该使用作用域。 And there we go: 然后我们去:

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

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. 所以这里的建议是:永远不要从类应该像作用域一样的类方法返回nil,否则你就会破坏作用域隐含的可链接性条件,它总是返回一个关系。

Scopes are extensible => ••••••••••••••••••••••••••••• Lets get pagination as our next example and I'm going to use the kaminari gem as basis. 范围是可扩展的=>•••••••••••••••••••••••••••让我们作为下一个示例获取分页,我将使用kaminari宝石作为基础。 The most important thing you need to do when paginating a collection is to tell which page you want to fetch: 在对集合进行分页时,您需要做的最重要的事情是告诉您要获取哪个页面:

    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

This all makes sense when we call things in this order, but it doesn't make any sense to call these methods in a collection that is not paginated, does it? 当我们按此顺序调用事物时,这一切都有意义,但是在没有分页的集合中调用这些方法没有任何意义,是吗? When you write scopes, you can add specific extensions that will only be available in your object if that scope is called. 编写范围时,可以添加仅在对象中可用的特定扩展(如果调用该范围)。 In case of kaminari, it only adds the page scope to your Active Record models, and relies on the scope extensions feature to add all other functionality when page is called. 对于kaminari,它只将页面范围添加到Active Record模型,并依赖范围扩展功能在调用页面时添加所有其他功能。 Conceptually, the code would look like this: 从概念上讲,代码看起来像这样:

     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

Scope extensions is a powerful and flexible technique to have in our toolchain. 范围扩展是我们工具链中强大而灵活的技术。 But of course, we can always go wild and get all that with class methods too: 但是,当然,我们总是可以疯狂地通过类方法获得所有这些:

  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

It is a bit more verbose than using a scope, but it yields the same results. 它比使用示波器更冗长,但它产生相同的结果。 And the advice here is: pick what works better for you but make sure you know what the framework provides before reinventing the wheel. 这里的建议是:选择哪种方法更适合您,但确保在重新发明轮子之前知道框架提供了什么。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM