勉強会にて
-
find
find_by
where
の使い分け - それぞれどんな時に使うのか
- 取れるデータはどんな形か
が理解できていない人が多かったので勉強会用資料として書きます。
※初心者向けですのでわかりやすさ重視を心掛けました
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 | 複数のデータを取得する場合 | 取り出し方 & データなしでカラ配列 |