【Rails】tryメソッドとtry!メソッドと&.演算子の違いを分りやすく説明します。

Rails

tryメソッドとは

実行したオブジェクトに指定したメソッドがあればメソッドの返り値を返し、メソッドがなければnilを返すメソッドです。

tryメソッドの使い方
リンクをコピーしました

コンソール | tryメソッドの構文
1
オブジェクト.try(:メソッド名)
コンソール | 使い方の例
1
2
3
4
5
6
7
8
9
10
11
12
13
#「emailカラム」が存在する場合
User.first.try(:email)
=> "programan@gmail.com"

nil.try(:email)
=> nil

# 以下hogehogeメソッドが存在しない場合
User.first.hogehoge
NoMethodError: undefined method 'hogehoge' for #<User:0x007fea0af846d8>

User.first.try(:hogehoge)
=> nil

tryメソッドは指定したメソッドが存在すればそのメソッドの返り値を返し、もし存在しなければnilを返すメソッドです。

このtryメソッドを使うケースは以下の二つです。

  1. 指定したメソッドがない場合にエラーを起こさせたくない
  2. tryメソッドを実行するオブジェクトがnilだった場合にエラーを起こさせたくない

上記の例を見るとわかる通り、hogehogeメソッドがない場合でもnilを返します。普通であれば、NoMethodErrorを起こすのですが、tryメソッドがあればエラーを起こさずに処理できます。

そして実行するオブジェクトがnilの場合でもエラーを起こしません。

例えば下記のようにhogehogeという名前のuserのemailを取得したいとします。

コンソール | オブジェクトがnilの場合
1
2
3
4
User.find_by('name like ?', '%hogehoge%')
=> nil
User.find_by('name like ?', '%hogehoge%').try(:email)
=> nil

もしhogehogeという名前のユーザーがいなくてnilが返ってきても、tryを使えばエラーが起きずにnilを返します。

そしてhogehogeというユーザーがいれば、そのユーザーのemailを返すことができます。

find_byメソッドについて知らない方は、世界で一番詳しいfind_byメソッドのいろいろな使い方を参考にしてください。

hogehogeがいた場合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
User.create(email: 'hogehoge@gmail.com', name: 'hogehoge', age: 30, sex: '男性', tall: 170, weight: 70, income: 300000, job_id: 1)

=> #<User:0x007fea0a617fa0
 id: 20,
 email: "hogehoge@gmail.com",
 name: "hogehoge",
 job_id: 1,
 sex: "男性",
 age: 30,
 tall: 170,
 weight: 70,
 income: 300000,

User.find_by('name like ?', '%hogehoge%').try(:email)
=> "hogehoge@gmail.com"

この様に指定したメソッドがなかった場合や実行するオブジェクトがnilだった場合でもエラーを起こさせたくない時にtryメソッドは使います。

tryメソッドはインスタンスに対して使うことが多いですが、hashに対しても使用出来ます。hashでどの様に使うかみていきましょう。

hashに対するtryの使い方
リンクをコピーしました

hashではtryメソッドを使う場合、fetchメソッドか[]かどちらかと合わせて使います。
両方のパターンを見ていきましょう。

fetchメソッド

コンソール | fetchとtryを合わせた使い方
1
2
3
4
5
6
7
pikawaka = {url: 'https://www.pikawaka.com'}

pikawaka.try(:fetch, :url, '値がありません')
=> "https://www.pikawaka.com"

pikawaka.try(:fetch, :name, '値がありません')
=> "値がありません"
コンソール | []とtryを合わせた使い方
1
2
3
4
5
6
7
pikawaka = {url: 'https://www.pikawaka.com'}

pikawaka.try(:[], :url)
=> "https://www.pikawaka.com"

pikawaka.try(:[], :name)
=> nil

fetchメソッドはハッシュから指定したキーのバリューを取り出すメソッドです。
もし指定したキーがない場合は、第三引数(今回は値がありませんに相当します)に指定した値を返します。

この様な形でhashに対してtryメソッドを使用できます。
fetchの場合はkeyがなければ、値がありませんと指定した値を返し:[]の場合はnilを返します。
それぞれkeyがあればそのkeyのvalueを返します。

ただhashに対してtryメソッドを使うメリットはあまりないと思ってます。
tryを使わずにvalueを取り出す例を見てみましょう。

コンソール | tryを使わない例
1
2
3
4
5
6
7
8
9
10
11
12
13
pikawaka = {url: 'https://www.pikawaka.com'}

