勉強会にて
- 
findfind_bywhereの使い分け
- それぞれどんな時に使うのか
- 取れるデータはどんな形か
が理解できていない人が多かったので勉強会用資料として書きます。
※初心者向けですのでわかりやすさ重視を心掛けました
findメソッド
自動で作られて勝手に連番になってくれるidってありますよね。
このidを絞り込みの条件にしてデータを取得する
こんな感じで作ると
| 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を使って検索します。
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を条件にして探してみる。
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を条件に検索してみる
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の上位版とでもいうべきかも。ただし記述が少し長くなる。
- 
findはidのみでしたが、find_byはid以外も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のデータを追加します。
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 です。
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の場合を検索して変数に入れます。
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は
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なら
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件ヒットします
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でもなくカラ配列です。
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の取り出し方
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で使うと思いますので、参考程度に。
def index
  @todos = Todo.where(title: "テスト")
end
<h1>ToDo一覧</h1>
<table>
  <% @todos.each do |todo| %>
    <tr>
      <th><%= todo.title %></th>
    </tr>
  <% end %>
</table>
テストというtitleを検索して表示している例です。
余談:エラーに気が付きにくいので注意
※ 少し難しいので省いてOKです。
whereだとデータなくてもエラーでもnilでもないので注意必要です。
条件に使いたいからデータあるかどうかで条件式書こうとか言っているとエラー起こるかもしれません。
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 | 複数のデータを取得する場合 | 取り出し方 & データなしでカラ配列 | 


