##Scope(スコープ)とは
scopeとは、クラスメソッドを使う際、可読性を保つためにあるものです。
例えば、あるUserのidを降順にし、かつ5つだけ表示させたい場合、以下のようなメソッドを使うことになります。
User.order(id: desc).limit(5)
しかし、このような記述をもしいろんなところで使うとした場合、長ったらしいメソッドチェーンを書くのは面倒だし、コントローラ側で使うときに可読性が落ちます。
そういったときに使うのがscope。
上と同じ式を一つのメソッドとして定義できます(ここでいうrecent)
class User < ApplicationRecord
scope :recent, -> { order(id: :desc).limit(5) }
end
class UsersController < ApplicationController
def index
@users = User.recent
end
これでコントローラ側でもUser.recentを使えるようになり、より簡潔になりましたね。
また、スコープに引数を渡すことも可能です。
class User < ApplicationRecord
scope :recent, -> (count) { order(id: :desc).limit(count) }
end
class UsersController < ApplicationController
def index
@users = User.recent(10)
end
これで、コントローラ側でUser.recent(10)
を呼べば最新の10件が取得できるようになります。
##スコープは簡潔に、使いやすく
Modelを呼ぶ際、どこからでも使えて便利なスコープですが、そのどこからでも使えるという部分において考慮すべき点があります。
それは、scope側をシンプルに記述してあげるという点です。
というのも、コントローラによってscopeに渡す値も違うからです。
例えば、国別でUserを検索する以下のスコープがあるとします。
scope :serch_user, ->(country) do
return if country.nil?
country == "japan" ? where(country: true) : where(country: false)
end
scope :serch_user, ->(country) do
return if country.nil?
country ? where(country: true) : where(country: false)
end
前者はcountryが"japan"という文字列で条件分岐しているのに対し、後者はcountryがtrueかfalseかによって分岐させています。
この場合、どう考えても単にbooleanで分岐させている後者のスコープの方が使い安いですよね。
前者の場合、わざわざ"japan"という文字でスコープに渡さなければなりません。
##まとめ
僕はscopeを作る際、以下のように考えてました。
viewから値を取ってくる → コントローラに渡す → そのままscopeに渡す
しかし、この発想ではそのコントローラでしか扱えないようなscopeになってしまう恐れがあります。 なので、
viewから値を取ってくる → コントローラに渡す → コントローラ側で、scopeで使える形に変換 → scopeに渡す
という前提でscopeを作る必要があります。
なので、scopeはどのコントローラからでも使いやすく、単純なものにする必要があるのです。
(と、多分いろいろ間違えてるのでご指摘ありましたらよろしくお願いしますm( )m)