【Rails】世界で一番詳しいfind_byメソッドのいろいろな使い方

Rails

find_byの使い方を徹底解説

find_byメソッドとは検索でヒットしたレコードの初めの一件だけを返すメソッドです。

find_byの動作確認動画

find_byの使い方の画像

find_byの使い方
リンクをコピーしました

検索したいカラムに対して値をセットして実行すると、Userモデルのインスタンスが一件だけ返ってきます。

shell
1
2
3
4
5
6
7
8
9
10
11
User.find_by(email: 'programan@gmail.com')

=> #<User:0x007fe080f0ecf0
 id: 1,
 email: "programan@gmail.com",
 name: "programan",
 job_id: 1,
 sex: 0,
 age: 25, tall: 175.0, weight: 68.0,
 created_at: Fri, 14 Dec 2018 01:21:17 UTC +00:00,
 updated_at: Fri, 14 Dec 2018 01:21:17 UTC +00:00>

find_byを使う箇所は下記の3点あります。

  1. 一件しかヒットしないレコードに対して使用
  2. unique制約がかかっているカラムに対して使用
  3. 完全一致以外での検索に使用(あいまい検索等、完全一致よりも使用頻度は少ない)

find_byは一件だけしか返ってこないので、単一カラムの検索であればunique制約をかけているカラムの検索に使うことが多いです。

あいまい検索
リンクをコピーしました

programanという文字が含まれている中で、誰でも良いからとりあえず一人のデータを見たいなという場合

shell
1
2
3
4
5
6
7
8
9
10
11
User.find_by("name like '%programan%'")

=> #<User:0x007fe080f0ecf0
 id: 1,
 email: "programan@gmail.com",
 name: "programan",
 job_id: 1,
 sex: 0,
 age: 25, tall: 175.0, weight: 68.0,
 created_at: Fri, 14 Dec 2018 01:21:17 UTC +00:00,
 updated_at: Fri, 14 Dec 2018 01:21:17 UTC +00:00>

programanという文字が含まれているユーザーは他にもいますが、find_byでは複数件ヒットした中の初めの一件だけ返ってきてます。

不等号検索
リンクをコピーしました

あれ、今日ユーザー登録してくれたユーザーいたかな??という時にはこんな感じで検索します。

shell
1
2
3
4
5
6
7
8
9
10
11
User.find_by('created_at > ?', Date.today)

=> #<User:0x007fe080f0ecf0
 id: 1,
 email: "programan@gmail.com",
 name: "programan",
 job_id: 1,
 sex: 0,
 age: 25, tall: 175.0, weight: 68.0,
 created_at: Fri, 14 Dec 2018 01:21:17 UTC +00:00,
 updated_at: Fri, 14 Dec 2018 01:21:17 UTC +00:00>

これでデータ返ってきてるんで、あ、今日ユーザー登録してくれた人いるな!という感じです。

もし登録してくれた人がいなかった場合はnilが返ってきます。

shell
1
2
3
User.find_by('created_at > ?', Date.tomorrow)

=> nil

nilが返ってきたら登録してくれたユーザーはいなかったなと確かめられる訳です。

複数の条件で指定した場合
リンクをコピーしました

そもそも一件しかヒットしないレコードに対してfind_byを使うことが多いと言いましたが、それは複数の条件を指定した場合でも同じです。それが複合uniqueのカラムに対しての検索です。複合uniqueとは一つのカラムの場合はいっぱいデータあるけど、複数カラムの一致するデータは一件しかないよという場合です。

例えば今みなさんに見てもらっているピカわか!を例に考えてみましょう。
ピカわか!では、カテゴリー(ruby rails javascript等)ごとに記事が書かれています。


ピカわか!のblogsからifという記事を取り出したいときに
rubyにもifという記事はありますし、javascriptにもifという記事があります。
すると、下記のように先に作成されたrubyのifの記事が取得されます。
javascriptのifの記事は取得されません。(find_byはヒットしたレコードのうち初めの一件を返すメソッドであるからです。)

shell
1
2
3
4
5
6
7
Blog.find_by(url_title: 'if')

=> #<Blog:0x007ff9172b9ba8
 id: 172,
 title: "if文を使っての条件分岐を徹底解説!",
 url_title: "if",
 category_id: 2(rubyのカテゴリー)

