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カラムを結合させています。