はじめに
駆け出しエンジニアの私にとって実務のコードはメソッドだらけでなかなか読み進めることが難しいです。
メソッドに出くわしたら、検索して書かれている場所を特定してアルゴリズムを理解して、また次の行へ・・・
そんな中次のようなメソッドがあり、コードの内で検索をかけるのですが、なぜかメソッドが見つからないことがありました。
モデル名.hoge
メソッドといえば
def hoge
end
この形が一般的です。
ですが、探してもどこにも見つかりません。
ただmodelにこんな記述がありました。
scope :hoge, -> { where.not(category_id: 3)}
どういうことだ・・・。このhogeは実はメソッドではいのか・・・?
scopeってなんだ・・・と思い、調べてみることにしました。
その内容を備忘録として残したいと思います。
もしも、おかしな点や間違いがありましたら、教えていただけると幸いです!
環境(自分のPC)
Ruby 2.7.1
Rails 6.0.5
scopeについて
- scopeとは
スコープを設定することで、関連オブジェクトやモデルへのメソッド呼び出しとして参照される、よく使用されるクエリを指定することができます。スコープでは、where、joins、includesなど、これまでに登場したすべてのメソッドを使用できます。
ふーむ。
少し難しいですが要は
scopeとはモデル側であらかじめメソッドのようなものを名前をつけて定義し、その名前をメソッドの様に呼び出すことができるものです。
scopeとはモデル側で定義することにより、同名のクラスメソッドを定義することができるものです。
記述の仕方としては次のように記述します。
scope :スコープの名前, -> { 条件式 }
先ほど登場したscopeとしては
scope :hoge, -> { where.not(category_id: 3)}
category_idが3ではない値を取得するhogeという名のscopeということになります。
定義したscopeは次のように使うことができます。
モデル名.hoge
やっぱり文字だけではわからないので実際にクラスメソッドとscopeを作って比較してみます。
前提
データを準備します。
dramasテーブルを作り値を準備します。
seeds.rbに次のように記述し、rails db:seedでDBにデータを登録します。
Drama.create!(
[
{
title: '世界の中心で愛をさけぶ',
actor: '山田孝之 綾瀬はるか',
period: 2004
},
{
title: '男女7人夏物語',
actor: '明石家さんま 大竹しのぶ',
period: 1986
},
{
title: 'ロングラブレター 〜漂流教室〜',
actor: '山田孝之 水川あさみ',
period: 2002
},
{
title: 'HERO',
actor: '木村拓哉 松たか子',
period: 2001
},
{
title: '北の国から',
actor: '田中邦衛 吉岡秀隆',
period: 1981
}
]
)
scopeを作ってみよう
periodが2001以上のレコードを取得するクラスメソッドとscopeを作ります。
クラスメソッドを作成する
dramasモデルにクラスメソッドを作成します。
class Drama < ApplicationRecord
def self.twenty_one_century_drama
where("period >= ?", 2001)
end
end
呼び出すと次のような戻り値となります。
periodが2001以上のレコードを取得しています。
scopeを利用した場合
今度はscopeを定義してみます。
class Drama < ApplicationRecord
scope :scope_twenty_one_century_drama, -> { where("period >= ?", 2001) }
end
呼び出すと次のような結果です。
periodが2001以上のレコードを取得しています。
クラスメソッドを使用する場合と全く同じ挙動をしています。
結局クラスメソッドとscopeは同じなのか
基本的にクラスメソッドとscopeは同じのように見えますが、実は違う部分があります。
それはnilを返す場合の挙動です。
- クラスメソッドの場合
class Drama < ApplicationRecord
def self.twenty_one_century_drama
nil
end
end
このようにクラスメソッドの処理をnilとします。
実行してみましょう。
戻り値はnilの値となっています。
- scopeの場合
次にscopeの条件式にもnilを与えてみましょう。
class Drama < ApplicationRecord
scope :scope_twenty_one_century_drama, -> { nil }
end
実行してみます。
上のようにDrama.allとして値が返ってきます。
このようにクラスメソッドはnilを返し、scopeは全レコードを返すという違いがあります。
このことからクラスメソッドにメソッドチェーンを使う場合は注意が必要です。
クラスメソッドにおいては戻り値がnilになるため、メソッドチェーンを使うとエラーになります。
scopeにも引数を渡せるが・・・
クラスメソッドには次のように引数を渡すことができます。
class Drama < ApplicationRecord
def self.twenty_one_century_drama(num)
where("period >= ?", 2001).limit(num)
end
end
scopeにも次のように記述することで引数を渡すことができます。
class Drama < ApplicationRecord
scope :scope_twenty_one_century_drama, -> (num){ where("period >= ?", 2001).limit(num) }
end
しかし、Railsガイドでは引数を使う場合はscopeではなく、クラスメソッドを使うように推奨されています。
scopeはあまりおすすめされない?
scopeに対しては使わない方がいいのではという意見もありました。
scopeを使うべきでないという理由としては
scopeを1行で書こうとしすぎるあまり、複雑なコードになってしまう。- コメントが書かれないことが多い
- テストコードが書かれないことが多い
といったものでした。
使うならば、見やすく誰からもわかるようにしなければなりませんね。
テストも見落とさず書くようにしましょう。
おわりに
今回はscopeの使い方について学んだことを書きました。
結果的にそこまで大きな違いはないことがわかりました。
自分が今後コードを書くときはクラスメソッドがわかりやすいなーと個人的には思いました。
コメントに@scivolaさんよりコメントをいただいています。勉強になりますので、ぜひ見てください
参考記事