名前はいいの思いつかなかったので仮。
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
- rails/finder_methods.rb at v4.2.0 · rails/rails · GitHub
- rails/core.rb at master · rails/rails · GitHub
- rails/querying.rb at master · rails/rails · GitHub
- rails/query_methods.rb at v4.2.0 · rails/rails · GitHub
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
が呼ばれてる。
最終的には同じ処理を通るのでよしとする。