セキュリティ系の勉強・その他開発メモとか雑談. Twitter, ブログカテゴリ一覧
本ブログはあくまでセキュリティに関する情報共有の一環として作成したものであり,公開されているシステム等に許可なく実行するなど、違法な行為を助長するものではありません.

【Rails5】find_by(nil)が先頭レコードを返す

//

find_by

find_byメソッドは、検索するカラムとその条件を指定してそのレコードのうち最初の一件を取得します。例えばfind_by(id: 1)ならidが1のレコードを返します。

find_by(nil) -> nil ???

使い方は正しくはありませんが、この使い方をするとnilが返ってきそうですが、実は先頭レコードが1件返ってきます。挙動としてはfirstと同じ感じ。こんな書き方は普段しませんが、カラムの指定を忘れ、paramsの値がnilであったりキー名をタイポしたりした際に、エラーになりません。

環境

ruby 2.5.3p105
Rails 5.2.3

テストに使用するモデル

$ bin/rails g model User name:string

Userモデルを作って、idとnameというカラムを持たせます。マイグレートしてください。ひとまず"Alice"という名前のUserを作っておきます。

発行されるSQL

User.find_by(name: "Alice")
User Load (0.7ms)  SELECT  `users`.* FROM `users` WHERE `users`.`name` = 'Alice' LIMIT 1
=> #<User id: 1, name: "aa", created_at: "2019-08-07 13:02:51", updated_at: "2019-08-07 13:02:51">


User.find_by("Alice")
User Load (0.6ms)  SELECT  `users`.* FROM `users` WHERE (Alice) LIMIT 1 #エラー


User.find_by(name:nil)
User Load (0.4ms)  SELECT  `users`.* FROM `users` WHERE `users`.`name` IS NULL LIMIT 1
=> nil


User.find_by(nil)
User Load (0.3ms)  SELECT  `users`.* FROM `users` LIMIT 1
=> #<User id: 1, name: "aa", created_at: "2019-08-07 13:02:51", updated_at: "2019-08-07 13:02:51">

なぜ

find_by

    def find_by(arg, *args)
      where(arg, *args).take
    rescue ::RangeError
      nil
    end

より、where(nil).takeが呼ばれます。

User.where(nil)
  User Load (0.5ms)  SELECT  `users`.* FROM `users` LIMIT 11
=> #<ActiveRecord::Relation [#<User id: 1, name: "aa", created_at: "2019-08-07 13:02:51", updated_at: "2019-08-07 13:02:51">, #<User id: 2, name: "Alice", created_at: "2019-08-07 13:24:35", updated_at: "2019-08-07 13:24:35">]>

より、where(nil)は条件なしで検索がかかるようです。
takelimit 1がかかるので、結果的に先頭のレコードが返って来る仕組みです。



github内でのissue

github.com
だいぶ昔に閉じられていますが、find_byはwhereのエイリアスだから、結局where(nil)の挙動の問題に帰結するよ。みたいな感じで閉じられています。where(nil)がそういう挙動なら、そりゃそうでしょ?ってお話でしょうか?

しかし、その一年後、いややっぱりおかしいよ。nilを返すべきだ。find_byの挙動はwhereの話と別にして考えるべきだ。みたいなことを言っている人が現れました。結局、何も変更がなかったようですが。
個人的にも、find_by(nil)nilを返すようにしていい気がします。変にテストとか通ってしまう可能性もあって、見落としがちになりそうです。