【Rails】presenceメソッドを使ってpresent?メソッドのコードをリファクタリングしよう!

Rails

presenceメソッドとは

presenceメソッドを使用したオブジェクトが存在すればそのオブジェクト自身を返し、存在しなければnilを返すメソッドです。

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

presenceメソッドの構文
1
オブジェクト.presence
使い方の例
1
2
3
4
5
6
7
name = 'programan'
name.presence
=> "programan"

name = ''
name.presence
=> nil

presenceメソッドはActiveSupportで定義されているメソッドです。

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

出力結果: num10または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

出力結果: num10または50または100のうちのどれかです。

つまりnilやfalseの場合は、|| 演算子は右の式に移ります。

nilとfalseを使った場合
1
2
score = nil || false || 10
=> 10

nil, falseは真と判定されないので、scoreに10が入りました。

この考え方を応用すると、オブジェクトがnilだった場合はこの値を返すということが簡潔に行えます。

実際の例を見てみましょう。

新規ユーザーを作成するviewのフォームから、収入の項目を空で送信した場合
incomeカラム(収入)にvalidationをかけているので、エラーが起きてしまいます。

下記は動画になります。

presenceメソッドの動画1

この様に未入力で送信されたとき、とりあえず収入を0でも良いから値を入れたいという時にparams[:user][:income].presence || 0とすると、フォームで入力された値が入っていればフォームで入力した値を返し、もし空で送られていれば0を返すということができる様になります。

下記は動画になります。

presenceメソッドの動画2

フォームに収入が入力されている場合はもちろん
params[:user][:income].presence || 0でフォームに入力した値を取得できます。

下記は動画になります。

presenceメソッドの動画3

この様にpresence || ~~ という形を使うとオブジェクトがあればオブジェクトを返し、もしなければ別の値を返り値に設定することが簡単になるので、ぜひ活用しましょう!

ピカわかマークポイント

  1. presenceメソッドはレシーバーの値があればレシーバーの値を返し、値がなければnilを返す
  2. || 演算子は左から順番にその式がtrueであるかを判定していて、falseが出れば、一つ右の式をtrueか判定してとtrueが出るまで繰り返す
  3. presence || ~~ という形をとるとレシーバーのオブジェクトがnilであった場合の返り値を|| 返したい値で設定できる

present?メソッドとの違いについて
リンクをコピーしました

presenceメソッドとpresent?メソッドの違いについて理解しましょう。

present?メソッドについて(falseの場合)
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を返すメソッドです。

present?メソッドについて(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)を返すメソッドです。
この真偽値を返すメソッドをどの様にコード上で使うかみてみましょう。

present?メソッドの使い方(if文の場合)
1
2
3
4
5
6
string = 'hogehoge'
if string.present?
  puts string
else
  puts 'stringは空文字です。'
end

この様に変数に値があれば〇〇、なければ〇〇という用途でif文と併用してpresent?メソッドはよく使用します。
また三項演算子でもpresent?メソッドはよく使います。

三項演算子について知らない人は、三項演算子を徹底解説!の記事に使い方が詳しく書いているのでご覧ください。

present?メソッドの使い方(三項演算子の場合)
1
2
string = 'hogehoge'
string.present? ? string : 'stringは空文字です。'

三項演算子でこの様に書き換えることができます。こういう書き方をしているコードはよく見かけますが、この書き方は全てpresenceメソッドと || 演算子の併用した書き方で綺麗で分かりやすいコードにリファクタリングすることができます。

present?メソッドをpresenceメソッドでリファクタリングしてみよう
リンクをコピーしました

リファクタリングする前にもう一度presenceメソッドがどの様に定義されているか見直しましょう。

ActiveSupport
1
2
3
def presence
  self if present?
end

presenceメソッドはpresent?がtrueな場合、つまり何かしらレシーバーに値が入っていればレシーバー自体を返し、もしレシーバーが空であればnilを返すメソッドです。そしてオブジェクト.presence || ~~ という形を使うとオブジェクトがあればオブジェクトを返し、もしなければ~~を返すことが出来ると説明しました。

この特性を活かすと

present?メソッドの使い方(三項演算子の場合)
1
2
string = 'hogehoge'
string.present? ? string : 'stringは空文字です。'

このコードを

present?メソッドをpresenceメソッドに書き換えた場合(三項演算子)
1
2
string = 'hogehoge'
string.presence || 'stringは空文字です。'

この様にリファクタリングすることができます。今回は短いコードなので、これがどれだけ簡潔になっているか分かりにくいかもしれないですが、数が多くなればなるほどより簡潔になっていることがわかります。

present?メソッドの使い方(if文の場合)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
=> 黒色

このコードを下記にリファクタリングできます。

present?メソッドをpresenceメソッドに書き換えた場合(||演算子)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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?を使って三項演算子で定義しているコードがある人は

