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

勉強会にて
- `find` `find_by` `where`の使い分け
- それぞれどんな時に使うのか
- 取れるデータはどんな形か

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

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

![todo2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/f86e1613-e378-88c4-421c-86f80497e919.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`の上位版とでもいうべきかも。ただし記述が少し長くなる。

- `find`は`id`のみでしたが、`find_by`は`id以外もOK`
- もちろんidでの検索もできる
- 条件を複数設定することが可能
- 取得できるデータが最初に見つかった`1件`**<font color="Red">（超重要！！）</font>**

| 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件目のデータ`を取得しています。

あと、データがない場合に`エラーでなくて` **<font color="Red">nil</font>** です。

```.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は少し違います。

- 該当データをすべて取得 **<font color="Red">※一件でないです</font>**
- 取得した件数が`一件でも配列`（取り出し方注意）

普通やらないですが、試しに`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
```

```.erb:index.html.erb
<h1>ToDo一覧</h1>
<table>
  <% @todos.each do |todo| %>
    <tr>
      <th><%= todo.title %></th>
    </tr>
  <% end %>
</table>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/187dc676-d23a-e21b-2e88-a36a5891c480.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|複数のデータを取得する場合|取り出し方 ＆ データなしでカラ配列|