すでにメンバーの場合は

無料会員登録

GitHubアカウントで登録 Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

登録がまだの方はこちらから

Pikawakaにログイン

GitHubアカウントでログイン Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

Rails

【Rails】 exists?メソッドの使い方~present?との違い

ぴっかちゃん
ぴっかちゃん

exists?メソッドとは、指定した条件のレコードがデータベースに存在するかどうかを真偽値で返すメソッドです。存在すればtrueを存在しなければfalseを返します。

exists?メソッドの構文
1
オブジェクト.exists?(条件)

例えば、以下のようにexists?メソッドの引数に条件を指定すると、条件にマッチするレコードがあればtrueを返し、レコードがなければfalseを返します。

コンソール | exists?メソッドの使用例
1
2
3
4
5
6
7
[1] pry(main)> User.exists?(name: '田中')
SELECT  1 AS one FROM `users` WHERE `users`.`name` = '田中' LIMIT 1
=> true

[2] pry(main)> User.exists?(name: '林')
SELECT  1 AS one FROM `users` WHERE `users`.`name` = '林' LIMIT 1
=> false

データベースの画像

上記のように、usersテーブルのnameカラムの値が"田中"のレコードは存在しますが、nameカラムの値が"林"のレコードは存在しないのでfalseを返します。また、引数の条件はハッシュ以外にも配列や整数なども使うことが出来ます。

基本的な使い方

この章では、existsメソッドの基本的な使い方について解説します。

exists?メソッドの定義方法

exists?メソッドの定義方法は、主に以下の3つの方法があります。

コンソール | exists?メソッドの定義方法
1
2
3
4
5
6
7
8
9
10
11
[1] pry(main)> User.exists?(1) # 引数に確認したいレコードの条件を指定
SELECT  1 AS one FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=> true

[2] pry(main)> User.exists?(name: '田中') # 引数に確認したいレコードの条件を指定
  User Exists (0.6ms)  SELECT  1 AS one FROM `users` WHERE `users`.`name` = '田中' LIMIT 1
=> true

[3] pry(main)> User.where(name: '田中').exists? # whereメソッドで条件を絞り込んで確認
  User Exists (0.4ms)  SELECT  1 AS one FROM `users` WHERE `users`.`name` = '田中' LIMIT 1
=> true

上記のコードは、exists?メソッドの引数に条件を渡す2つの定義方法とwhereメソッドで条件を絞り込んで、exists?メソッドでレコードがあるか確認する定義方法です。

それでは、この3つの定義方法を1つずつ順番に解説します。

指定したidのレコードを確認する

exists?メソッドの引数に整数を渡すと、指定した整数の主キーを持つレコードを確認します。

コンソール | 主キーが1のレコードの存在を確認する
1
2
3
[1] pry(main)> User.exists?(1)
SELECT  1 AS one FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=> true

指定した条件のレコードを確認する

exists?メソッドの引数にハッシュ(カラム名: '値')を渡すと、その条件に一致するレコードの存在を確認します。

コンソール | nameカラムの値が'田中'のレコードがあるか確認する
1
2
3
[2] pry(main)> User.exists?(name: '田中')
SELECT  1 AS one FROM `users` WHERE `users`.`name` = '田中' LIMIT 1
=> true

以下のコードのように、複数の要素を条件に指定することも出来ます。発行されるSQLは条件の前後にANDが使われているので、どちらの条件も満たすレコードが存在する場合、trueを返します。

コンソール | nameカラムの値が"田中"、かつageカラムの値が30のレコードがあるか確認する
1
2
3
[2] pry(main)> User.exists?(name: '斎藤', age: 30)
SELECT  1 AS one FROM `users` WHERE `users`.`name` = '斎藤' AND `users`.`age` = 30 LIMIT 1
=> true

また、以下のようにハッシュの値に配列で複数の値を渡すことも出来ます。この場合に、発行されるSQLはIN句が使われているので、どちらか一方の条件を満たすレコードが存在する場合、trueを返します。

コンソール | nameカラムの値が"林"、もしくは"斎藤"のレコードがあるか確認する
1
2
3
[2] pry(main)> User.exists?(name: ['林','斎藤'])
SELECT  1 AS one FROM `users` WHERE `users`.`name` IN ('林', '斎藤') LIMIT 1
=> true

