Ruby on Rails ActiveRecord范围与类方法

时间:2015-10-04 05:21:04

标签: ruby-on-rails ruby rails-activerecord

Rails在内部将范围转换为类方法,那么为什么我们不能自己使用类方法而不是使用范围。

4 个答案:

答案 0 :(得分:10)

来自fine guide

  

14范围
  [...]
  要定义一个简单的作用域,我们在类中使用scope方法,传递我们想要在调用此作用域时运行的查询:

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

这与定义类方法完全相同,您使用的是个人偏好:

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

特别注意:

  

这与定义类方法完全相同,您使用的是个人偏好

还有little further(Rails3指南在BTW中说同样的事情):

  

14.1传递参数
  [...]
  使用类方法是接受范围参数的首选方法。

所以你使用它是一个偏好的问题,甚至建议你对带参数的作用域使用类方法。

使用scope主要是一个符号问题。如果你说scope :whatever,那么你明确地说whatever是一个查询构建者;如果您说def self.whatever,那么您并未暗示whatever方法的意图,那么您只需定义一些可能会或可能不会像范围一样的类方法。< / p>

当然, 14.1 通过建议在范围采用参数时不使用scope来弄清楚这种符号的区别。还要记住,在Rails3中你可以说:

scope :published, where(published: true)

所以一个无争议的范围在视觉上是清洁的&#34;并且简洁但添加一个lambda来处理参数会使它看起来更混乱:

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

但是Rails4甚至想要无争议的范围内的lambdas,这种区别现在更不合理了。

我怀疑此时的差异是历史性的。范围在之前的时代可能是一些特殊的东西,但在Rails3时代成为普通的旧类方法,以减少重复并更好地与Rails3附带的新查询接口进行网格化。

因此,如果您愿意,可以跳过scope并直接进入课程方法。当您的范围需要参数时,甚至鼓励您这样做。

答案 1 :(得分:1)

Scopes只是class methods. 内部活动记录将范围转换为类方法。

“他们之间没有区别”或“这是品味的问题”。我倾向于同意这两个句子,但我想表明两者之间存在一些细微差别。 这blogs很好地解释了差异。

答案 2 :(得分:0)

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

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

答案 3 :(得分:-5)

为什么我应该使用范围,如果它只是类方法的语法糖?“。所以这里有一些有趣的例子供您考虑。

范围始终是可链接的=&gt;    ••••••••••••••••••••••••••••••••••••••••••••    让我们使用以下场景:用户将能够按状态过滤帖子,按最新更新的顺序排序。很简单,让我们写下范围:

  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,否则你就会破坏作用域隐含的可链接性条件,它总是返回一个关系。

范围是可扩展的=&gt;   •••••••••••••••••••••••••••••            让我们作为下一个例子进行分页,我将使用kaminari gem作为基础。在对集合进行分页时,您需要做的最重要的事情是告诉您要获取哪个页面:

    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

它比使用示波器更冗长,但它会产生相同的结果。这里的建议是:选择哪种方法更适合您,但确保在重新发明轮子之前知道框架提供了什么。