Blog.whereで複数出てくるか確認してみます。

shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
 Blog.where(url_title: 'if')

=> [#<Blog:0x007ff9172cc4d8
 id: 172,
 title: "if文を使っての条件分岐を徹底解説!",
 url_title: "if",
 category_id: 2(rubyのカテゴリー),
#<Blog:0x007ff9172cc398
  id: 1343,
  title: "javascriptのif",
  url_title: "if",
  panel_name: nil,
  panel_type_id: 52,
  category_id: 42(javascriptのカテゴリー)>]

上記のようにifに関して2つのレコードが取得されてしまいます。rubyのifの記事かjavascriptのifの記事かどちらか一つの記事を表示しなければいけないので、category_idとurl_titleを指定してそれぞれどのカテゴリーのif文のレコードを取得するように書く必要があります。

shell
1
2
3
4
5
6
7
Blog.find_by(category_id: 2, url_title: 'if')

=> #<Blog:0x007ff9172b9ba8
 id: 172,
 title: "if文を使っての条件分岐を徹底解説!",
 url_title: "if",
 category_id: 2(rubyのカテゴリー)

shell
1
2
3
4
5
6
7
8
9
Blog.find_by(category_id: 42, url_title: 'if')

<Blog:0x007ff9172cc398
  id: 1343,
  title: "javascriptのif",
  url_title: "if",
  panel_name: nil,
  panel_type_id: 52,
  category_id: 42(javascriptのカテゴリー)>

それぞれurl_titleをifでfind_byした場合には、どのカテゴリーのif文が取得できるかわからないが、category_idとurl_titleを指定することで想定した一件のblogを取得したい!このようなケースでfind_byを使うことがよくあります。

アソシエーションに対して使用する場合
リンクをコピーしました

単体モデルで使いがちですが、アソシエーションに対してfind_byを使うことも良くあります。

このアソシエーションで使うケースはよくあるのですが、例えばbefore_actionでset_categoryみたいなメソッドを実行していて@categoryがある場合に使うことがあります。
イメージは下記の様な形で使うことがあります。

ruby
1
2
3
4
5
6
7
8
9
before_action :set_category

def show
  @blog = @category.blogs.find_by(url_title: 'if')
end

def set_category
  @category = Category.find(params[:category_id])
end

このparams[:category_id]に2(rubyのカテゴリー)が入っていたら、@categoryにはjavascriptのcategoryのインスタンスが入っているので、javascriptのifの記事が@blogに入る形です。

カラムが存在しない、ヒットしたものがない、例外を発生させたい場合
リンクをコピーしました

find_byメソッドを実行して該当のデータが無かったり、カラム名を間違えていたり、例外を発生させたい場合、どのように記述すれば良いのでしょうか?

検索にヒットしたものがなかった場合
リンクをコピーしました

find_byでヒットしたものがなかった場合はnilが返ってきます。

shell
1
2
3
Blog.find_by(url_title: 'ifhogehoge')

=> nil

該当のカラムが存在しない場合
リンクをコピーしました

該当のカラムが存在しない場合は、エラーが返ってきます。

shell
1
2
Blog.find_by(url_titleaaa: 'if')
ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'blogs.url_titleaaa' in 'where clause'

例外を発生させたい場合
リンクをコピーしました

レコードが絶対見つかる場合を想定しているのに、万が一レコードが見つからない場合は想定外のrequestを送ってきていると考えられ、例外処理でエラーページを表示したい時があるとします。
そういうときはfind_byではなく、find_by!を使います。

通常のfind_byだとヒットしたものがなかった場合はnilが返ってくるが、find_by!を使うと例外を発生させられることが出来ます。

shell
1
2
3
Blog.find_by!(url_title: 'hogehoge')

ActiveRecord::RecordNotFound: Couldn't find Blog

findメソッドとの違い
リンクをコピーしました

find_byメソッドはid以外の検索で使用するのに対し、findメソッドはidのみしか検索できません。返り値は単数ならインスタンスが返ってきます。

shell
1
2
3
4
5
6
7
8
9
10
11
User.find(1)

=> #<User:0x007fe080f0ecf0
 id: 1,
 email: "programan@gmail.com",
 name: "programan",
 job_id: 1,
 sex: 0,
 age: 25, tall: 175.0, weight: 68.0,
 created_at: Fri, 14 Dec 2018 01:21:17 UTC +00:00,
 updated_at: Fri, 14 Dec 2018 01:21:17 UTC +00:00>

