簡體   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