Ruby on Rails

【Rails】なんで検索のメソッドはクラスメソッドじゃなくてスコープをつかったほうがいいの?

Railsでコードを書いていると、検索メソッドをクラスメソッドで書いているとscopeで定義したほうがいいんじゃないかという指摘をもらうことがあったりします。僕もなんとなく慣習なのかと思っていましたが、スコープで書いたほうがいい理由みたいなのが見つかったので書いていきたいと思います。

スコープで書いたほうがいい理由

結論ですが、メソッドチェーンでつないだときにチェーンが途切れないためです。
例えばこんなクラスがあったとします。(if falseに対するツッコミはなしでお願いします。)

class Article < ApplicationRecord
  scope :public_articles, -> { where(is_public: true) if false }
end

このArticleクラスに対して定義したメソッドを呼び出すとなにが返るでしょうか?
コンソールで試すとif文がfalseなので実行されていないにも関わらず、ちゃんとActiveRecord_Relationが返ってきていることが分かります。

Article.public_articles.class
=> Article::ActiveRecord_Relation

Article.public_articles
=>   Article Load (5.8ms)  SELECT `articles`.* FROM `articles`
[#<Article:0x0000aaaad9b6e8e0
  id: 1,
  title: "aaa",
  content: "aaa",
  created_at: Wed, 29 Dec 2021 06:06:16.344008000 UTC +00:00,
  updated_at: Wed, 29 Dec 2021 06:06:16.344008000 UTC +00:00,
  is_public: true>,
 #<Article:0x0000aaaad9c41ee8
  id: 2,
  title: "記事",
  content: "内容",
  created_at: Wed, 29 Dec 2021 06:06:26.953078000 UTC +00:00,
  updated_at: Wed, 29 Dec 2021 06:06:26.953078000 UTC +00:00,
  is_public: true>]

一方、これと同じメソッドをクラスメソッドで定義するとどうなるでしょうか?

class Article < ApplicationRecord
  def self.public_articles
    where(is_public: true) if false
  end
end

コンソールを使ってメソッドを呼び出してみます。

Article.public_articles
=> nil

nilが返っちゃいました。if文がfalseなので、実行されずに戻り値がnilになっているわけですね。これでメソッドをチェーンするとエラーになります。

Article.public_articles.recent
NoMethodError: undefined method `recent' for nil:NilClass

クラスメソッドで必ずActiveRecord_Relationが返るようにすればいいですが、スコープを使ったほうが安全なのが分かったと思います。

検索のメソッドはチェーンして使われることを想定しないといけないので、(もしあったら僕ならチェーンできると思っちゃいます)戻り値がnilにならないように配慮しないといけない。それがscopeを使うと簡単にできるということでした。nilにならないようなメソッドでもどうせならscopeに定義するで統一しようぜということで検索系のメソッドはscopeで書くという慣習になっているのだと思いました。想像ですがw

今日は短いですが以上です。
よい年末を!

ABOUT ME
sakai
東京在住の30歳。元々は車部品メーカーで働いていてましたが、プログラミングに興味を持ちスクールに通ってエンジニアになりました。 そこからベンチャー → メガベンチャー → 個人事業主になりました。 最近は生成 AI 関連の業務を中心にやっています。 ヒカルチャンネル(Youtube)とワンピースが大好きです!