pikawaka.fetch(:url, '値がありません')
=> "https://www.pikawaka.com"

pikawaka.fetch(:name, '値がありません')
=> '値がありません'

pikawaka[:url]
=> "https://www.pikawaka.com"

pikawaka[:name]
=> nil

結局hashで使えるfetch(:key, 'keyがなかった場合の値')と[:key]がtryと同じ挙動なので、hashに対してtryを使う必要は特にありません。hashでtryも使えるんだな〜くらいの認識でいてもらって、hashでtryメソッドを使っているコードを見かけたら、tryを消した形でリファクタリングできると覚えておきましょう!

tryをチェインで使用する場合の注意点
リンクをコピーしました

tryメソッドをchainで使用する場合には、使い方を間違えればエラーが起きる可能性があるので1点注意が必要です。それは、nilに対してメソッドを実行しない様にすることです。

コンソール | tryをchainで使う場合に注意すること
1
2
3
4
5
6
7
8
9
10
11
# nouserというユーザーは存在しないのでnilが返る
User.find_by('name like ?', '%nouser%')
=> nil

# nouserというユーザーは存在しないのでnilが返り、そのnilに対してtry(:name)を実行してnilがまた返るがtryを実行していないsizeメソッドはエラーが出る
User.find_by('name like ?', '%nouser%').try(:name).size
NoMethodError: undefined method 'size' for nil:NilClass

# try(:name)を実行してnilが返り値として返り、そのnilにtryをsizeメソッドで実行してるのでエラーが出ない
User.find_by('name like ?', '%nouser%').try(:name).try(:size)
=> nil

この様にtryをchainして使う場合はchainしたあとのメソッドにもtryを使う様にしましょう。
もしメソッドを実行するオブジェクトがnilであった場合にtryをつけていなければNoMethodErrorになります。
今回はsizeメソッドを実行するオブジェクトがnilだったので、エラーが起きました。
tryを使うケースはエラーを起こしたくないという理由が多いので、エラーを起こさずにchainを使う場合はchain先にもtryを使う様にしましょう。

チェインについて知らない方はメソッドチェインについて解説の記事を参照ください。

ピカわかマークポイント

  1. tryメソッドは指定したメソッドがあればその返り値を返し、なければnilを返す
  2. hashにもtryメソッドは使えるが、基本的にfetchや:[]でリファクタリングできる
  3. tryをチェインする際には、chain先にもtryを使う様にする

try!メソッドの使い方
リンクをコピーしました

try!メソッドは先ほど説明したtryメソッドと違って、存在しないメソッドを指定した場合にはNoMethodErrorを起こしてしまいます。

コンソール | try!メソッドの構文
1
オブジェクト.try!(:メソッド名)
コンソール | 使い方の例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
User.first.try!(:email)

=> #<User:0x007f9a5bd0e060
 id: 1,
 email: "programan@gmail.com">

User.first.hogehoge
NoMethodError: undefined method 'hogehoge' for #<User:0x007fea0af846d8>

User.first.try!(:hogehoge)
NoMethodError: undefined method 'hogehoge' for #<User:0x007f8a4e4f2ab0>

 User.first.try(:hogehoge)
=> nil

nil.try!(:email)
=> nil

一見エラーを起こすならば、エラーを起こさないtryメソッドの方が使い勝手が良いと思う人がいるかもしれません。

確かにエラーを起こさないtryメソッドは便利に感じますが、逆に言えばNoMethodErrorが出ているのに気づくことができません。実は裏側ではバグを起こしていてアプリに何かおかしいデータが表示されているのに、誰もその事に気づけない事態が発生します。またメソッド名をタイポしていてもメソッド名を間違えていることにも気付けません。

その様なバグが起こっているのに気付けない事態を起こさないために、エラーをキャッチできるtry!メソッドが生まれました。try!メソッドの特徴は以下の2つです。

  1. 指定したメソッドが存在しない場合はNoMethodErrorが起きる
  2. nilに対して実行した場合は、nilを返しエラーを起こさない

1については先ほど説明した通りです。

2のnilに対してエラーを起こさないのは、どういうケースで便利になるか見てみましょう。

コンソール | try!をnilに対して使う例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
nil.try!(:hello)
=> nil

User.find_by(name: 'programan').hello
=> "こんにちは、programanです。"

params[:name]
=> "田中"

User.find_by(name: params[:name])
=> nil

User.find_by(name: params[:name]).try!(:hello)
=> nil

