LoginSignup
11
5

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-08-11

はじめに

駆け出しエンジニアの私にとって実務のコードはメソッドだらけでなかなか読み進めることが難しいです。
メソッドに出くわしたら、検索して書かれている場所を特定してアルゴリズムを理解して、また次の行へ・・・
そんな中次のようなメソッドがあり、コードの内で検索をかけるのですが、なぜかメソッドが見つからないことがありました。

モデル名.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にデータを登録します。

seeds.rb
Drama.create!(
    [
      {
        title: '世界の中心で愛をさけぶ',
        actor: '山田孝之 綾瀬はるか',
        period: 2004
      },
      {
        title: '男女7人夏物語',
        actor: '明石家さんま 大竹しのぶ',
        period: 1986
      },
      {
        title: 'ロングラブレター 〜漂流教室〜',
        actor: '山田孝之 水川あさみ',
        period: 2002
      },
      {
        title: 'HERO',
        actor: '木村拓哉 松たか子',
        period: 2001
      },
      {
        title: '北の国から',
        actor: '田中邦衛 吉岡秀隆',
        period: 1981
      }
     ]
    )

Image from Gyazo
データの準備は完了です。

scopeを作ってみよう

periodが2001以上のレコードを取得するクラスメソッドとscopeを作ります。

クラスメソッドを作成する

dramasモデルにクラスメソッドを作成します。

drama.rb
class Drama < ApplicationRecord
  def self.twenty_one_century_drama
    where("period >= ?", 2001)
  end
end

呼び出すと次のような戻り値となります。
Image from Gyazo
periodが2001以上のレコードを取得しています。

scopeを利用した場合

今度はscopeを定義してみます。

drama.rb
class Drama < ApplicationRecord
  scope :scope_twenty_one_century_drama, -> { where("period >= ?", 2001) }
end

呼び出すと次のような結果です。
Image from Gyazo
periodが2001以上のレコードを取得しています。
クラスメソッドを使用する場合と全く同じ挙動をしています。

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

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

  • クラスメソッドの場合
drama.rb
class Drama < ApplicationRecord
  def self.twenty_one_century_drama
    nil
  end
end

このようにクラスメソッドの処理をnilとします。
実行してみましょう。
Image from Gyazo
戻り値はnilの値となっています。

  • scopeの場合
    次にscopeの条件式にもnilを与えてみましょう。
drama.rb
class Drama < ApplicationRecord
  scope :scope_twenty_one_century_drama, -> { nil }
end

実行してみます。
Image from Gyazo
上のようにDrama.allとして値が返ってきます。

このようにクラスメソッドはnilを返し、scopeは全レコードを返すという違いがあります。
このことからクラスメソッドにメソッドチェーンを使う場合は注意が必要です。
クラスメソッドにおいては戻り値がnilになるため、メソッドチェーンを使うとエラーになります。
Image from Gyazo

scopeにも引数を渡せるが・・・

クラスメソッドには次のように引数を渡すことができます。

drama.rb
class Drama < ApplicationRecord
  def self.twenty_one_century_drama(num)
    where("period >= ?", 2001).limit(num)
  end
end

Image from Gyazo

scopeにも次のように記述することで引数を渡すことができます。

drama.rb
class Drama < ApplicationRecord
  scope :scope_twenty_one_century_drama, -> (num){ where("period >= ?", 2001).limit(num) }
end

Image from Gyazo
実行結果はクラスメソッドと同じになります。

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

scopeはあまりおすすめされない?

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

  • scopeを1行で書こうとしすぎるあまり、複雑なコードになってしまう。
  • コメントが書かれないことが多い
  • テストコードが書かれないことが多い

といったものでした。
使うならば、見やすく誰からもわかるようにしなければなりませんね。
テストも見落とさず書くようにしましょう。

おわりに

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

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

参考記事

11
5
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
5