Rails クラスメソッドとscopeっていったい何が違うのよ！

# はじめに
駆け出しエンジニアの私にとって実務のコードはメソッドだらけでなかなか読み進めることが難しいです。
メソッドに出くわしたら、検索して書かれている場所を特定してアルゴリズムを理解して、また次の行へ・・・
そんな中次のようなメソッドがあり、コードの内で検索をかけるのですが、なぜかメソッドが見つからないことがありました。
~~~ ruby
モデル名.hoge
~~~
メソッドといえば
~~~ ruby
def hoge
   
end
~~~
この形が一般的です。
ですが、探してもどこにも見つかりません。
ただmodelにこんな記述がありました。
~~~ ruby
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とはモデル側で定義することにより、同名のクラスメソッドを定義することができるものです。

記述の仕方としては次のように記述します。

~~~ ruby
scope :スコープの名前, -> { 条件式 }
~~~
先ほど登場したscopeとしては
~~~ ruby
scope :hoge, -> { where.not(category_id: 3)}
~~~
category_idが3ではない値を取得するhogeという名のscopeということになります。
定義したscopeは次のように使うことができます。
~~~ ruby
モデル名.hoge
~~~

やっぱり文字だけではわからないので実際にクラスメソッドとscopeを作って比較してみます。
# 前提
データを準備します。
dramasテーブルを作り値を準備します。
seeds.rbに次のように記述し、rails db:seedでDBにデータを登録します。
~~~ seeds.rb
Drama.create!(
    [
      {
        title: '世界の中心で愛をさけぶ',
        actor: '山田孝之 綾瀬はるか',
        period: 2004
      },
      {
        title: '男女７人夏物語',
        actor: '明石家さんま 大竹しのぶ',
        period: 1986
      },
      {
        title: 'ロングラブレター 〜漂流教室〜',
        actor: '山田孝之 水川あさみ',
        period: 2002
      },
      {
        title: 'HERO',
        actor: '木村拓哉 松たか子',
        period: 2001
      },
      {
        title: '北の国から',
        actor: '田中邦衛 吉岡秀隆',
        period: 1981
      }
     ]
    )
~~~
[![Image from Gyazo](https://i.gyazo.com/ce29eaeec3ada1c3f129d8a4b826fb38.png)](https://gyazo.com/ce29eaeec3ada1c3f129d8a4b826fb38)
データの準備は完了です。

# scopeを作ってみよう
periodが2001以上のレコードを取得するクラスメソッドとscopeを作ります。
### クラスメソッドを作成する

dramasモデルにクラスメソッドを作成します。
~~~ drama.rb
class Drama < ApplicationRecord
  def self.twenty_one_century_drama
    where("period >= ?", 2001)
  end
end
~~~
呼び出すと次のような戻り値となります。
[![Image from Gyazo](https://i.gyazo.com/c863c832cc89ead2469c4a795d54d7b5.png)](https://gyazo.com/c863c832cc89ead2469c4a795d54d7b5)
periodが2001以上のレコードを取得しています。

### scopeを利用した場合

今度はscopeを定義してみます。
~~~ drama.rb
class Drama < ApplicationRecord
  scope :scope_twenty_one_century_drama, -> { where("period >= ?", 2001) }
end
~~~
呼び出すと次のような結果です。
[![Image from Gyazo](https://i.gyazo.com/ae1aa1b336437111362b89bdd25dbb7f.png)](https://gyazo.com/ae1aa1b336437111362b89bdd25dbb7f)
periodが2001以上のレコードを取得しています。
クラスメソッドを使用する場合と全く同じ挙動をしています。

# 結局クラスメソッドとscopeは同じなのか

基本的にクラスメソッドとscopeは同じのように見えますが、実は違う部分があります。
それは**nilを返す場合の挙動**です。

- クラスメソッドの場合
~~~ drama.rb
class Drama < ApplicationRecord
  def self.twenty_one_century_drama
    nil
  end
end
~~~
このようにクラスメソッドの処理をnilとします。
実行してみましょう。
[![Image from Gyazo](https://i.gyazo.com/eda477870a2f153e9f425f851f895244.png)](https://gyazo.com/eda477870a2f153e9f425f851f895244)
戻り値はnilの値となっています。

- scopeの場合
次にscopeの条件式にもnilを与えてみましょう。
~~~ drama.rb
class Drama < ApplicationRecord
  scope :scope_twenty_one_century_drama, -> { nil }
end
~~~
実行してみます。
[![Image from Gyazo](https://i.gyazo.com/ba206a0504a036134a9731d69790008a.png)](https://gyazo.com/ba206a0504a036134a9731d69790008a)
上のようにDrama.allとして値が返ってきます。

このようにクラスメソッドはnilを返し、scopeは全レコードを返すという違いがあります。
このことからクラスメソッドにメソッドチェーンを使う場合は注意が必要です。
クラスメソッドにおいては戻り値がnilになるため、メソッドチェーンを使うとエラーになります。
[![Image from Gyazo](https://i.gyazo.com/796cd32da809002dc3e72f0d3c01605e.png)](https://gyazo.com/796cd32da809002dc3e72f0d3c01605e)

# scopeにも引数を渡せるが・・・
クラスメソッドには次のように引数を渡すことができます。
~~~ drama.rb
class Drama < ApplicationRecord
  def self.twenty_one_century_drama(num)
    where("period >= ?", 2001).limit(num)
  end
end
~~~
[![Image from Gyazo](https://i.gyazo.com/cc19d6e26d3007ef5d095d42e2a34377.png)](https://gyazo.com/cc19d6e26d3007ef5d095d42e2a34377)

scopeにも次のように記述することで引数を渡すことができます。
~~~ drama.rb
class Drama < ApplicationRecord
  scope :scope_twenty_one_century_drama, -> (num){ where("period >= ?", 2001).limit(num) }
end
~~~
[![Image from Gyazo](https://i.gyazo.com/5c73e57e4c44ab08594ed5b7abcca4a4.png)](https://gyazo.com/5c73e57e4c44ab08594ed5b7abcca4a4)
実行結果はクラスメソッドと同じになります。

~~しかし、Railsガイドでは引数を使う場合はscopeではなく、クラスメソッドを使うように推奨されています。~~

# scopeはあまりおすすめされない？
scopeに対しては使わない方がいいのではという意見もありました。
scopeを使うべきでないという理由としては

- ~~scopeを1行で書こうとしすぎるあまり、複雑なコードになってしまう。~~
- コメントが書かれないことが多い
- テストコードが書かれないことが多い
 
といったものでした。
使うならば、見やすく誰からもわかるようにしなければなりませんね。
テストも見落とさず書くようにしましょう。

# おわりに
今回はscopeの使い方について学んだことを書きました。
結果的にそこまで大きな違いはないことがわかりました。
自分が今後コードを書くときはクラスメソッドがわかりやすいなーと個人的には思いました。

**コメントに@scivolaさんよりコメントをいただいています。勉強になりますので、ぜひ見てください**

### 参考記事
https://railsguides.jp/

https://pikawaka.com/rails/scope

https://nekonenene.hatenablog.com/entry/do-not-use-rails-scope