User.find_by(name: params[:name]).hello
NoMethodError: undefined method hello' for nil:NilClass

nilに対して実行してもエラーが出ないということは、いろいろなところで便利になります。

例えば、検索フォームから田中というユーザーがいるか検索し、もし存在すればhelloメソッドを実行し、いなければnilを返したいという時に上記の例のように使えます。返り値はnilになるので、もし存在しなければ〇〇を返すみたいなことが簡潔に書けるので、その書き方を紹介していきます。

tryメソッドで「もし存在しなければ〇〇を返す」を簡潔に書く方法
リンクをコピーしました

もし存在しなければ〇〇を返す という書き方は2つあります。

  1. try!(:メソッド名) || 〇〇でnilの時に〇〇を返す
  2. オブジェクト.presence || 〇〇でnilの時に〇〇を返す

まず1からみていきましょう。

コンソール | try!と||を合わせて使ってみる
1
2
3
4
5
6
7
8
9
params[:name]
=> "田中"

User.find_by(name: params[:name]).try!(:hello)
=> nil

User.find_by(name: params[:name]).try!(:hello) || '田中というユーザーは存在しません'
=> "田中というユーザーは存在しません"

この様に||を使うとnilであった場合に || 以降の処理を書くことができます。この様に書くことにより田中というユーザーがいればhelloメソッドを実行し、もし存在しなければ〇〇を返すということが簡単にできる様になりました。

|| の詳しい動作については||の使い方を説明を参照ください。

またnilの場合に || 以降の処理を実行するので、tryメソッドでももちろん使えます。

コンソール | tryと||を合わせて使ってみる
1
2
User.find_by(name: params[:name]).try(:hello) || '田中というユーザーは存在しません'
=> "田中というユーザーは存在しません"

「2.オブジェクト.presence || 〇〇でnilの時に〇〇を返す」に関してはtry!メソッドを使っていないのですが、tryと||を合わせて使う方法と同様に大変便利なメソッドなので、紹介しておきます。

コンソール | try!をpresenceメソッドでリファクタリングした方法
1
2
3
4
5
6
7
8
9
10
11
 User.find_by(name: 'programan').presence || 'programanというユーザーは存在しません'
=> #<User:0x007f9a5caee950
 id: 1,
 email: "programan@gmail.com",
 name: "programan",

params[:name]
=> "田中">

User.find_by(name: params[:name]).presence || '田中というユーザーは存在しません'
=> "田中というユーザーは存在しません"

presenceメソッドもtryメソッドと似てまして、オブジェクト.presence || 〇〇という書き方でオブジェクトが存在すればオブジェクトを返し、存在しなければ〇〇を返すということができます。presenceメソッドについて知らない方は

presenceメソッドを使ってpresent?メソッドのコードをリファクタリングしよう!に詳しく使い方が載っているので参考にしてもらえればと思います。

presenceメソッドは、オブジェクト.presence || 〇〇という書き方でオブジェクトが存在すればオブジェクトを返し、存在しなければ〇〇を返すという用途で使う分にはかなり便利なメソッドで私も多用しているのですが、try || 〇〇の様にメソッド名が存在すればメソッドの返り値を返し、存在しなければ〇〇を返すという用途では使えないので使い分けが必要です。

どういうことかというと

コンソール | presenceとtry!の違い
1
2
3
4
5
params[:name]
=> "田中"

User.find_by(name: params[:name]).hello.presence || '田中というユーザーは存在しません'
NoMethodError: undefined method 'hello' for nil:NilClass

田中が存在しなかった場合、nilに対してhelloメソッドを実行してしまうのでNoMethodErrorになってしまいます。

コンソール | presenceとtry!の違い
1
2
3
4
5
params[:name]
=> "田中"

User.find_by(name: params[:name]).try!(:hello).presence || '田中というユーザーは存在しません'
=> "田中というユーザーは存在しません"

この様にtryを使ってnilガードをした後にpresenceを使うとうまくいくのですが、結局これだとpresenceがなくても同じ実行になるので意味がありません。つまりオブジェクトが存在するならばpresenceメソッド、メソッド名が存在するならばtryメソッドと使い分けるのが良さそうです。

ピカわかマークポイント

  1. try!メソッドは存在しないメソッドを指定した場合、NoMethodErrorを起こす。
  2. try!メソッドはnilに対してエラーを起こさないので、try!(:メソッド名) || ○○という書き方ができる
  3. 存在チェックの対象がオブジェクトならばpresenceメソッド、メソッドならばtry!メソッドを使う