上記のコードは、nameカラムの値が"林"、もしくは"斎藤"にマッチするレコードがあるか確認します。

以下のように、nameカラムの値が"林"のレコードは存在しませんが、"斎藤"のレコードは存在するのでtrueを返します。

usersテーブルのnameカラムの値を確認

絞り込みと併用する

exists?メソッドは、以下のコードのようにwhereメソッドと併用して使うことが出来ます。

コンソール | whereメソッドで条件を絞り込みexists?メソッドでレコードがあるか確認する
1
2
3
[3] pry(main)> User.where(name: '田中').exists?
SELECT  1 AS one FROM `users` WHERE `users`.`name` = '田中' LIMIT 1
=> true

上記のコードは、以下のexists?メソッドの引数に条件を入れた場合と同じ結果(true)を得ることが出来ます。

コンソール | nameカラムの値が'田中'のレコードがあるか確認する
1
2
3
[2] pry(main)> User.exists?(name: '田中')
SELECT  1 AS one FROM `users` WHERE `users`.`name` = '田中' LIMIT 1
=> true

発行しているSQLは同じですが、この2つのコードは、exists?メソッドの引数に条件を指定しているかwhereメソッドで条件を絞り込んでいるかが違いますね。

もう少し詳しく解説すると、前者のUser.where(name: '田中').exists?は絞り込みをかけたwhereの存在チェックを行っています。後者のUser.exists?(name: '田中')は、カラムに指定したデータのユーザーがいるか存在チェックを行っています。

複雑な条件を絞り込んで、そのレコードに対して存在チェックを行いたい場合は前者の方がよく使われます。

条件文

exists?メソッドは真偽値を返すので、if文を使って指定した条件のレコードがある場合とない場合で処理を分けることが出来ます。

if文を使う場合-->
1
2
3
4
5
if モデル名.exists?(条件)
  # 条件に指定したレコードが存在する場合の処理
else
  # 条件に指定したレコードが存在しない場合の処理
end

例えば、usersテーブルのnameカラムの値が"斎藤"のレコードが存在すれば、「斎藤という名前のユーザーは存在します。」を返し、レコードが存在しなければ「斎藤という名前のユーザーは1件も存在しません。」を返す場合は、以下のように記述します。

コンソール | if文を使う例
1
2
3
4
5
6
7
[1] pry(main)> if User.exists?(name: '斎藤')
[1] pry(main)*   '斎藤という名前のユーザーは存在します。'
[1] pry(main)* else
[1] pry(main)*   '斎藤という名前のユーザーは1件も存在しません。'
[1] pry(main)* end
SELECT  1 AS one FROM `users` WHERE `users`.`name` = '斎藤' LIMIT 1
=> "斎藤さんという名前のユーザーは存在します。"

三項演算子

先ほどのif文の例は、以下のように三項演算子に書き換えることが出来ます。

コンソール | 三項演算子を使った例
1
2
3
4
#【三項演算子の構文】 条件式 ? レコードがある場合の処理 : レコードがない場合の処理
[2] pry(main)> User.exists?(name: '斎藤') ? '斎藤さんという名前のユーザーは存在します。' : '斎藤さんという名前のユーザーは存在しません。'
SELECT  1 AS one FROM `users` WHERE `users`.`name` = '斎藤' LIMIT 1
=> "斎藤さんという名前のユーザーは存在します。"

このように、三項演算子を使うことで1行でコードをシンプルに記述することが出来ます。

後置if

trueを返した場合だけ何か処理を行う際は、後置ifで簡単に記述する事ができます。

1
2
3
4
5
# filename: コンソール | 後置ifを使った例
#【後置ifの構文】 trueの場合の処理 if 条件式
[3] pry(main)> '斎藤さんという名前のユーザーは存在します。'  if User.exists?(name: '斎藤')
SELECT  1 AS one FROM `users` WHERE `users`.`name` = '斎藤' LIMIT 1
=> "斎藤さんという名前のユーザーは存在します。"

