更新日:
【Rails】 presenceメソッドの便利な使い方やリファクタリング方法
presenceメソッドとは、presenceメソッドを使用したオブジェクトが存在すればそのオブジェクト自身を返し、存在しなければnilを返すメソッドです。
1
オブジェクト.presence
1
2
3
4
5
6
7
name = 'programan'
name.presence
=> "programan"
name = ''
name.presence
=> nil
presenceメソッドとは
この章では、presenceメソッドの基本的な使い方からpresenceを使ったリファクタリングの方法まで1つ1つ丁寧に解説します。
presenceメソッドの基本的な使い方
1
オブジェクト.presence
1
2
3
4
5
6
7
name = 'programan'
name.presence
=> "programan"
name = ''
name.presence
=> nil
presenceメソッドはActiveSupportで定義されているメソッドです。
1
2
3
def presence
self if present?
end
上記の様に定義されており、present?がtrueの時にオブジェクト自身(self)を返します。そしてpresent?がfalseの場合は、nilが返ります。
上記の例題でいうとname = 'programan'
の場合はpresent?
はtrue
になるので、'programan'
が返り、name = ''
の場合はpresent?
がfalse
なのでnil
が返ったということになります。
present?がfalseでnilが返った場合に || 演算子と併用して書けば、かなり簡潔にコードが書けます。次はpresenceメソッドと || 演算子の併用する場合を解説します。
presence || ~~ の形で簡潔に書いてみよう
|| 演算子についてまず簡単におさらいします。
1
2
3
4
5
6
num = 100
if num == 10 || num == 50 || num == 100
puts 'numは10または50または100のうちのどれかです。'
end
出力結果: numは10または50または100のうちのどれかです。
このようにor的な形で || 演算子を使うことがよくあります。
なぜorの様に動くかというと、 || 演算子は左から順番にその式がtrueであるかを判定していて、falseが出れば一つ右の式をtrueか判定して、falseが出ればまた一つ右の式をtrueか判定してと繰り返し、trueが出ればその式は真になるという考え方です。
文章での説明は難しいのでコードで見て理解しましょう。
1
2
3
4
5
6
num = 100
if num == 10(/numは10ではないのでfalse、右の式が判定される/) || num == 50(/numは50ではないのでfalse、右の式が判定される/) || num == 100(/true/)
puts 'numは10または50または100のうちのどれかです。'
end
出力結果: numは10または50または100のうちのどれかです。
つまりnilやfalseの場合は、|| 演算子は右の式に移ります。
1
2
score = nil || false || 10
=> 10
nil, falseは真と判定されないので、scoreに10が入りました。
この考え方を応用すると、オブジェクトがnilだった場合はこの値を返すということが簡潔に行えます。
実際の例を見てみましょう。
新規ユーザーを作成するviewのフォームから、収入の項目を空で送信した場合
incomeカラム(収入)にvalidationをかけているので、エラーが起きてしまいます。
このように未入力で送信されたとき、とりあえず収入を0でも良いから値を入れたいという時にparams[:user][:income].presence || 0とすると、フォームで入力された値が入っていればフォームで入力した値を返し、もし空で送られていれば0を返すということができる様になります。
フォームに収入が入力されている場合はもちろん
params[:user][:income].presence || 0でフォームに入力した値を取得できます。
このようにpresence || ~~
という形を使うとオブジェクトがあればオブジェクトを返し、もしなければ別の値を返り値に設定することが簡単になるので、ぜひ活用しましょう!
またRubyの用語やコードの読み方に不安があるという方は、こちらの書籍を手元に置いとくと良いでしょう。
presenceメソッドとpresent?メソッドの違い
presenceメソッドとpresent?メソッドの違いについて理解しましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
value = nil
value.present?
=> false
array = []
array.present?
=> false
hash = {}
hash.present?
=> false
string = ''
string.present?
=> false
false.present?
=> false
presenceメソッドはレシーバーの値があればレシーバーの値を返し、値がなければnilを返しました。
しかし、present?メソッドは「空配列・空ハッシュ・空文字」といった器はあるけど、中身が空のものは全てfalseで返すメソッドです。
そして、数字の0でも何かしら文字が入っているとtrueを返すメソッドです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
num = 0
num.present?
=> true
string = 'hogehoge'
string.present?
=> true
array = ['hogehoge']
array.present?
=> true
hash = {value: 'hogehoge'}
hash.present?
=> true
true.present?
=> true
つまり値が入っていればtrue、値が空であればfalseを返す真偽値(true, false)を返すメソッドです。
この真偽値を返すメソッドをどの様にコード上で使うかみてみましょう。
1
2
3
4
5
6
string = 'hogehoge'
if string.present?
puts string
else
puts 'stringは空文字です。'
end
このように変数に値があれば〇〇、なければ〇〇という用途でif文と併用してpresent?メソッドはよく使用します。
また三項演算子でもpresent?メソッドはよく使います。三項演算子について知らない人は、「三項演算子の使い方」の記事を参考にしてください。
1
2
string = 'hogehoge'
string.present? ? string : 'stringは空文字です。'
三項演算子でこのように書き換えることができます。こういう書き方をしているコードはよく見かけますが、この書き方は全てpresenceメソッドと || 演算子の併用した書き方で綺麗で分かりやすいコードにリファクタリングすることができます。
present?メソッドをpresenceメソッドでリファクタリングしてみよう
リファクタリングする前にもう一度presenceメソッドがどの様に定義されているか見直しましょう。
1
2
3
def presence
self if present?
end
presenceメソッドはpresent?がtrueな場合、つまり何かしらレシーバーに値が入っていればレシーバー自体を返し、もしレシーバーが空であればnilを返すメソッドです。
そして、オブジェクト.presence || ~~
という形を使うとオブジェクトがあればオブジェクトを返し、もしなければ~~を返すことが出来ると説明しました。
この特性を以下のコードに活かしてみます。
1
2
string = 'hogehoge'
string.present? ? string : 'stringは空文字です。'
上記のコードを以下のようにこのようにリファクタリングすることができます。
1
2
string = 'hogehoge'
string.presence || 'stringは空文字です。'
今回は短いコードなので、これがどれだけ簡潔になっているか分かりにくいかもしれないですが、数が多くなればなるほどより簡潔になります。
さらに以下のサンプルコードをリファクタリングしてみます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def get_color(blue, yellow, red, white, black)
if blue.present?
'青'
elsif yellow.present?
'黄色'
elsif red.present?
'赤色'
elsif white.present?
'白色'
elsif black.present?
'黒色'
end
end
blue = ''
yellow = ''
red = ''
white = ''
black = '黒色'
color = get_color(blue, yellow, red, white, black)
puts color
=> 黒色
上記のコードは、以下のようにリファクタリングできます。
1
2
3
4
5
6
7
8
9
10
11
12
13
def get_color(blue, yellow, red, white, black)
blue.presence || yellow.presence || red.presence || white.presence || black.presence
end
blue = ''
yellow = ''
red = ''
white = ''
black = '黒色'
color = get_color(blue, yellow, red, white, black)
puts color
=> 黒色
かなり綺麗で簡潔なコードになったと思いませんか?
present?を使って三項演算子で定義しているコードの場合も確認してみましょう。
1
オブジェクト.present? ? オブジェクト : 'オブジェクトはありません'
上記のコードを以下のように書き換えます。
1
オブジェクト.presence || 'オブジェクトはありません'
そして、if elsif present?
を使ってる場合はどうでしょうか。以下のコードを使ってみます。
1
2
3
4
5
6
7
if オブジェクト1.present?
'オブジェクト1'
elsif オブジェクト2.present?
'オブジェクト2'
elsif オブジェクト3.present?
'オブジェクト3'
end
上記のコードを以下のようにリファクタリングします。
1
オブジェクト1.presence || オブジェクト2.presence || オブジェクト3.presence
このようにpresenceメソッドを上手く活用すると、スッキリして見やすいコードになります!
presenceメソッドとtryメソッドの違い
presenceメソッドとの違いを見る前に、まずtryメソッドとはどの様なメソッドであるか知りましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
job = Job.first
=> #<Job:0x007fea0b7c5dd8
id: 1,
name: "プログラマン講師">
job.try(:name)
=> "プログラマン講師"
job.try(:hoge)
=> nil
job.try(:hoge) || 'hogeメソッドはありません。'
=> "hogeメソッドはありません。"
nil.try(:name)
=> nil
tryメソッドはオブジェクト.try(:メソッド名)の返り値があればその返り値を返し、もし返り値がなければnilを返すメソッドです。このメソッドの最大の特徴はnilガードが出来る点にあります。
例えばnil.try(:name)としてもエラーが出ずにnilを返すので、エラーが起きないことがtryメソッドの最大の特徴になります。オブジェクト.try(:メソッド名)があればその返り値を返し、もしなければnilを返すという点でpresenceメソッドとよく似たメソッドになります。
もしなければnilを返すのでpresenceメソッドと同様、下記の様に || 演算子と併用できます。
1
オブジェクト.try(:メソッド名) || ~~
tryメソッドはnilガードが出来るので、基本的にNoMethodErrorを起こしません。存在しないメソッドを実行してもエラーが出ないので、エラーが出なくて便利と思うかもしれません。
ですがNoMethodErrorをキャッチせずにnilを出して処理するのは、エラーが起こっていることに気づかずにアプリケーションが動いているので、アプリにバグが出ているのに気付いていない可能性が非常に大きくなります。
そんな時NoMethodErrorを起こした時にエラーを起こそうと作られたのが、try!メソッドになります。
try!メソッドについて
1
2
3
4
5
6
7
8
9
10
11
12
13
14
job = Job.first
=> #<Job:0x007fea0b7c5dd8
id: 1,
name: "プログラマン講師">
job.try!(:name)
=> "プログラマン講師"
job.try!(:hoge)
=> NoMethodError: undefined method 'hoge' for #<Job:0x007fea0aea33e0>
job.try!(:hoge) || 'hogeメソッドはありません。'
=> NoMethodError: undefined method 'hoge' for #<Job:0x007fea0aea33e0>
このようにtry!の場合はメソッドがなかった場合nilではなくNoMethodErrorを返すのでエラーをキャッチできます。そしてtry!メソッドのもう一つ注目して欲しいポイントがnilに対してメソッドを実行した場合にはNoMethodErrorを吐かないことです。
1
2
3
4
5
6
7
8
9
10
11
nil.try!(:hoge)
=> nil
params[:job_name]
=> "消防士"
Job.find_by(name: params[:job_name])
=> nil
Job.find_by(name: params[:job_name]).try!(:hoge)
=> nil
find_byメソッドについて知らない方はこちらの「find_byメソッドのいろいろな使い方」の記事を参考にしてください。
もし送られてきた値がDBに存在しなくてもエラーを出さずに、もし存在すればhogeメソッドを実行するということができます。nilに対しても利用できるので、NoMethodErrorをキャッチできるバージョンのtryメソッドということでかなり利便性が高くなります。
nilに対してメソッドを実行した場合にはNoMethodErrorを吐かないということで
1
2
Job.find_by(name: params[:job_name]).try!(:hoge) || '消防士は登録されておりません'
=> "消防士は登録されておりません"
このような使い方もできます。
シンプルにjobというインスタンスに指定したメソッドがないとき(Job.first.try!(:hoge)にNoMethodErrorをキャッチできるということですね。
そしてこのtry!メソッドと同様の動きをする&.(ぼっち演算子)と呼ばれるものがあります。
このtry!メソッドをぼっち演算子に書き換えることができるので、ぼっち演算子の動きも見てみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
job = Job.first
=> #<Job:0x007fea0b7c5dd8
id: 1,
name: "プログラマン講師">
job&.name
=> "プログラマン講師"
job&.hoge
=> NoMethodError: undefined method 'hoge' for #<Job:0x007fea0aea33e0>
job&.hoge || 'hogeメソッドはありません。'
=> NoMethodError: undefined method 'hoge' for #<Job:0x007fea0aea33e0>
nil&.hoge
=> nil
このようにぼっち演算子とtry!メソッドは同様の動きをします。個人的にぼっち演算子の方が記述が少なくてスマートに見えるのでtry!よりも&.の方がよく見かける気がします。ちなみに&.(ぼっち演算子)と呼ばれる由来は人が膝を抱えてふさぎ込んでいるように見えることからの様です。
上記で説明した通り、try!メソッドはtryメソッドよりもエラーをキャッチできてより利便性が高くなったメソッドです。ですからpresenceメソッドと動作の違いを比べるのはtry!メソッドにして二つのメソッドがどの様に違うか見比べてみましょう。
try!メソッドとpresenceメソッドを比べてみよう
二つのメソッドとの違いの見比べ方ですが、下記の違いで見比べていきたいと思います。
- nilに対して実行した場合の返り値の違い
- 空文字に対して実行した場合の返り値の違い
- 速度の違い
まず1のnilに対しての違いを見てみましょう。
1
2
3
4
nil.try!(:name)
=> nil
nil.presence
=> nil
nilに対して実行した場合は、結果は同じですね。
それでは2. 空文字に対して実行した場合の返り値の違いを見てみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
Job.first.update(name: '')
job = Job.first
=> #<Job:0x007fea0b7c5dd8
id: 1,
name: "">
job.try!(:name)
=> ""
job.try(:name) || '職業不明'
=> ""
jobのnameカラムの値がプログラマン講師であったのを空文字にupdateして || 演算子を併用した場合、|| 以降の職業不明ではなく元々入っている空文字が出力されています。個人的にtry!メソッドのこの動作は不便と感じていてもしnameが空であれば、職業不明と出力したいです。
それではこれをpresenceメソッドで実行すればどうなるでしょうか?
1
2
3
4
5
6
7
8
9
10
11
job = Job.first
=> #<Job:0x007fea0b7c5dd8
id: 1,
name: "">
job.name.presence
=> nil
job.name.presence || '職業不明'
=> "職業不明"
presenceメソッドの場合空文字であればpresent?がfalseになってnilが返るので、job.name.presence || '職業不明'とした場合は'職業不明'を返り値に返すことができます。
こういった点からpresenceメソッドが便利と感じていて、個人的にはpresenceメソッドを好んで使います。また送られてきたデータがDBにあるか確認して、もしなければ~~を返すということもpresenceメソッドを使うとできるので大変便利です。
1
2
3
4
5
6
7
8
params[:job_name]
=> "消防士"
Job.where(name: params[:job_name])
=> []
Job.where(name: params[:job_name]).presence || '消防士は登録されておりません'
=> "消防士は登録されておりません"
このように該当したデータがなければ || ~~を返すという風にも書けるので、presenceメソッドの方が使い勝手が良いです。
3 速度の違いについて最後に見比べてみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Benchmark.bm 10 do |r|
r.report "try!" do
Job.first.try!(:name)
end
r.report "presence" do
Job.first.name.presence
end
end
user system total real
0.000000 0.000000 0.000000 ( 0.001415) ←try!
0.000000 0.000000 0.000000 ( 0.001159) ←presence
=> [#<Benchmark::Tms:0x007fea0ac7cff8 @cstime=0.0, @cutime=0.0, @label="try!", @real=0.0014150002971291542, @stime=0.0, @total=0.0, @utime=0.0>,
#<Benchmark::Tms:0x007fea0ac76130 @cstime=0.0, @cutime=0.0, @label="presence", @real=0.0011590002104640007, @stime=0.0, @total=0.0, @utime=0.0>]
わずかにpresenceメソッドの方が早いですね!
これがバッチなどで数十万回実行する様なことがあった場合、目に見える形でpresenceメソッドの方が早くなると思われます。
try!メソッドを使っていればpresenceメソッドに書き換えられるケースが多いので、try!メソッドを使っている人はメリットの多いpresenceメソッドに書き換える様にしましょう。
この記事のまとめ
- presenceメソッドはレシーバーの値があればレシーバーの値を返し、値がなければnilを返す
presence || ~~
という形をとるとレシーバーのオブジェクトがnilであった場合の返り値を|| 返したい値で設定できる- presenceメソッドでpresent?メソッドとtry、try!メソッドの記述のコードをリファクタリングすることができる