1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

find, find_by, whereの違いと特徴を丁寧に

Last updated at Posted at 2019-10-21

勉強会にて

  • find find_by whereの使い分け
  • それぞれどんな時に使うのか
  • 取れるデータはどんな形か

が理解できていない人が多かったので勉強会用資料として書きます。
※初心者向けですのでわかりやすさ重視を心掛けました

findメソッド

自動で作られて勝手に連番になってくれるidってありますよね。
このidを絞り込みの条件にしてデータを取得する

todo2.gif

こんな感じで作ると

id title created_at updated_at
1 ああああ 2019-10-18 04:05:36.776003 2019-10-18 04:05:36.776003
2 買い物 2019-10-18 04:05:43.090180 2019-10-18 04:05:43.090180
3 帰宅 2019-10-18 04:05:51.098753 2019-10-18 04:05:51.098753

こんな感じでデータベースに登録されます。
で、その最初の列のidを使って検索します。

.rb
2.5.3 :001 > Todo.find(1)
  Todo Load (0.2ms)  SELECT  "todos".* FROM "todos" WHERE "todos"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 => #<Todo id: 1, title: "ああああ", created_at: "2019-10-18 04:05:36", updated_at: "2019-10-18 04:05:36">

データが1件取れます。同じidは存在しないので1件しか取れないのが当たり前ですが一応。

試しにidが10を条件にして探してみる。

.rb
2.5.3 :002 > Todo.find(10)
Todo Load (0.1ms)  SELECT  "todos".* FROM "todos" WHERE "todos"."id" = ? LIMIT ?  [["id", 10], ["LIMIT", 1]]
Traceback (most recent call last):
        1: from (irb):2
ActiveRecord::RecordNotFound (Couldn't find Todo with 'id'=10)

そんなデータないです!
というエラーが出る。これ注意です。whereではエラーは出ません。find_byではnilです。

whereで存在しないidを条件に検索してみる

.rb
2.5.3 :003 > Todo.where(id: 10)
  Todo Load (0.1ms)  SELECT  "todos".* FROM "todos" WHERE "todos"."id" = ? LIMIT ?  [["id", 10], ["LIMIT", 11]]
 => #<ActiveRecord::Relation []> 

カラの配列[]が取得されてます。
同じようなことをしても動作が少し違います。

find_byメソッド

一概には言えないですけどfindの上位版とでもいうべきかも。ただし記述が少し長くなる。

  • findidのみでしたが、find_byid以外もOK
  • もちろんidでの検索もできる
  • 条件を複数設定することが可能
  • 取得できるデータが最初に見つかった1件(超重要!!)
id title created_at updated_at
1 ああああ 2019-10-18 04:05:36.776003 2019-10-18 04:05:36.776003
2 買い物 2019-10-18 04:05:43.090180 2019-10-18 04:05:43.090180
3 帰宅 2019-10-18 04:05:51.098753 2019-10-18 04:05:51.098753
4 ああああ 2019-10-18 04:05:56.098753 2019-10-18 04:05:56.098753

例えばさっきのデータに同じtitleのデータを追加します。

.rb
2.5.3 :001 > Todo.find_by(title: "ああああ")
  Todo Load (0.1ms)  SELECT  "todos".* FROM "todos" WHERE "todos"."title" = ? LIMIT ?  [["title", "ああああ"], ["LIMIT", 1]]
 => #<Todo id: 1, title: "ああああ", created_at: "2019-10-18 04:05:36", updated_at: "2019-10-18 09:17:15"> 

1件目のデータを取得しています。

あと、データがない場合にエラーでなくて nil です。

.rb
2.5.3 :004 > Todo.find_by(title: "いいいい")                                                                    
  Todo Load (0.1ms)  SELECT  "todos".* FROM "todos" WHERE "todos"."title" = ? LIMIT ?  [["title", "いいいい"], ["LIMIT", 1]]
 => nil 

findで予期しないでnilが返ってくるとよろしくない場合あり、書くにしても少し長くなるなどfindの使い道はあります。間違いも減りますので基本的にはidで検索するときはfindでいく方がいいと思います。

whereメソッド

前述の2つは似てましたがwhereは少し違います。

  • 該当データをすべて取得 ※一件でないです
  • 取得した件数が一件でも配列(取り出し方注意)

普通やらないですが、試しにidが1の場合を検索して変数に入れます。

.rb
2.5.3 :006 > todo = Todo.where(id: 1)                                                                             
  Todo Load (0.2ms)  SELECT  "todos".* FROM "todos" WHERE "todos"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 11]]
 => #<ActiveRecord::Relation [#<Todo id: 1, title: "ああああ", created_at: "2019-10-18 04:05:36", updated_at: "2019-10-18 09:17:15">]> 

[#<Todo id: 1, title: "テスト", created_at: "2019-10-18 04:05:36", updated_at: "2019-10-18 09:17:15">]

配列で囲われていますね。→ []
というわけでTodo.where(id: 1).idみたいに取り出すことできません。
findとfind_byは

.rb
2.5.3 :007 > Todo.find(1).title
  Todo Load (0.1ms)  SELECT  "todos".* FROM "todos" WHERE "todos"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 => "ああああ"

みたいに取り出せます。

whereなら

.rb
2.5.3 :009 > todo = Todo.where(id: 1)                                                                    
  Todo Load (0.1ms)  SELECT  "todos".* FROM "todos" WHERE "todos"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 11]]
 => #<ActiveRecord::Relation [#<Todo id: 1, title: "ああああ", created_at: "2019-10-18 04:05:36", updated_at: "2019-10-18 09:17:15">]> 