上記は、usersテーブルにnameカラムの値が"斎藤"のレコードが存在した場合に「斎藤さんという名前のユーザーは存在します。」を実行します。

後置unless

falseを返した場合だけ何か処理を行う際は、後置unless文でシンプルに1行で記述する事ができます。

後置ifを使った例 -->
1
2
# 【後置unlessの構文】falseの場合の処理 unless 条件式
'検索したユーザーはみつかりませんした' unless User.exists?(name: params[:q])

上記のparams[:q]は、ユーザー検索で送信されたパラメーターが入ります。これを条件に指定することで、usersテーブルのnameカラムに検索したユーザー名のレコードがなければ文言を表示させる処理を行えます。

引数

exists?メソッドの引数に渡すことが出来る形式は、以下の5つあります。

形式 内容 使用例
整数(Integer) 指定した整数の主キーを持つレコードを確認する User.exists?(1)
文字列(String) 指定した文字列に対応する主キーのレコードを確認する User.exists?("1")
ハッシュ(Hash) 指定したハッシュの条件に合うレコードを確認する User.exists?(name: "斎藤")
配列(Array) 検索スタイルの条件を配列に指定する事が出来る User.exists?(['name = ?', '斎藤'])
引数なし モデルの後に引数なしでexists?メソッドを使った場合、テーブルが空か確認する User.exists?

参考: API dock exists?
それでは、1つ1つの形式を確認します。

整数(Integer)

exists?メソッドの引数に 整数を渡すと、指定した整数の主キーを持つレコードを確認します。

exists?メソッドの引数に整数を渡した場合-->
1
モデル名.exists?(整数)
コンソール | 引数に整数を渡してidのレコードを確認する
1
2
3
4
5
6
7
[1] pry(main)> User.exists?(1)
SELECT  1 AS one FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=> true

[2] pry(main)> User.exists?(100)
SELECT  1 AS one FROM `users` WHERE `users`.`id` = 100 LIMIT 1
=> false

文字列(String)

exists?メソッドの引数に 文字列を渡すと、この文字列に対応する主キーのレコードを確認します。

exists?メソッドの引数に文字列を渡した場合
1
モデル名.exists?("文字列")
1
2
3
4
5
6
7
8
# filename:コンソール | 引数に文字列を渡してidのレコードを確認する
[1] pry(main)> User.exists?("1")
SELECT  1 AS one FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=> true

[2] pry(main)> User.exists?("100")
SELECT  1 AS one FROM `users` WHERE `users`.`id` = 100 LIMIT 1
=> false

以下のような文字列を渡した場合は、主キーが0(id = 0)のレコードを確認してfalseを返します。

1
2
3
4
# filename:コンソール | 引数の文字列が主キーに対応しないためfalseを返す
[3] pry(main)> User.exists?("aaa")
SELECT  1 AS one FROM `users` WHERE `users`.`id` = 0 LIMIT 1
=> false

ハッシュ(Hash)

exists?メソッドの引数に ハッシュを渡すと、その条件に一致するレコードの存在を確認します。

exists?メソッドの引数にHashを渡した場合
1
モデル名.exists?(Hash)
1
2
3
4
# filename:コンソール | 引数に渡したHashの条件に一致するレコードがあるか確認する
[1] pry(main)> User.exists?(:name => "斎藤")
SELECT  1 AS one FROM `users` WHERE `users`.`name` = '斎藤' LIMIT 1
=> true

上記のコードは、ハッシュロケット(=>)を使った古い書き方なので、以下のようにキーをシンボルにしてハッシュロケットを省略する事が出来ます。

1
2
3
4
# filename: コンソール | キーをシンボルにしてハッシュロケットを省略する
[2] pry(main)> User.exists?(name: "斎藤")
SELECT  1 AS one FROM `users` WHERE `users`.`name` = '斎藤' LIMIT 1
=> true

ハッシュロケットやシンボルについては、ハッシュのキーにシンボルを使う方法を参照してください。

配列(Array)

exists?メソッドの引数には、配列で検索スタイルの条件を渡す事が出来ます。

exists?メソッドの引数に配列を渡した場合
1
モデル名.exists?([検索スタイル])

