#便利なscope
はじめに
前回の記事でenum素敵ってなりましたね。じゃあscopeも素敵ってなっときましょう。
##scopeとは##
scope?グローバル変数とかのこと?とか最初は勘違いしてました。違います。
こいつは、SQL文をメソッドにしてくれる良いやつなんです。
では例を用いて説明しましょう。
##scopeを書かない場合##
以下の様にな探索をかける場合、この様なuserの探索が他の場面でも必要になった時に管理に困ります。やっぱり年齢は20歳以上で探索しようだの、やっぱり女性に絞ろうだのいった時に修正箇所が広がり大変です。
なのでscopeを用いてメソッドとし、カプセル化しましょう。オブジェクティブに生きていくと幸せなのです。
class UsersController < ApplicationController
def index
@user_index = User.where(status: :normal)
.where(gender: :male)
.where('age >= 18')
end
end
##scopeでmodelにまとめる。
さっきのsql文をmodelにまとめると下記の様になります。
class User < ApplicationRecord
enum status: {
normal: 0, #通常会員
premium: 1, #プレミアム会員
}
enum gender: {
male: 0, #男性
female: 1, #女性
}
scope :status_normal, -> { where(status: :normal) }
scope :male, -> { where(gender: :male) }
scope :adult, -> { where('age >= 18') }
end
上記が基本のscopeの構文です。scope スコープ名 処理
となってます。処理はラムダ式で書いてます。do~endでもかけます。(ラムダ式の方が綺麗に見えるンゴ!)
これでコントローラにてメソッドが使えます。
class UsersController < ApplicationController
def index
@user_index = User.status_normal.male.adult
end
end
ここで一つ、**クラスメソッドでもよくね?**という疑問。
確かに、User.rb
で上記のクエリを発行する様なメソッドを定義しても良さそうです。
しかし、スコープとクラスメソッドの違いはメソッドチェーンの点で利があることです。
scopeでは条件がnilの場合.all
を返してくれます。
例えば引数を与えて、それを元に条件式を用いる場合、
scope :age_younger, ->(age) { where("age < ?", age) if age.present? }
class << self
def self.age_younger(age)
where("age < ?", age) if age.present?
end
end
上記では条件式がfalseの場合、scopeは.all
を返してくれますが、クラスメソッドはnilを返すので安易にメソッドチェーンにできません。
例えば、「20歳以上、男性」の検索の時は困りませんが、「年齢指定なし、男性」の検索の時、クラスメソッドだとメソッドチェーンしていた場合、nil
が返ってしまいます。一方、scopeは年齢指定なしでも.all
が返ってくるので男性で検索がかかります。
これがscope
の利点です。
クエリがにnil
になる可能性がある場合がscopeを使いましょう。確実にクエリが存在する場合はクラスメソッドでも大丈夫です。
最後に、下記のメソッドチェーンをコントローラに書いとくのは再利用もできないし、管理がしにくくなるので、modelでメソッドにまとめます。
@user_index = User.status_normal.male.adult
class User < ApplicationRecord
#ここまでは略します
scope :status_normal, -> { where(status: :normal) }
scope :male, -> { where(gender: :male) }
scope :adult, -> { where('age >= 18') }
class << self
#通常会員の成人男性のクエリ発行
def noraml_adult_male
status_normal.male.adult
end
end
end
class UsersController < ApplicationController
def index
@user_index = User.normal_adult_male
end
end
めちゃんこスッキリですね!この様にオブジェクティブ指向なコーディングが自然にできる様になりたいものです。
##ちょっと待ったあああああああああああああああああ
すみません。ご指摘が入りまして、
以下のscope(status_normal
,male
)はenumで既にwhere句が生成されているのでいらないですね。
class User < ApplicationRecord
enum status: {
normal: 0, #通常会員
premium: 1, #プレミアム会員
}
#User.normalで`where status = 0`
enum gender: {
male: 0, #男性
female: 1, #女性
}
#User.maleで`where status = 0
scope :adult, -> { where('age >= 18') }
class << self
#通常会員の成人男性のクエリ発行
def noraml_adult_male
normal.male.adult
end
end
end
##まとめ
明日はまだなに書くか決めてませんが、頑張ります。
##参考にしたの
Rails: スコープをモデルの外でチェインするのはやめておけ(翻訳)
https://techracho.bpsinc.jp/hachi8833/2018_03_19/53537
Railsでよく利用する、Scopeの使い方。
https://qiita.com/ngron/items/14a39ce62c9d30bf3ac3
Active Record クエリインターフェイス
https://railsguides.jp/active_record_querying.html
【Rails】 scope は 常に ActiveRecord_Relation を返す素晴らしいやつ
http://bamboo-yujiro.hatenablog.com/entry/2017/07/21/011138