更新日:
【Rails】 「tryメソッド」「try!メソッド」「&.演算子」の違いとは?
tryメソッドとは、実行したオブジェクトに指定したメソッドがあればメソッドの返り値を返し、メソッドがなければnilを返すメソッドです。
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メソッドの使い方
この章では、tryメソッドの使い方について解説します。
tryメソッドを使用するケース
tryメソッドは、指定したメソッドが存在すればそのメソッドの返り値を返し、もし存在しなければnilを返すメソッドです。
このtryメソッドを使うケースは以下の二つです。
- 指定したメソッドがない場合にエラーを起こさせたくない
- tryメソッドを実行するオブジェクトがnilだった場合にエラーを起こさせたくない
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
上記の例を見るとわかる通り、hogehogeメソッドがない場合でもnilを返します。普通であれば、NoMethodErrorを起こすのですが、tryメソッドがあればエラーを起こさずに処理できます。
そして実行するオブジェクトがnilの場合でもエラーを起こしません。mark
例えば下記のようにhogehogeという名前のuserのemailを取得したいとします。
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メソッドのいろいろな使い方を参考にしてください。
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メソッドか[]かどちらかと合わせて使います。
両方のパターンを見ていきましょう。
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, '値がありません')
=> "値がありません"
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を取り出す例を見てみましょう。
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に対してメソッドを実行しない様にすることです。
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を使う様にしましょう。
チェインについて知らない方はメソッドチェインについて解説の記事を参照ください。
try!メソッドの使い方
try!メソッドは先ほど説明したtryメソッドと違って、存在しないメソッドを指定した場合にはNoMethodErrorを起こしてしまいます。
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つです。
- 指定したメソッドが存在しない場合はNoMethodErrorが起きる
- nilに対して実行した場合は、nilを返しエラーを起こさない
1については先ほど説明した通りです。
2の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つあります。
- try!(:メソッド名) || 〇〇でnilの時に〇〇を返す
- オブジェクト.presence || 〇〇でnilの時に〇〇を返す
まず1からみていきましょう。
1
2
3
4
5
6
7
8
params[:name]
=> "田中"
User.find_by(name: params[:name]).try!(:hello)
=> nil
User.find_by(name: params[:name]).try!(:hello) || '田中というユーザーは存在しません'
=> "田中というユーザーは存在しません"
この様に||を使うとnilであった場合に || 以降の処理を書くことができます。この様に書くことにより田中というユーザーがいればhelloメソッドを実行し、もし存在しなければ〇〇を返すということが簡単にできる様になりました。
|| の詳しい動作については||の使い方を説明を参照ください。
またnilの場合に || 以降の処理を実行するので、tryメソッドでももちろん使えます。
1
2
User.find_by(name: params[:name]).try(:hello) || '田中というユーザーは存在しません'
=> "田中というユーザーは存在しません"
「2.オブジェクト.presence || 〇〇でnilの時に〇〇を返す」に関してはtry!メソッドを使っていないのですが、tryと||を合わせて使う方法と同様に大変便利なメソッドなので、紹介しておきます。
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 || 〇〇の様にメソッド名が存在すればメソッドの返り値を返し、存在しなければ〇〇を返すという用途では使えないので使い分けが必要です。
どういうことかというと
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になってしまいます。
1
2
3
4
5
params[:name]
=> "田中"
User.find_by(name: params[:name]).try!(:hello).presence || '田中というユーザーは存在しません'
=> "田中というユーザーは存在しません"
この様にtryを使ってnilガードをした後にpresenceを使うとうまくいくのですが、結局これだとpresenceがなくても同じ実行になるので意味がありません。つまりオブジェクトが存在するならば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!メソッドよりも&.の方が便利です。&.の方が便利な理由は
- &.の方が記述が少なくてすっきり見える
- &.はRubyの言語の中で定義されている
- &.の方がtry!メソッドよりも実行速度が速い
以上の3つの点からtry!メソッドよりも&.の方が良いです。1つ1つ説明していきます。
&.の方が記述がtry!メソッドより記述が少なく見える理由
まず1の&.の方が記述が少なくてすっきり見えるについてですが、これは見たままの通りです。記述が短ければあまり思わないかもしれませんが、chainで長く書くとより差は歴然です。
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をインストールしているだけで使える&.演算子の方がいつでも使えるという点で便利です。
ActiveSupportについては、こちらの参考書でも便利な使い方が解説されているのでぜひ学んでみましょう。
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を返すメソッドのことだよ
- 存在しないメソッドを指定すればNoMethodErrorを返す
- メソッドの存在確認をしたいならばtry!メソッドを使おう!