id以外で検索した場合
リンクをコピーしました

もしid以外で検索するとエラーが出てしまいます。

shell
1
2
3
User.find(name: 1)

ActiveRecord::RecordNotFound: Couldn't find User with 'id'={:name=>1}

配列で指定した場合
リンクをコピーしました

単数ならインスタンスで返ってきますが、配列の場合だと配列で返ってきます。

shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
User.find([1,2])

=> [#<User:0x007fe080f0ecf0
 id: 1,
 email: "programan@gmail.com",
 name: "programan",
 job_id: 1,
 sex: 0,
 age: 25, tall: 175.0, weight: 68.0,
 created_at: Fri, 14 Dec 2018 01:21:17 UTC +00:00,
 updated_at: Fri, 14 Dec 2018 01:21:17 UTC +00:00>,
  id: 2,
  email: "programan_father@gmail.com",
  name: "programan_father",
  job_id: 2,
  sex: 0,
  age: 58,
  tall: 173.0,
  weight: 60.0,
  created_at: Fri, 19 Jul 2019 11:24:34 UTC +00:00,
  updated_at: Fri, 19 Jul 2019 11:24:34 UTC +00:00>]

find_byでid検索した場合
リンクをコピーしました

shell
1
2
3
4
5
6
7
8
9
10
11
User.find_by(id: 1)

=> #<User:0x007fe080f0ecf0
 id: 1,
 email: "programan@gmail.com",
 name: "programan",
 job_id: 1,
 sex: 0,
 age: 25, tall: 175.0, weight: 68.0,
 created_at: Fri, 14 Dec 2018 01:21:17 UTC +00:00,
 updated_at: Fri, 14 Dec 2018 01:21:17 UTC +00:00>

インスタンスが返ってきます。findと一緒です。ただfindはid検索専用のメソッドなので、わざわざfind_byを使うことはないです。基本的にidの検索の場合はfindメソッドを使用して、id以外の検索の場合でuniqueなレコードを取得したい際にfind_byメソッドを使用します。

findメソッドの詳しい具体的な使い方はfindメソッドを徹底解説をご覧ください。

whereメソッドとの違い
リンクをコピーしました

find_byメソッドとwhereメソッドの一番の違いは、find_byメソッドの返り値がインスタンスであるのに対し、whereメソッドの返り値は配列であるということです。返り値がどう違うか見ましょう。

find_byメソッドとの返り値の違い
リンクをコピーしました

shell
1
2
3
4
5
6
7
8
9
10
11
12
13
User.where(name: 'programan')

=> [#<User:0x007fe07e36c480 
id: 1, 
email: "programan@gmail.com", 
name: "programan", 
job_id: 1, 
sex: 0,
age: 25,
tall: 175.0,
weight: 68.0,
created_at: Fri, 19 Jul 2019 11:24:34 UTC +00:00,
updated_at: Fri, 19 Jul 2019 11:24:34 UTC +00:00>]

一件しかヒットしなくても配列が返ってきます。

メソッドチェーンでfind_byメソッドを使う場合
リンクをコピーしました

whereの返り値に対してチェーンしてfind_byメソッドを書くことも出来ます。
その場合はand検索になります。

下記のサンプルコードの場合だとnameカラムがprogramanであるかつageが25のレコードを一件取得するというふうになります。

shell
1
2
3
4
5
6
7
8
9
10
11
12
13
User.where(name: 'programan').find_by(age: 25)

=> [#<User:0x007fe07e36c480 
id: 1, 
email: "programan@gmail.com", 
name: "programan", 
job_id: 1, 
sex: 0,
age: 25,
tall: 175.0,
weight: 68.0,
created_at: Fri, 19 Jul 2019 11:24:34 UTC +00:00,
updated_at: Fri, 19 Jul 2019 11:24:34 UTC +00:00>]

whereメソッドの詳しい具体的な使い方はwhereメソッドを徹底解説をご覧ください。

まとめ

  • find_byメソッドとは検索条件にヒットした初めの一件を返すメソッドです。
  • 返り値はインスタンスです。
  • uniqueなカラムや複合uniqueなカラムに対して使用します。