繁体   English   中英

Ruby on Rails ActiveRecord 作用域与类方法

[英]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那么你就明确表示whateverwhatever都是查询构建器; 如果说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.

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