例えば、以下のコードのようにプレースホルダー(?)を使った検索スタイルの条件を配列で渡す事が出来ます。nameカラムの値が"斎藤"のレコードの存在を確認します。

1
2
3
4
5
# filename:コンソール | 配列にプレースホルダーを使った例
# 最初の要素の「?」に'斎藤'が入る
[1] pry(main)> User.exists?(['name = ?', '斎藤'])
SELECT  1 AS one FROM `users` WHERE (name = '斎藤') LIMIT 1
=> true

また、以下のコードのようにexists?メソッドの引数に配列を使えば、LIKE句であいまい検索をする事が出来ます。

1
2
3
4
5
6
7
8
# filename: コンソール | exists?メソッドであいまい検索をする場合
[2] pry(main)> User.exists?(['name LIKE ?', "%斎%"])
SELECT  1 AS one FROM `users` WHERE (name LIKE '%斎%') LIMIT 1
=> true

[3] pry(main)> User.exists?(['name LIKE ?', "%あ%"])
SELECT  1 AS one FROM `users` WHERE (name LIKE '%あ%') LIMIT 1
=> false

1番目のコードは、nameカラムに「斎」の値があるレコードを検索し、斎藤さんのレコードがあるのでtrueを返します。2番目のコードは、nameカラムに「あ」の値があるレコードを検索していますが、存在しないのでfalseを返します。

引数なし

以下のように、モデル名の後に引数なしでexists?メソッドを使った場合、テーブルにレコードが存在していればtrueを返し、レコードが存在していなければfalseを返します。

引数なしの場合
1
モデル名.exists?
1
2
3
4
# filename:コンソール | usersテーブルにレコードが存在するか確認する
[1] pry(main)> User.exists?
SELECT  1 AS one FROM `users` LIMIT 1
=> true

また、以下のようなwhereメソッドで条件を絞り込んで、exists?メソッドでその条件のレコードが存在するか確認するというパターンは良く利用します。

コンソール | whereメソッドと併用してexists?メソッドを使う
1
2
3
4
5
6
7
[2] pry(main)> User.where(name: '斎藤').exists?
SELECT  1 AS one FROM `users` WHERE `users`.`name` = '斎藤' LIMIT 1
=> true

[3] pry(main)> User.where(name: '遠藤').exists?
SELECT  1 AS one FROM `users` WHERE `users`.`name` = '遠藤' LIMIT 1
=> false

上記のコードは、以下のwhereメソッドを使わない場合と同様の結果(true)が返ります。

コンソール | existsメソッドのみを使用した場合
1
2
3
[1] pry(main)> User.exists?(name: '田中')
SELECT  1 AS one FROM `users` WHERE `users`.`name` = '田中' LIMIT 1
=> true

どちらも同じSQLが発行されていますが、前者の引数がないexists?は

exists?メソッドには二つの使い方があります。
引数がある場合と引数がない場合です。

実際にコードを見て比べてみましょう。

コンソール | 引数がある場合
1
2
3
[1] pry(main)> User.where(name: '田中').exists?
SELECT  1 AS one FROM `users` WHERE `users`.`name` = '田中' LIMIT 1
=> true

上記の引数がない場合は、nameが田中さんのユーザーが存在するかどうかをbooleanで返しています。これはUser.where(name: '田中')のレシーバーに対して存在するかどうかをexists?で返していることになります。

対して引数がある場合はどうでしょう?

コンソール | 引数がある場合
1
2
3
[1] pry(main)> User.where(nickname: '田中').exists?(created_at: Date.yesterday..Date.today)
  User Exists (0.7ms)  SELECT  1 AS one FROM `users` WHERE `users`.`nickname` = '田中' AND `users`.`created_at` BETWEEN '2020-06-07' AND '2020-06-08' LIMIT 1
=> false

上記の引数がある場合を比べてみると、User.where(nickname: '田中')のレシーバーのデータが昨日 ~ 今日に作成されていれば true, そうでなければfalseを返すようにしています。

つまりexists?の引数がある場合とない場合の違いは、レシーバーのオブジェクトが存在するか?と、レシーバーのオブジェクトに対してさらにexists?の引数に指定した条件のオブジェクトが存在するかの違いになります。

