先輩からちょいと指摘された行ったリファクタリングが、ちょいと深くておもしろかったのでメモ。
背景とやりたいこと
ゲームのコースのデータを管理するgameモデルがあるとします。
ゲームにはstage1, 2があり、各コースにはnormal, specialの2つの種類(kind)があります。
# == 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だけで同じことを実現しています。エコ!
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で「プログラミングは動けばいいんですよね」と語られていてとなりましたが、この課題に見られるように、僕はその段階からステップアップして、プログラムがどう動くかを気遣えるようになりたいと思ったりとか。
@Kta-M に感謝!