【Rails初心者から中級者まで】scopeの基礎から完全解説

# はじめに

rails単体でのアプリケーションでは正直scopeというものは使わなかったですが、実務においてSPAだとかなり使う面があったので、今回記事にしました。

### scopeとは何か

https://railsguides.jp/active_record_querying.html#%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97:~:text=strict_loading%3A%20true%0Aend-,15%20%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97,-%E3%82%88%E3%81%8F%E4%BD%BF%E3%81%86%E3%82%AF

Rails ガイドさん より

> よく使うクエリをスコープに設定すると、関連オブジェクトやモデルへのメソッド呼び出しとして参照できるようになります。スコープでは、where、joins、includesなど、これまでに登場したメソッドをすべて使えます。どのスコープメソッドも、常にActiveRecord::Relationオブジェクトを返します。スコープの本体では、別のスコープなどのメソッドをスコープ上で呼び出せるようにするため、ActiveRecord::Relationかnilのいずれかを返すようにすべきです。


簡単にいうとSQL文をモデルに対してメソッド化して使える仕組みのことです。
### なぜscopeを使うべきか
私の業務でもSPAでの開発を行っているのですが、scopeを使う第一メリットとしてscopeで必要なデータのみの取得を行うため**query（クエリ）の最適化ができSPAのパフォーマンス向上**につながる。ことやSPAで**データのフィルタリング**ができるため用いられる。　ことが私の第一印象です。

他にも

https://pikawaka.com/rails/scope

Pikawaka さんより
* 条件式に名前を付けられるので、直感的なコードになる
* 修正箇所を限定することが出来る
* コードが短くなる

というメリットがあります。

# scopeの基本
### 基本的な構文

```ruby:spec基本構文
class モデル名 < ApplicationRecord
  scope :スコープの名前, -> { 条件式 }
end
```
**scopeの名前は自分でつけることができます（任意の名前ok）**

基本的な構文は上の形になるのですが、じゃあどんな時に使うのかとなると
```ruby:例
class HomeController < ApplicationController
  def index
    if params[:kind] == 'following'
      @posts = Post.includes(:user).where(user_id:
                    [*current_user.followings.ids]).order('created_at DESC').page(params[:page]).per(9)
    else
      @posts = Post.includes(:user).order('created_at DESC').page(params[:page]).per(9)
      @post = Post.new
    end
  end

```

これは私が未経験の頃rails単体でアプリケーション作成中のコードですね。
何してるかというと、簡単なフォロー関係にある投稿を最新順に9投稿取得するというコードになっています。(gemのkaminariを使用)

見てくれたらわかると思うのですが、やたら冗長ですね。

このSQL文の部分をPost モデルにscopeとして記述すると

```ruby:Postモデル
class Post < ApplicationRecord
  belongs_to :user

  scope :with_users, -> { includes(:user) }
  scope :newest_first, -> { order('created_at DESC') }
  scope :from_followed_users, -> (user) { where(user_id: user.followings.ids) }
  scope :paginate, ->(page, per_page = 9) { page(page).per(per_page) }
  
end

```
そして、先ほどのControllerに適用させると

```ruby:
class HomeController < ApplicationController
  def index
    @posts = Post.with_users.newest_first
    if params[:kind] == 'following'
      @posts = @posts.from_followed_users(current_user)
    else
      @post = Post.new
    end
    @posts = @posts.paginate(params[:page])
  end
```

今ちょっと修正しましたけど、あ〜ら不思議スッキリしたコードになりました！
このようにして、Controllerで使い回すSQL文の部分はspecに書くとDRYになります！

ちなみにscope paginateの第二引数はデフォルト値であって
```ruby
Post.paginate(3,15)
# 3ページ、15個のdataを取得
```
ともできます。
さらに
scopeの ' -> 'はラムダ式の省略記法を表しています。つまり

```ruby
scope :from_followed_users, lambda { |user| 
  where(user_id: user.followings.ids)
}
scope :paginate, lambda { |page, per_page = 9| 
  page(page).per(per_page)
}
  
```
こっちでも良きです。

# さらなる使用例

UserとPostに(1対多)、PostとCommentに(1対多)の関係があるとします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3494258/d4300424-20d2-c357-bb01-3d25b66d5c78.png)
あるUserからそのUserに対するCommentを取得するとします。すると、Postの数×Commentの数だけ試行回数を行う必要があります。そこでUserとPostをjoinしてUserのidで絞るとCommentの数以下の試行回数ですみます。
ここで、scopeの出番です。
```ruby
class Comment < ApplicationRecord

  has_one :user, through: :post
  belongs_to :post
  
  scope :users_with_posts, lambda { |user_id|
    join(:user)
      .where(user: {user_id:})
      .distinct
  }

end
```
ここでこのscopeメソッドを実際に使うためには
```ruby:Comment.rb
has_one :user, through: :post
belongs_to :post
```
```ruby:Post.rb
has_many :comments
belongs_to :user
```
この**リレーションシップを記述していないとscopeはうまくいきませんでした**。

また、今回は親、子、孫の関係で`has_one :user, through: :post`があるのでjoinの文は`join(:user)`で良かったのですが、Postとfavorite(いいね機能),PostとCommentのような場合だと`join(post: :favorites)`と記述する必要があります。

このようにすることでSPAだとクライアントサイドの要求に対して効率的なAPIレスポンスを提供することができ,SPAのパフォーマンス向上につながると考えられます。

# scopeとクラスメソッドの違い
いろいろ読ませていただきました。大変勉強になりました。

https://qiita.com/tatsu0209/items/5b91c4698d5631c74869

https://qiita.com/Ryuta1346/items/87be4100d7322a26acff

https://techtechmedia.com/difference-between-scope-and-class-method-rails/

* scopeはメソッドチェーンができるというところ
* 引数にnilを取るとscopeはall, クラスメソッドはfalseが返り値となる

それ以外は大きな違いはないということのようです。

https://techblog.whitehealthcare.co.jp/entry/2023/09/11/135251

ですが、この記事を拝見してから
`ActiveRecord::Relation オブジェクトを返す時はscopeを使う!`

# 終わりに

scopeを使うことでコードの可読性や再利用性の向上があるので、railsを使う初心者はぜひ学習してみてはいかがでしょうか。

私も早く『私はITエンジニアです』と名乗れるようになりたいですねぇ(5年後くらいかな)

誤り、改善等ございましたらご指摘していただくとありがたいです。
ここまでありがとうございました。