present?メソッドとの違い

present?メソッドは、使用したオブジェクトが空ではないか存在チェックしてくれるメソッドです。
以下のように記述する事で、present?メソッドでもレコードの存在確認をすることが出来ます。

コンソール | exists?メソッドとpresetnt?メソッドの使用例
1
2
3
4
5
6
7
[1] pry(main)> User.exists?(name: '斎藤')
SELECT  1 AS one FROM `users` WHERE `users`.`name` = '斎藤' LIMIT 1
=> true

[2] pry(main)> User.find_by(name: '斎藤').present?
SELECT  `users`.* FROM `users` WHERE `users`.`name` = '斎藤' LIMIT 1
=> true

どちらのメソッドもレコードの存在をチェックしますが、場面によっては使い分けが必要です。

present?メソッドを使う場面

present?メソッドは、以下のようにpresent?メソッドでレコードの存在確認を行った後に、インスタンスを使って何か処理をしたいときに使用します。

Controller | present?メソッドを使う場面 -->
1
2
3
4
5
6
user = User.find_by(name: params[:user][:name])
if user.present?
  redirect_to user_path(user), notice: "#{user.name}さん。のプロフィールページへようこそ"
else
  redirect_to root_path, alert: "#{params[:user][:name]}さんのプロフィールは見つかりませんでした。"
end

上記のコードでは、ユーザー検索で送信されたパラメータをfind_byメソッドで検索して、そのレコードがあれば、レコード(Userモデルのインスタンス)をuserに格納します。レコードがなければ、userにはnilが入ります。

そして、present?メソッドで存在チェックを行います。インスタンスが存在すれば、そのインスタンスを使ってユーザーのページにredirect_toメソッドで遷移させて、存在しなければルートへ遷移します。

このように、レコードの存在チェックを行った後にインスタンスを使って何か処理をする場合はexists?メソッドではなくpresent?メソッドを使います。

present?メソッドについて詳しい解説は、「present?メソッドの使い方」を参考にしてください。

exists?メソッドを使う場面

exists?メソッドは、以下のように存在チェックの後にインスタンスを使う必要がなく、レコードの存在チェックだけを行う場合に使用します。

exists?メソッドを使った場合
1
2
3
4
5
if User.exists?(name: params[:user][:name])
  "#{params[:user][:name]}さんは登録されています。"
else
  "#{params[:user][:name]}さんは未登録です。"
end

上記は、ユーザー検索で送信されたパラメータのレコードの存在チェックを行っています。
レコードが存在すれば、パラメータの名前で登録していることを出力させて、レコードが存在しなければ、未登録だということを出力します。

このように、インスタンスを使わずにレコードの存在チェックだけ行う場合はexists?メソッドを使用します。

ぴっかちゃん

これまで学習したRuby on Railsについて体系的に整理したい場合は、こちらの書籍が参考になりますよ!

応用的な使い方

この章ではexists?メソッドの応用的な使い方について解説します。

自作メソッドの作成方法

exists?メソッドを利用した自作メソッドを作成し、リファクタリングする方法を解説します。

以下は、ownersテーブルとcatsテーブルとdogsテーブルで、テーブル同士の関連付け
が行われています。

ownersテーブルとcatsテーブルとdogsテーブルの構成

この3つのテーブルに対応するOwnerモデルとCatモデルとDogモデルには、それぞれ以下のようにアソシエーションが定義されています。

app/models/owner.rb | Ownerモデルの関連付け-->
1
2
3
4
class Owner < ApplicationRecord
    has_many :cats
    has_many :dogs
end
app/models/cat.rb | Catモデルの関連付け -->
1
2
3
class Cat < ApplicationRecord
    belongs_to :owner
end
app/models/dog.rb | Dogモデルの関連付け-->
1
2
3
class Dog < ApplicationRecord
    belongs_to :owner
end

上記のアソシエーションは、「1人の飼い主(Owner)は、複数の猫/犬を飼い、1匹の猫(Cat)/犬(Dog)は、1人の飼い主に属する関係」です。アソシエーションの詳しい解説は、「アソシエーションを図解形式で理解する!」を参考にしてください。

