2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

はじめに

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

scopeとは何か

Rails ガイドさん より

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

簡単にいうとSQL文をモデルに対してメソッド化して使える仕組みのことです。

なぜscopeを使うべきか

私の業務でもSPAでの開発を行っているのですが、scopeを使う第一メリットとしてscopeで必要なデータのみの取得を行うためquery(クエリ)の最適化ができSPAのパフォーマンス向上につながる。ことやSPAでデータのフィルタリングができるため用いられる。 ことが私の第一印象です。

他にも

Pikawaka さんより

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

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

scopeの基本

基本的な構文

spec基本構文
class モデル名 < ApplicationRecord
  scope :スコープの名前, -> { 条件式 }
end

scopeの名前は自分でつけることができます(任意の名前ok)

基本的な構文は上の形になるのですが、じゃあどんな時に使うのかとなると

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として記述すると

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に適用させると

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の第二引数はデフォルト値であって

Post.paginate(3,15)
# 3ページ、15個のdataを取得

ともできます。
さらに
scopeの ' -> 'はラムダ式の省略記法を表しています。つまり

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
あるUserからそのUserに対するCommentを取得するとします。すると、Postの数×Commentの数だけ試行回数を行う必要があります。そこでUserとPostをjoinしてUserのidで絞るとCommentの数以下の試行回数ですみます。
ここで、scopeの出番です。

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メソッドを実際に使うためには

Comment.rb
has_one :user, through: :post
belongs_to :post
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とクラスメソッドの違い

いろいろ読ませていただきました。大変勉強になりました。

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

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

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

終わりに

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

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

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

2
1
0

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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?