名前はいいの思いつかなかったので仮。

find_by(nil) にハマるで書いたんだけど、またやっちゃう自信があったので、Railsの勉強がてら機械的に防止するGemを作った。

そもそもバグじゃないの?

find_by(nil) returns first record instead of nil · Issue #14867 · rails/rails · GitHub
ということなので、バグじゃない。仕様。

  • nil, 1, '1' => 最初のレコード
  • 'a' => エラー
  • 0 => nil

になる。

where_is_nil Gemは何をするか

find_by(nil)find_by(1)が呼ばれた時に、ログやエラーを出して、find_by(id: 1)の間違いじゃない?と警告します。

設定項目などはGitHubに。
tnantoka/where_is_nil · GitHub

find_byだけで、whereの時はnilでも何もしていない。
(GitHubで検索したらwhere(nil)はたくさん使われてたので。また、当初の目的はタイポ防止で、間違えてwhere(nil)することはあまりなさそうなため)

インストールと設定

# Gemfile
gem 'where_is_nil'

$ bundle

するだけです。

参考にしたGem

Gemは書き慣れてないので、いろいろ参考にさせていただいた。

alfa-jpn/kakurenbo · GitHub
yuki24/did_you_mean · GitHub
airblade/paper_trail · GitHub
plataformatec/devise · GitHub

The Basics of Creating Rails Plugins — Ruby on Rails Guides
公式のガイドもわりと充実してる。

一応動く状態になったので、個人プロジェクトで使っていこう。


以下メモ。

find_by(nil)した時に何が起きてるか

Railsのソースで関連しそうなところ(4.2.0)

find_by/where
arel

処理を追う

find_byはそのまま素通りして、whereになげる。

where内のcase文。
nilの場合はblank?がtrueになるので、selfがそのまま返される。
それがtakeされるので最初のレコードが取得される。

1やid: 1の場合は、where!が呼ばれる。

where_valuesが以下のように更新される。

  • { id: 1 } => [#<Arel::Nodes::Equality>]
  • 1 => [1]

あとはtakeの時にSQLに変換される。そこはarelの処理。

build_arelからcollapse_wheresが呼ばれ、以下のように変換される。

  • { id: 1 } => Equalityのまま変化なし
  • 1 => #<Arel::Nodes::Grouping>

それが、Arel::Nodes::And.newに渡される。

なんだかんだで最終的に、to_sqlされると、

irb> User.where(1).to_sql
=> "SELECT \"users\".* FROM \"users\" WHERE (1)"
irb> User.where(id: 1).to_sql
=> "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"id\" = 1"
irb> User.where(nil).to_sql
=> "SELECT \"users\".* FROM \"users\""

となる。

後から気づいたけど、↑はrelationなので、whereとかを呼んだ後find_byした時の話。
User.find_byを直接呼んだ場合は、
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/core.rb
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/querying.rb
が呼ばれてる。

最終的には同じ処理を通るのでよしとする。