それでは、このデータを使って解説していきます。

メソッドを作成

田中さんと加藤さんが猫を飼っているか調べたい場合は、以下のようにアソシエーションとexists?メソッドで記述することが出来ます。

コンソール | アソシエーションにexists?メソッドを使う例
1
2
3
4
5
6
7
8
9
[1] pry(main)> Owner.find_by(name: '田中').cats.exists?
SELECT  `owners`.* FROM `owners` WHERE `owners`.`name` = '田中' LIMIT 1
SELECT  1 AS one FROM `cats` WHERE `cats`.`owner_id` = 1 LIMIT 1
=> true

[2] pry(main)> Owner.find_by(name: '加藤').cats.exists?
SELECT  `owners`.* FROM `owners` WHERE `owners`.`name` = '加藤' LIMIT 1
SELECT  1 AS one FROM `cats` WHERE `cats`.`owner_id` = 4 LIMIT 1
=> false

上記のコードは、以下の画像のように、田中さんの猫のレコードは存在するのでtrueを返し、加藤さんの猫のレコードは存在しないのでfalseを返します。

ownersテーブルとcatsテーブルの構成

つまり、catsテーブルの外部キー制約のカラム(owner_id)に、飼い主の主キーのレコードが存在すればtrueを返し、レコードが存在しなければfalseを返します。

コードを再確認する -->
1
2
Owner.find_by(name: '田中').cats.exists?
Owner.find_by(name: '加藤').cats.exists?

上記のコードは、調べる飼い主が違うだけで.cats.exists?のコードは同じです。

このような場合は、同じような処理が2つ並んでいると冗長なので、以下のようにowner.rbhave_cat?メソッドを定義することで、処理をまとめることが出来ます。

app/models/owner.rb | have_cat?メソッドを定義する -->
1
2
3
def have_cat?
  cats.exists?
end  

メソッド作成時の注意点ですが、exists?メソッドの返り値は真偽値なので、メソッド名の最後に必ず「?」をつけましょう。

理由としては、Owner.find_by(name: '田中').have_cat?とメソッドを呼び出した際に、have_cat?に対する返り値がtrueなら猫を飼っていて、falseなら猫を飼っていないという事がメソッド名から読み取ることが出来るからです。

?を付けないからと言ってエラーになることはありませんが、真偽値を返す場合のメソッド名の最後に「?」をつける事で、メソッド名だけで処理の内容を理解することが出来るので可読性もあがります。

自作メソッドでリファクタリング

先ほど作成したhave_cat?メソッドを使って、以下のコードをリファクタリングします。

リファクタリング前のコード-->
1
Owner.find_by(name: '田中').cats.exists?

このコードにhave_cat?メソッドを使うと、以下のように記述することが出来ます。

コンソール | リファクタリング後のコード
1
2
3
4
[4] pry(main)> Owner.find_by(name: '伊藤').have_cat?
SELECT  `owners`.* FROM `owners` WHERE `owners`.`name` = '伊藤' LIMIT 1
SELECT  1 AS one FROM `cats` WHERE `cats`.`owner_id` = 2 LIMIT 1
=> true

メソッドを使うことで、コードも短くなり可読性が上がります。

また、以下のように後置unlessを使って、加藤さんの飼い猫がいなければ文言を出力することも出来ます。

コンソール | 後置unlessを使う場合
1
2
3
4
[5] pry(main)> '猫可愛いですよ〜!飼います?'  unless Owner.find_by(name: '加藤').have_cat?
SELECT  `owners`.* FROM `owners` WHERE `owners`.`name` = '加藤' LIMIT 1
SELECT  1 AS one FROM `cats` WHERE `cats`.`owner_id` = 4 LIMIT 1
=> "猫可愛いですよ〜!飼います?"

|| 演算子を併用する

|| 演算子は、左から順番にtrueであるか判定していて、falseが出れば一つ右の式をtrueか判定してと繰り返して、trueが出ればその式は真になります。

コンソール | 「|| 演算子」の使用例
1
2
3
4
[1] pry(main)> number = 100
=> 100
[2] pry(main)> number == 10 || number == 20 || number == 100
=> true # number == 100がtrueになるので、式全体がtrueになる

