Railsでレコードを1つ取得する方法

ある案件のバグ調査をしていてUser.find_by(5)という取得方法でレコードを取得していたのが原因だったのですが、他にも似たようなことが起きないか調べたので残しておこうと思います(本来はテストで見つけるべきだったと思います)

主キーで1件取得する方法

まず結論から言うとidが主キーの場合、下記5つの方法で取得してくるデータは同じになります。

  • User.find(5)

    SELECT  `users`.* FROM `users` WHERE `users`.`id` = 5 LIMIT 1
    
  • User.find_by(id: 5)

    SELECT  `users`.* FROM `users` WHERE `users`.`id` = 5 LIMIT 1
    
  • User.find_by(‘users.id = ?’, 5)

    SELECT  `users`.* FROM `users` WHERE (users.id = 5) LIMIT 1
    
  • User.where(id: 5).take

    SELECT  `users`.* FROM `users` WHERE `users`.`id` = 5 LIMIT 1
    
  • User.where(‘users.id = ?’, 5).take

    SELECT  `users`.* FROM `users` WHERE (users.id = 5) LIMIT 1
    

バグになっていた取得方法

今回調べたバグの原因でもあったのですが、User.find_by(5)というように指定するとWHERE句が常にtrueになり条件がかからなくなってしまいます(User.find_by(0)とするとWHERE句がfalseなのでなにも取得できません)

User.find_by(5)
# SELECT  `users`.* FROM `users` WHERE (5) LIMIT 1

また、同じ現象がwhereを使った場合にも起きるので注意です。

User.where(5).take
# SELECT  `users`.* FROM `users` WHERE (5) LIMIT 1

なぜこんな方法を許可しているのか

バグを見つけたときはなぜRailsはこんな紛らわしい方法の指定を許しているのかと思ったのですが、逆の観点から考えるとActiveRecordで対応していないクエリを指定できる必要がある為そうなっていそうです。

ハッシュ以外の場合は以下のようにWHEREの中に直接していた値が入るという仕様になっているようです。

User.find_by("users.name LIKE '%羽生%'")
# SELECT  `users`.* FROM `users` WHERE (users.name LIKE '%羽生%') LIMIT 1