2.5.3 :010 > todo[0].id
  Todo Load (0.3ms)  SELECT "todos".* FROM "todos" WHERE "todos"."id" = ?  [["id", 1]]
 => 1 

こんな感じで取り出します。全て配列で取得するので何番目かを指定しないといけないわけです。

今回取得できたのが1件なので分かりにくいかもしれませんので複数件取得できる条件で試してみます。
Todo.where(title: "ああああ")は2件ヒットします

.rb
2.5.3 :010 > todo = Todo.where(title: "ああああ")                                                            
  Todo Load (0.1ms)  SELECT  "todos".* FROM "todos" WHERE "todos"."title" = ? LIMIT ?  [["title", "テスト"], ["LIMIT", 11]]
 => #<ActiveRecord::Relation [#<Todo id: 1, title: "ああああ", created_at: "2019-10-18 04:05:36", updated_at: "2019-10-18 09:17:15">, #<Todo id: 5, title: "ああああ", created_at: "2019-10-21 04:04:05", updated_at: "2019-10-21 04:04:05">]>

ちょっと整理すると

#<ActiveRecord::Relation 
[
#<Todo id: 1, title: "ああああ", created_at: "2019-10-18 04:05:36", updated_at: "2019-10-18 09:17:15">,
#<Todo id: 5, title: "ああああ", created_at: "2019-10-21 04:04:05", updated_at: "2019-10-21 04:04:05">
]>

こんなデータ取れてます。
Todo.where(title: "ああああ").titleとするとidが1とidが5にデータ2つあるからどっちかわからないわけです。

というわけで最初に
todo = Todo.where(id: 1)としてデータを取得した後に
todo[0].idとして配列の0番目のidを取り出しました。

ついでにさっきのtitleが「ああああ」の例なら
todo = Todo.where(title: "ああああ")としてデータを取得した後に
todo[0].title  

結果:"テスト"

todo[0].created_atなら

結果:Fri, 18 Oct 2019 04:05:36 UTC +00:00

こんな感じで取れます。(Datetimeなのでこんな形式)

検索条件に一致しなかった場合

先ほどfindのところで説明しましたが、エラーでもnilでもなくカラ配列です。

.rb
2.5.3 :003 > Todo.where(id: 10)
  Todo Load (0.1ms)  SELECT  "todos".* FROM "todos" WHERE "todos"."id" = ? LIMIT ?  [["id", 10], ["LIMIT", 11]]
 => #<ActiveRecord::Relation []> 

whereの取り出し方

.rb
2.5.3 :027 > todos = Todo.where(title: "ああああ")                                                                  
  Todo Load (0.1ms)  SELECT  "todos".* FROM "todos" WHERE "todos"."title" = ? LIMIT ?  [["title", "テスト"], ["LIMIT", 11]]
 => #<ActiveRecord::Relation [#<Todo id: 1, title: "ああああ", created_at: "2019-10-18 04:05:36", updated_at: "2019-10-18 09:17:15">, #<Todo id: 5, title: "ああああ", created_at: "2019-10-21 04:04:05", updated_at: "2019-10-21 04:04:05">]> 
2.5.3 :028 > todos.each do |todo|
2.5.3 :029 >     puts todo.title
2.5.3 :030?>   end
テスト
テスト

todos.each do |todo|をつかって取り出しています。
todosに配列形式で入っているので1つの要素ずつtodoという変数に代入しています。

繰り返し1回目の変数todo
#<Todo id: 1, title: "ああああ", created_at: "2019-10-18 04:05:36", updated_at: "2019-10-18 09:17:15">

繰り返し2回目の変数todo
#<Todo id: 5, title: "ああああ", created_at: "2019-10-21 04:04:05", updated_at: "2019-10-21 04:04:05">

このように1つずつの要素がtodoに入っていて、1回目、2回目の要素の中にはtitleが一つしかありません。
なのでtodo.titleとすればデータを特定できるので取り出すことができます。

ついでにRailsで書くと

たぶんRailsで使うと思いますので、参考程度に。

todos_controller.rb
def index
  @todos = Todo.where(title: "テスト")
end
index.html.erb
<h1>ToDo一覧</h1>
<table>
  <% @todos.each do |todo| %>
    <tr>
      <th><%= todo.title %></th>
    </tr>
  <% end %>
</table>

image.png

テストというtitleを検索して表示している例です。

余談:エラーに気が付きにくいので注意

※ 少し難しいので省いてOKです。

whereだとデータなくてもエラーでもnilでもないので注意必要です。
条件に使いたいからデータあるかどうかで条件式書こうとか言っているとエラー起こるかもしれません。

.rb
2.5.3 :015 > todo = Todo.where(title: "いいいい")                                                      
  Todo Load (0.1ms)  SELECT  "todos".* FROM "todos" WHERE "todos"."title" = ? LIMIT ?  [["title", "いいいい"], ["LIMIT", 11]]
 => #<ActiveRecord::Relation []> 
2.5.3 :016 > !!todo
 => true
2.5.3 :017 > todo = Todo.find_by(title: "いいいい")                                                               
  Todo Load (0.1ms)  SELECT  "todos".* FROM "todos" WHERE "todos"."title" = ? LIMIT ?  [["title", "いいいい"], ["LIMIT", 1]]
 => nil 
2.5.3 :018 > !!todo
 => false 

whereとfind_byで返ってくるもの違うことに注意

【まとめ】それぞれの使い分けと注意

種類 使い分け 注意
find idで特定の1件取得すればいい場合 idのみ
find_by id以外で特定の1件取得すればいい場合 最初の1件のみ & データなしでnil
where 複数のデータを取得する場合 取り出し方 & データなしでカラ配列
1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?