exists?メソッドや作成したhave_cat?メソッドのように、真偽値を返すメソッドと||演算子を併用することで簡潔なコードを書くことが出来ます。

例えば、以下のように犬も猫も飼っていない吉田さんのようなownerに対して、何か処理を行う場合を想定します。

猫も犬も飼っていない吉田さんを追加する

吉田さんが猫も犬も飼っていなければ、「ペットを飼いましょう」と出力する場合は、以下のように|| 演算子に加えて後置unlessを使うことでより簡潔に記述出来ます。

コンソール | 後置unlessと|| 演算子を使った例
1
2
3
4
5
6
7
[6] pry(main)> owner = Owner.find_by(name: '吉田')
  Owner Load (0.4ms)  SELECT  `owners`.* FROM `owners` WHERE `owners`.`name` = '吉田' LIMIT 1

[6] pry(main)> 'ペットを飼いましょう!'  unless owner.have_cat? || owner.dogs.exists?
SELECT  1 AS one FROM `cats` WHERE `cats`.`owner_id` = 5 LIMIT 1
SELECT  1 AS one FROM `dogs` WHERE `dogs`.`owner_id` = 5 LIMIT 1
=> "ペットを飼いましょう!"

上記の||演算子の箇所では、吉田さんは猫を飼ってないのでowner.have_cat?の式でfalseを返し、右のowner.dogs.exists?の式を判定します。犬も飼ってないのでfalseを返し式全体がfalseになります。

||演算子とunlessの流れ

そして、上記の画像のように式全体がfalseになったことで、unlessでfalseの場合の「ペットを飼いましょう」という処理が実行されます。

コードを再度確認 -->
1
 'ペットを飼いましょう!'  unless owner.have_cat? || owner.dogs.exists?

上記のコードを再確認すると、have_cat?メソッド同様に.dogs.exists?もメソッドに処理をまとめることが出来るので、以下のようにowner.rbhave_dog?メソッドを定義します。

app/models/owner.rb | have_dog?メソッドを定義する-->
1
2
3
def have_dog?
  dogs.exists?
end  

have_dog?メソッドを使うと、以下のようにコードの可読性が上がります。

have_dog?メソッドを使ったリファクタリング後のコード-->
1
 'ペットを飼いましょう!'  unless owner.have_cat? || owner.have_dog?

上記のコードは、ownerがペットを飼っているか確認しているので、以下のhave_pet?メソッドをowner.rbに定義し処理をまとめることによって、更にリファクタリングすることが可能です。

app/models/owner.rb | have_pet?メソッドを定義する-->
1
2
3
def have_pet?
  have_cat? || have_dog? # have_cat?がfalseならhave_dog?を判定する
end  

have_pet?メソッドを使うと、以下のように簡潔に記述することが出来ます。

コンソール | リファクタリング後
1
2
3
4
[9] pry(main)> 'ペットを飼いましょう!'  unless owner.have_pet?
SELECT  1 AS one FROM `cats` WHERE `cats`.`owner_id` = 5 LIMIT 1
SELECT  1 AS one FROM `dogs` WHERE `dogs`.`owner_id` = 5 LIMIT 1
=> "ペットを飼いましょう!"

リファクタリング前のコードと比べると、以下のようにコードが短くなり可読性が上がります。

リファクタリング前のコードとメソッドを作成したリファクタリング後のコードの比較-->
1
2
3
4
5
# リファクタリング前
'ペットを飼いましょう!'  unless owner.have_cat? || owner.dogs.exists?

# リファクタリング後
'ペットを飼いましょう!'  unless owner.have_pet?

真偽値を返す処理がある際は、||演算子と併用することで、このように可読性のある便利なメソッドを作成することが出来ます。

この記事のまとめ

  • exists?メソッドとは、指定した条件のレコードがデータベースに存在するかどうかを真偽値で返すメソッド
  • レコードが存在すればtrueを存在しなければfalseを返す
  • exists?メソッドの引数に渡すことが出来る形式は、整数、文字列、ハッシュ、配列、引数なしがある