はじめに

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

モデル名.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
      }
     ]
    )

Rails クラスメソッドとscopeっていったい何が違うのよ!
データの準備は完了です。

scopeを作ってみよう

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

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

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

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

呼び出すと次のような戻り値となります。
Rails クラスメソッドとscopeっていったい何が違うのよ!
periodが2001以上のレコードを取得しています。

scopeを利用した場合

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

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

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

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

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

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

このようにクラスメソッドの処理をnilとします。
実行してみましょう。
Rails クラスメソッドとscopeっていったい何が違うのよ!
戻り値はnilの値となっています。

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

実行してみます。
Rails クラスメソッドとscopeっていったい何が違うのよ!
上のようにDrama.allとして値が返ってきます。

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

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

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

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

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

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

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

Rails クラスメソッドとscopeっていったい何が違うのよ!
実行結果はクラスメソッドと同じになります。

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

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

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

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

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

おわりに

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

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

参考記事


原创声明:本文系作者授权爱码网发表,未经许可,不得转载;

原文地址:https://www.likecs.com/show-308623051.html

相关文章: