scopeについて

scopeとは

ActiveRecord::Relationを返すメソッドです。

関連オブジェクトやモデルへの呼び出しなどでよく使用されるクエリを指定することができます。

scopeの中にはwhereやjoins,includesなどのメソッドを使用することができるようです。

例えばコントローラーで条件を指定すると可読性が下がってしまうな〜と感じた時に、モデルでscopeを設定することでその悩みを解決することができる優れものです。

使用例)

ApplicationRecordからクラスを継承した Articleクラスにおいて出版されたか否かを判定するscopeを定義しています。

class Article< ApplicationRecord
  scope :published,-> { where(published:true) }
end

これで、コントローラーで下記のように設定することができます。

class ArticleController < ApplicationController
  def published
 @published = Article.published
#=> Article.rbで定義した、publishedがtrueのArticleを呼び出しています。
  end
end

下記のようにスコープをスコープ内で使用することもできます。チェーンさせると言うみたいです。

class Article< ApplicationRecord
  scope :published,-> { where(published:true) }
  scope :published_and_commented,-> { published.where("comments_count > 0") }
  scope :past_published, -> { where('published_at <= ?', Time.current) }
end

こうすることで、例えばArticleControllerでpublished_and_commentedを呼び出したときに、下記のようにコードの記述量が削減されます。

class ArticleController < ApplicationController
  def published
 @published_and_commented = Article.published_and_commented
// @published = Article.find_by(published: true).where(comments_count > 0)と同じ
 @published = Article.past_pablished
// @published = Article.where(published_at: Time.current)と同じ
  end
end

引数も渡すこともできます。下記の例だと、timeに入る値によって動的にSQLを発行させることができます。

class Article < ApplicationRecord
  scope :created_before, ->(time) { where("created_at < ?", time) }
end

条件文も作ることもできます。下記の例だと、timeが存在する場合にscopeが適用されるよう設定されています。

class Article < ApplicationRecord
  scope :created_before, ->(time) { where("created_at < ?", time) if time.present? }
end

注意したい点として、この条件文は評価した結果がfalseだとしても、ActiveRecord::Relationオブジェクトを返すため、スコープでチェーンされていてかつどちらかがnilの場合、NoMethodErrorを発生させることがあります。

class Article< ApplicationRecord
  scope :published,-> { where(published:true) }
  scope :published_and_commented,-> { published.where("comments_count > 0") if time.present?}
end
//この場合、timeが存在していない場合はnilを返すので、NoMethodErrorを返す。

where句と同様、And条件でscopeをマージさせることもできます。

class User < ApplicationRecord
  scope :active, -> { where state: 'active' }
  scope :inactive, -> { where state: 'inactive' }
end

User.active.inactive
# SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'inactive'
// nilじゃないもの全てを表示しています。

join句を使ってマージさせることもできます。

scope :by_tag, ->(tag_id) { joins(:article_tags).where(article_tags: { tag_id: tag_id }) }
//by_tagというscopeを作成しています。
//tag_idとarticle_tagsが一致する場所で、article_tagsカラムとtag_idカラムを結合させています。

Active Record クエリインターフェイス - Railsガイド