Edited at

便利なscope 初心者→中級者へのSTEP6/25


便利なscope

はじめに

前回の記事でenum素敵ってなりましたね。じゃあscopeも素敵ってなっときましょう。


scopeとは

scope?グローバル変数とかのこと?とか最初は勘違いしてました。違います。

こいつは、SQL文をメソッドにしてくれる良いやつなんです。

では例を用いて説明しましょう。


scopeを書かない場合

以下の様にな探索をかける場合、この様なuserの探索が他の場面でも必要になった時に管理に困ります。やっぱり年齢は20歳以上で探索しようだの、やっぱり女性に絞ろうだのいった時に修正箇所が広がり大変です。

なのでscopeを用いてメソッドとし、カプセル化しましょう。オブジェクティブに生きていくと幸せなのです。


users_controller.rb

class UsersController < ApplicationController

def index
@user_index = User.where(status: :normal)
.where(gender: :male)
.where('age >= 18')

end
end



scopeでmodelにまとめる。

さっきのsql文をmodelにまとめると下記の様になります。


user.rb

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でもかけます。(ラムダ式の方が綺麗に見えるンゴ!)

これでコントローラにてメソッドが使えます。


users_controller.rb

class UsersController < ApplicationController

def index
@user_index = User.status_normal.male.adult

end
end


ここで一つ、クラスメソッドでもよくね?という疑問。

確かに、User.rbで上記のクエリを発行する様なメソッドを定義しても良さそうです。

しかし、スコープとクラスメソッドの違いはメソッドチェーンの点で利があることです。

scopeでは条件がnilの場合.allを返してくれます。

例えば引数を与えて、それを元に条件式を用いる場合、


User.rb

scope :age_younger, ->(age) { where("age < ?", age) if age.present? }



User.rb

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


user.rb

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



users_controller.rb

class UsersController < ApplicationController

def index
@user_index = User.normal_adult_male
end
end

めちゃんこスッキリですね!この様にオブジェクティブ指向なコーディングが自然にできる様になりたいものです。


ちょっと待ったあああああああああああああああああ

すみません。ご指摘が入りまして、

以下のscope(status_normal,male)はenumで既にwhere句が生成されているのでいらないですね。


user.rb

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