present?メソッドの使い方(三項演算子の場合)
1
オブジェクト.present? ? オブジェクト : 'オブジェクトはありません'

このコードを

present?メソッドをpresenceメソッドに書き換えた場合(三項演算子)
1
オブジェクト.presence || 'オブジェクトはありません'

この様なコードに書き換えましょう。

そしてif elsif present?を使ってる記述の人は

present?メソッドの使い方(if文の場合)
1
2
3
4
5
6
7
if オブジェクト1.present?
  'オブジェクト1'
elsif オブジェクト2.present?
  'オブジェクト2'
elsif オブジェクト3.present?
  'オブジェクト3'
end

このコードを

present?メソッドをpresenceメソッドに書き換えた場合(||演算子)
1
オブジェクト1.presence || オブジェクト2.presence || オブジェクト3.presence

この様にリファクタリングする様にしましょう。そうすればあなたの書いているコードはすごくスッキリして見やすいコードになります!

ピカわかマークポイント

  1. present?を使っているif文はpresenceでリファクタリング
  2. present?を使っている三項演算子はpresenceでリファクタリング

tryメソッドとの違いについて
リンクをコピーしました

presenceメソッドとの違いを見る前に、まず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.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メソッドと同様、下記の様に || 演算子と併用できます。

tryメソッドと || 演算子を併用する方法
1
オブジェクト.try(:メソッド名) || ~~

tryメソッドはnilガードが出来るので、基本的にNoMethodErrorを起こしません。存在しないメソッドを実行してもエラーが出ないので、エラーが出なくて便利と思うかもしれません。

ですがNoMethodErrorをキャッチせずにnilを出して処理するのは、エラーが起こっていることに気づかずにアプリケーションが動いているので、アプリにバグが出ているのに気付いていない可能性が非常に大きくなります。

そんな時NoMethodErrorを起こした時にエラーを起こそうと作られたのが、try!メソッドになります。

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を吐かないことです。

nilがレシーバーの場合
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を吐かないということで

nilがレシーバーで || 演算子と併用した場合
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!よりも&.の方がよく見かける気がします。ちなみに&.(ぼっち演算子)と呼ばれる由来は人が膝を抱えてふさぎ込んでいるように見えることからの様です。

ピカわかマークポイント

  1. tryメソッドはtry(:メソッド名)があればオブジェクト自身を、なければnilを返す
  2. try!メソッドはtry!(:メソッド名)があればオブジェクト自身を、なければNoMethodErrorを返す
  3. nilに対してtry!を実行してもNoMethodErrorにはならない
  4. try! || ~~を併用して使用できる
  5. &.(ぼっち演算子)はtry!と同様の動きをする

上記で説明した通り、try!メソッドはtryメソッドよりもエラーをキャッチできてより利便性が高くなったメソッドです。ですからpresenceメソッドと動作の違いを比べるのはtry!メソッドにして二つのメソッドがどの様に違うか見比べてみましょう。

try!メソッドとpresenceメソッドを比べてみよう
リンクをコピーしました

二つのメソッドとの違いの見比べ方ですが、下記の違いで見比べていきたいと思います。

  1. nilに対して実行した場合の返り値の違い
  2. 空文字に対して実行した場合の返り値の違い
  3. 速度の違い

まず1のnilに対しての違いを見てみましょう。

オブジェクトがnilの場合に比べてみる
1
2
3
4
nil.try!(:name)
=> nil
nil.presence
=> nil

nilに対して実行した場合は、結果は同じですね。

それでは2. 空文字に対して実行した場合の返り値の違いを見てみましょう。

中身が空の場合(try!の場合)
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メソッドで実行すればどうなるでしょうか?

中身が空の場合(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メソッドを使うとできるので大変便利です。

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メソッドの方が早くなると思われます。

ピカわかマークポイント

  1. try!メソッド、presenceメソッド共にnilに対しての返り値はnilになる。(nilガードができる)
  2. try!(:メソッド名)の返り値が""の場合、空文字が返る
  3. "".presenceの場合、nilが返る
  4. 空文字をnilと返さないので、presenceメソッドの方が便利
  5. 速度はpresenceの方が早い(数十万回と実行する場合は明らかな差が出そう)

try!メソッドを使っていればpresenceメソッドに書き換えられるケースが多いので、try!メソッドを使っている人はメリットの多いpresenceメソッドに書き換える様にしましょう。

まとめ

・presenceメソッドはレシーバーの値があればレシーバーの値を返し、値がなければnilを返す

・presence || ~~ という形をとるとレシーバーのオブジェクトがnilであった場合の返り値を|| 返したい値で設定できる

・presenceメソッドでpresent?メソッドとtry、try!メソッドの記述のコードをリファクタリングすることができる