&.(ぼっち演算子)の使い方
リンクをコピーしました

&.(ぼっち演算子)について説明します。この演算子は&.の形がしゃがみ込んで見える様な形からぼっち演算子と呼ばれる様になりました。またsafe navigation演算子・Null条件演算子とも呼ばれています。いろいろ呼び方があったりしますが皆さんはこの演算子がどの様な挙動をするか既に知っています。

&.がどの様に動作するか見て見ましょう。

コンソール | &.(ぼっち演算子)の使い方
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#programanが存在する場合helloメソッドが実行される
User.find_by(name: 'programan')&.hello || 'programanというユーザーは存在しません'
=> "こんにちは、programanです。"

params[:name]
=> "田中"

#田中が存在しない場合
User.find_by(name: params[:name])&.hello || '田中というユーザーは存在しません'
=> "田中というユーザーは存在しません"

#以下programanが存在しない場合
User.find_by(name: 'programan').hoge
NoMethodError: undefined method 'hoge' for #<User:0x007ffd29f47fa8>

User.find_by(name: 'programan')&.hoge
NoMethodError: undefined method 'hoge' for #<User:0x007ffd29e7d3c0>

User.find_by(name: 'programan').try!(:hoge)
NoMethodError: undefined method 'hoge' for #<User:0x007ffd29e1c458>

上の実行結果を見てと分かるとおり、&.とtry!メソッドは同じ挙動をします。
ですが、try!メソッドよりも&.の方が便利です。&.の方が便利な理由は

  1. &.の方が記述が少なくてすっきり見える
  2. &.はRubyの言語の中で定義されている
  3. &.の方がtry!メソッドよりも実行速度が速い

以上の3つの点からtry!メソッドよりも&.の方が良いです。1つ1つ説明していきます。

&.の方が記述がtry!メソッドより記述が少なく見える理由
リンクをコピーしました

まず1の&.の方が記述が少なくてすっきり見えるについてですが、これは見たままの通りです。記述が短ければあまり思わないかもしれませんが、chainで長く書くとより差は歴然です。

コンソール | &.とtry!の違い
1
2
User.find_by(name: params[:name])&.hello&.hello2&.hello3&.hello4
User.find_by(name: params[:name]).try!(:hello).try!(:hello2).try!(:hello3).try!(:hello4)

&.はRubyの言語の中で定義されている
リンクをコピーしました

Ruby 2.3.0から&.(ぼっち演算子)がRubyに導入されました。Ruby 2.3.0以上のバージョンなら&.を使うことができます。対してtry!メソッドはActiveSupportで定義されているメソッドになります。

ActiveSupportはRailsで定義されていて便利なメソッドがたくさんありますが、Railsを導入していない限りはtry!メソッドは使えないのでRubyをインストールしているだけで使える&.演算子の方がいつでも使えるという点で便利です。

try、try!、&.の速度を比べてみよう
リンクをコピーしました

&.の方がtry!メソッドよりも実行速度が速いです。実際にそれぞれtryメソッド、try!メソッド、&.演算子を同じ処理で実行して、ベンチマークで速度を比べて見ましょう。

コンソール | &. try! tryの速度の違い
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Benchmark.bm 10000 do |r|
   r.report "&." do
     user&.hello
   end
   r.report "try!メソッド" do
     user.try!(:hello)
   end
   r.report "tryメソッド" do
     user.try(:hello)
   end
 end

=> [@label="&.演算子", @real=1.4000001101521775e-05,
      @label="try!メソッド", @real=3.300000025774352e-05,
      @label="tryメソッド", @real=5.0999999075429514e-05]

ダントツで&.演算子が速いことが分りましたね。
tryメソッドが一番遅いです。
これからはtry!メソッドで書いてあるモノは全て&.演算子で書き換えましょう。そうすると記述もすっきり見えて、処理も早くなるので、try!メソッドのコードを書いている人はすぐに&.演算子でリファクタリングしましょう。

まとめ
  • tryメソッドは、メソッドが存在すればメソッドの返り値を返し、存在しなければnilを返す
  • try!メソッドは、存在しないメソッドを指定すればNoMethodErrorを返す
  • メソッドの存在確認をしたいならばtry!メソッドを使う
  • オブジェクトの存在確認をしたいならばpresenceメソッドを使う
  • nilの場合についての処理は || 演算子を使う様にするとすっきり書ける
  • try!メソッドは&.演算子を使ってリファクタリングする様にする