10
2

More than 1 year has passed since last update.

「SQLだけで済むので...」?先輩のコメントからany?とexists?の違いを学ぶ(Ruby,Rails)

Last updated at Posted at 2021-10-01

先輩からちょいと指摘された行ったリファクタリングが、ちょいと深くておもしろかったのでメモ。

背景とやりたいこと

ゲームのコースのデータを管理するgameモデルがあるとします。
ゲームにはstage1, 2があり、各コースにはnormal, specialの2つの種類(kind)があります。

app/models/game.rb
# == Schema Information
#
# Table name: games
#
#  id                 :bigint           not null, primary key
#  name               :text             not null
#  stage              :integer          not null
#  kind               :integer          default("first_normal"), not null

class Game < ApplicationRecord
  exnum stage: { first: 1, second: 2 }, _prefix: true
  exnum kind:  { normal: 10, special: 20 }, _prefix: true

やりたいこと→各stageにkind_specialがあるかを判定するモデルメソッドを作りたいです。

実装!

まずは僕が書いたコードと、先輩に提示されたコードを。

自分のコード

class Game < ApplicationRecord
  def self.stage_has_special?(stage)
    send("stage_#{stage}").any?(&:kind_special?)
  end
end

先輩のコード

class Game < ApplicationRecord
  def self.stage_has_special?(stage)
    send("stage_#{stage}").kind_special.exists?
  end
end

上の二つのコードどちらでも、「各stageにそれぞれkind_specialがあるかを判定する」というやりたいことは実現できます。

「なんでこのリファクタリングが必要なんだろう、可読性が上がるから?」としか気付けなかった僕はまだまだなようで、先輩からは「SQLだけで済むからです」と返ってきました。

「SQLだけで済む」とは?

2つのプログラムのログを見比べつつ、それぞれどんな挙動になるのか整理してみました。

僕コードのSQL

Game Load (1.1ms)
SELECT
  "games".*
FROM
  "games"
WHERE
  "games"."stage" = \ $ 1 [["stage", 1]]

僕のSQLは特定のstageのgamesを全て取得してきているだけです。
ということは、ログには現れませんが、この後にRubyのコードが走っているということになります。
any?でループが回ります。

先輩コードのSQL

Game Exists ? (1.4ms)
SELECT
  1 AS one
FROM
  "games"
WHERE
  "games"."stage" = \ $ 1
  AND "games"."kind" = \ $ 2
LIMIT
  \ $ 3 [["stage", 2], ["kind", 15], ["LIMIT", 1]]

先輩のコードは、特定のstageのgamesを条件付きで取得します。
その条件とは、どのstageか、kind_specialかなので、一度SQLを発行して終了です。
先ほどと違って、この後にRubyは動きません!
exists?メソッドはSQLでLIMIT 1を発行してくれるので、見つかった時点でBooleanを返してくれます。

確かにSQLだけで済んでいます!SQLだけで同じことを実現しています。エコ!:earth_asia:

any?とexists?まとめ

具体的にはEnumerable#any?ActiveRecord::FinderMethods#exists?のまとめです。

ここで出てきたany?はRubyのEnumerableモジュールのインスタンスメソッドでRubyを動かすメソッド。
exists?はActiveRecordのメソッドで、SQLを発行するメソッドです。Rubyは動きません(ハズ)。

どちらのメソッドもデータの存在を尋ねるという意味ではとても似ていて、RailsガイドのActive Recordのページ「20 オブジェクトの存在チェック」という章で、「モデルやリレーションでの存在チェックにはany?やmany?も使用できます。」と書いてあって、浅読みすると「exists?の代わりにany?も使えるよ!」と勘違いしそうですが、ここでいうany?Enumerable#any?ではなく、ActiveRecordで定義されているany?メソッドのことのようで、この記事で見てきたany?とは別物のようです。

ドキュメントで言うとこの辺のany?でしょうか↓
https://api.rubyonrails.org/classes/ActiveRecord/Associations/CollectionProxy.html#method-i-any-3F
https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-any-3F

参考までにexists?のドキュメント↓
https://api.rubyonrails.org/v6.1.3.1/classes/ActiveRecord/FinderMethods.html#method-i-exists-3F

最後に

とある実業家と「ベンチャー女優」が出演するYouTubeで「プログラミングは動けばいいんですよね:star2:」と語られていて:open_mouth:となりましたが、この課題に見られるように、僕はその段階からステップアップして、プログラムがどう動くかを気遣えるようになりたいと思ったりとか。

@Kta-M に感謝!

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