[英]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.