すでにメンバーの場合は

無料会員登録

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

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

Pikawakaにログイン

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

Ruby on Railsでデータ操作をしてみよう

この記事で出来るようになること

CRUD

ActiveRecordは、CRUDの機能を提供します。

CRUDとは、Create(作成)・Read(読み込み)・Update(更新)・Delete(削除)の頭文字を繋げた用語で、データ操作の基本となる4つの処理のことでしたね。

SQLでは、CRUDに対応するデータ操作言語のキーワードとしてINSERTSELECTUPDATEDELETEが用意されており、これらを利用してデータ操作を学習しました。

CRUD SQL文 SQL文の説明
Create(作成) INSERT文 テーブルにレコードを挿入する
Read(読み込み) SELECT文 テーブルのレコードを抽出する
Update(更新) UPDATE文 テーブルのレコードを更新する
Delete(削除) DELETE文 テーブルのレコードを削除する

Railsでは、ActiveRecordによってCRUDに対応する便利なメソッドがたくさん用意されており、これらのメソッドを利用してデータ操作することができます。

ActiveRecordが提供するメソッドやクラスを利用することで「オブジェクトを操作するようにデータ操作できる」と説明しましたね。データ操作のメソッドを学習する前に、イメージとして理解しておきましょう。

オブジェクトを操作するようにデータ操作できる

ActiveRecordの規約で少し触れましたが、ActiveRecordでは「モデルのクラス」と「データベースのテーブル」が1対1で紐付けられます。

例えば、以下のようにモデルのUserクラスの場合は、データベースのusersテーブルに紐付きます。

例 | app/models/user.rb
1
2
class User < ApplicationRecord
end

Userモデルに対応するテーブル

ActiveRecordでは「モデル」と「テーブル」だけではなく、「インスタンス」と「レコード」、「プロパティ」と「カラム」も1対1で紐付けられます。

モデルクラスのインスタンスは、テーブルのレコード1件に対応するオブジェクトになり、オブジェクトのプロパティは、テーブルのカラムに対応します。

1対1の関係

Rubyのクラスとインスタンスの章で学習しましたが、インスタンスを生成するには、クラスに対して「newメソッド」を呼び出します。

クラスのインスタンスを生成する
1
クラス名.new

モデルクラスの場合でも、同じように「newメソッド」を呼び出してインスタンスを生成します。しかし、通常のクラスとは違って生成されたインスタンスは、「テーブルのレコード1件」に対応するオブジェクトになります。

モデルクラスのインスタンスを生成する
1
2
モデルのクラス名.new 
#レコード1件に対応するオブジェクトになる

モデルクラスのインスタンス(オブジェクト)に対して、ActiveRecordが提供するメソッドを利用することで、テーブルにレコードを挿入したり、更新や削除などが行えます。まさにオブジェクトを操作するように、データ操作ができます。

例えば、Userモデルクラスのインスタンスに対して、ActiveRecordが提供するsaveメソッドを呼び出せば、テーブルにデータを挿入することができます。

usersテーブルに1件のレコードを挿入する
1
2
3
4
5
# Userモデルのインスタンスをuserに代入する
user = User.new(name: "山田花子", age: 34)

# saveメソッドでインスタンスをデータベースに保存する
user.save

メソッドの箇所で詳しく説明しますが、モデルのクラスに対してnewメソッドを呼び出した時点では、「テーブルのレコード1件」に対応するオブジェクトが生成されただけです。実際にデータベースに保存するのは、saveメソッドが行います。

SQLではINSERT構文を使用して、テーブルに新しいレコードを挿入しましたが、RailsではActiveRecordのお陰で、オブジェクトを操作するようにデータ操作が可能になります。

SQLの場合
1
INSERT INTO users(name, age) VALUES('山田花子', 34);
Railsの場合
1
2
user = User.new(name: "山田花子", age: 34)
user.save

ちなみにモデルのインスタンスに対してsaveメソッドを呼び出すと、裏側では以下のようなSQL文が発行されます。実際にはINSERT構文を使用して、テーブルにレコードを挿入していることがわかりますね。

Railsの場合
1
2
3
4
user = User.new(name: "山田花子", age: 34)

user.save
# INSERT INTO `users` (`name`, `age`, `created_at`, `updated_at`) VALUES ('山田花子', 34, '2023-03-24 07:47', '2023-03-24 07:47'

newメソッドを呼び出した時点で特に指定されてないcreated_atupdated_atの値は、発行されるSQL文により、「レコード作成時に現在の日時を自動的に設定する」ということを確認できます。

ぴっかちゃん

CRUD系のメソッドってたくさんありそう。理解できるかな・・・。

ここでは基本的なメソッドだけを押さえるよ。SQLは学習済みだから、裏側で発行されるSQL文を確認すると、そのメソッドが何をしているのか理解しやすくなるかも!

ぴかわかさん

準備:コンソールを起動しよう

データ操作は、コンソール上で行います。Railsでは、consoleコマンドでコンソールを起動させて、Railsアプリケーションとコマンドラインでやり取りすることができます。

Webにアクセスすることなく、サーバーのデータを変更するときやメソッドを試すときに便利です。内部ではirbが使われているので、同じように操作を行えます。

ターミナル | コンソールを起動させる
1
2
rails console
# rails c でも可能

コンソールを起動させる

他にもオプションの-sandboxを付けることで、終了時にコンソールで変更したデータを元に戻すことができます。

ターミナル | Sandboxモードでコンソールを起動させる
1
2
rails console -sandbox
# rails c -s でも可能

上記のコマンドを実行すると、下の画像のようにin sandboxが表示されます。

Sandboxモードのコンソールを起動

コンソールを終了するには、exitquitを実行します。

コンソール | コンソールを終了する
1
2
3
exit
#もしくは、以下を実行する
quit

今回は、実際にデータベースにデータを反映させていくので、Sandboxモードではなく、通常モードでコンソールを起動させます。

ぴかわかさん
コンソールを起動してみましょう

以下のコマンドを実行して、コンソールを起動してください。

employee_managementディレクトリへ移動
1
cd ~/environment/employee_management
~/environment/employee_management | 通常モードのコンソールを起動
1
rails c

上記のコマンドを実行すると、以下のように表示されます。

コンソールを起動させる

Create

Createは、「テーブルにレコードを挿入する」というデータ操作の基本処理を指します。

Railsの場合は、ActiveRecordが提供するsaveメソッド、createメソッドを利用することで、テーブルにレコードを挿入することができます。

メソッド 説明
new クラスのインスタンスを生成する(saveメソッドを使用して保存する)
save モデルのインスタンスをデータベースに保存する
create モデルのインスタンスの生成と同時にデータベースに保存する

createメソッド

createメソッドは、モデルのインスタンス生成と同時にデータベースに保存するクラスメソッドです。

Userモデルにnameとageというプロパティがある場合、Userモデルのクラスに対してcreateメソッドを呼び出すと、新しいレコードが1件作成され、データベースに保存されます。

モデルのインスタンスを生成して保存
1
モデルのクラス名.create(カラム名1: 1, カラム名2: 2)
例 | Userモデルのインスタンス生成して保存
1
User.create(name: "山田花子", age: 34)
createメソッドを利用して、departmentsテーブルに1件のレコードを挿入しましょう

以下をコンソールで実行し、nameカラムの値が"開発"のレコードをdepartmentsテーブルに挿入しましょう。

コンソール | Departmentモデルのインスタンス生成して保存
1
Department.create(name: "開発")

上記を実行すると、以下のように裏側でSQL文が発行されます。SQLの章で学習したようにINSERT構文で、テーブルに新しいレコードを挿入していることがわかりますね。

created_atupdated_atには、現在の日時が自動的に設定されます。

テーブルに新しいレコードを挿入

phpMyAdminで確認すると、以下のようにdepartmentsテーブルにnameカラムの値が開発のレコードが1件挿入されています。また、idも自動的に値が入っていますね。

newメソッド/saveメソッド

saveメソッドは、モデルのインスタンスをデータベースに保存します。

createメソッドとは違い、saveメソッドはインスタンスを生成しません。そのため、インスタンス生成を行うnewメソッドとセットで利用します。

newメソッドとsaveメソッドの使い方
1
2
3
4
5
 # Userモデルのインスタンスを生成して、userに代入する
user = User.new(name: "山田花子", age: 34)

# saveメソッドでインスタンスをデータベースに保存する
user.save

モデルのクラスに対してnewメソッドを呼び出した時点では、「テーブルのレコード1件」に対応するオブジェクトが生成されただけです。実際にデータベースに保存するのは、saveメソッドが行います。

saveメソッド

レコードに挿入する値は、最初のコードのようにnewメソッドの引数にハッシュで一度に指定することもできますし、あとのコードのように指定することもできます。

レコードに挿入する値のセット方法
1
2
user = User.new(name: "山田花子", age: 34)
user.save
レコードに挿入する値のセット方法
1
2
3
4
5
user = User.new

user.name = "山田花子"
user.age = 34
user.save
new/saveメソッドで、departmentsテーブルに1件のレコードを挿入しましょう

nameカラムの値が"営業"のレコードをdepartmentsテーブルに挿入してみます。

まずは、newメソッドを利用して、Departmentモデルのインスタンスを生成します。以下のコードをコンソールに記述し、実行しましょう。

コンソール | Departmentモデルのインスタンスを生成する
1
department = Department.new(name: "営業")

上記を実行すると、以下のようにDepartmentモデルのインスタンスが生成されます。この時点では、newメソッドの引数で指定したname属性のみ値が設定されており、idやcreated_atとupdated_atの属性値は、いずれもnilで値が何も入っていない状態です。

Departmentモデルのインスタンスを生成

続いて、生成したDepartmentモデルのインスタンスをsaveメソッドでデータベースに保存します。以下のコードをコンソールに記述し、実行しましょう。

コンソール | 生成したインスタンスをデータベースに保存する
1
department.save

上記を実行すると、以下のように裏側でSQL文が発行されます。INSERT構文で、テーブルに新しいレコードを挿入していることがわかりますね。

created_atupdated_atには、現在の日時が自動的に設定されます。

生成したインスタンスをデータベースに保存する

phpMyAdminで確認すると、以下のようにdepartmentsテーブルにnameカラムの値が営業のレコードが1件挿入されています。また、idも自動的に値が入っていますね。

phpMyAdminで確認

そして、Departmentモデルのインスタンスが入るdepartmentをコンソールで実行すると、以下のようにnewメソッドを呼び出した時点では、nilだった属性値に値が設定されていることがわかります。

departmentを実行

idには2という値が設定され、created_atやupdated_atには、saveメソッドを実行した際の日時、つまりレコードを作成した現在の日時が設定されます。

複数レコードを同時に挿入する

複数レコードを同時に挿入する方法はいくつかありますが、ここでは配列とハッシュを利用して、createメソッドで一括挿入してみます。

createメソッドでは、1件のレコードをデータベースに挿入することができましたね。

モデルのインスタンスを生成して保存
1
モデルのクラス名.create(カラム名1: 1, カラム名2: 2)

複数のレコードをデータベースに挿入させるには、配列の要素にハッシュを指定します。

モデルのインスタンスを複数生成して保存する場合
1
2
3
4
5
モデルのクラス名.create([
  { カラム名1: 1 }, # 1件目のレコード
  { カラム名2: 2 }, # 2件目のレコード
  { カラム名3: 3 }  # 3件目のレコード
])
employeesテーブルに複数レコードを同時に挿入してみましょう

employeesテーブルに挿入するデータは、以下の通りです。

id name birthday department_id
1 田中太郎 1980-10-22 1
2 山田花子 1983-08-20 1
3 高橋一朗 1986-06-16 2
4 伊藤晴子 1987-10-01 1
5 鈴木二郎 1990-01-17 2
6 山口冬子 1993-05-12 2

employeesテーブルに複数レコードを挿入するために、以下のコードをコンソールに記述し、実行しましょう。

コンソール
1
2
3
4
5
6
7
8
Employee.create([
  { name: '田中太郎', birthday: '1980-10-22', department_id: 1 },
  { name: '山田花子', birthday: '1983-08-20', department_id: 1 },
  { name: '高橋一朗', birthday: '1986-06-16', department_id: 2 },
  { name: '伊藤晴子', birthday: '1987-10-01', department_id: 1 },
  { name: '鈴木二郎', birthday: '1990-01-17', department_id: 2 },
  { name: '山口冬子', birthday: '1993-05-12', department_id: 2 }
])

上記を実行すると、以下のように裏側でSQL文が発行されます。INSERT構文で、テーブルに新しいレコードを6件挿入していることがわかりますね。

レコードを6件挿入

phpMyAdminでemployeesテーブルを表示して、createメソッドで指定した6件のレコードがテーブルに挿入されていることを確認しましょう。

phpMyAdminで確認

Read

Readは、「テーブルのレコードを抽出する」というデータ操作の基本処理を指します。

Railsの場合は、ActiveRecordが提供するallメソッド、findメソッドなどを利用することで、テーブルのレコードを抽出することができます。

メソッド 説明
all 全てのレコードを取得する
find 「ID」に当てはまるレコードを全て取得する
find_by 「条件」を指定して最初の1件を取得する
where 「条件」に当てはまるレコードを全て取得する

allメソッド

モデルのクラスに対してallメソッドを呼び出すと、モデルに対応するテーブルのレコードをすべて取得します。

モデルに対応するテーブルのレコードをすべて取得
1
モデルのクラス名.all

例えば、usersテーブルのレコードを全て取得する場合はUser.allと記述します。

usersテーブルのレコードをすべて取得する
1
User.all

usersテーブルのレコードをすべて取得する

employeesテーブルのレコードをすべて取得しましょう

employeesテーブルの全てのレコードを取得してみます。

employeesテーブルの全てのレコードを取得

以下のコードをコンソールに記述し、実行しましょう。

コンソール | employeesテーブルのレコードをすべて取得する
1
Employee.all

上記を実行すると、以下のように裏側でSQL文が発行されます。SQLの章で学習したようにSELECT構文で、テーブルのすべてのレコードを抽出していることがわかりますね。

戻り値には、employeesテーブルの全てのレコードが設定されています。

テーブルのレコードを抽出

findメソッド

findメソッドは、引数の主キーに対応するレコードを取得するクラスメソッドです。

findメソッドの引数には1つの主キーだけではなく、配列で複数の主キーの値を指定して、レコードを取得することもできます。

主キー値に対応するレコードを取得する
1
モデルのクラス名.find(主キー値)
配列で複数の主キー値を指定する場合
1
モデルのクラス名.find([主キー値1, 主キー値2])

テーブルの主キーは、idというカラム名がデフォルトで使われます。

例えば、以下のようにUser.find(1)と指定する場合、Userモデルに対応するusersテーブルの主キー、つまりidの値が1のレコードを取得します。

例 | idの値が1のレコードを取得する
1
User.find(1)

主キーが1のレコードを取得する

以下のように指定すると、idの値が12のレコードを取得します。

例 | idの値が1と2のレコードを取得する
1
User.find([1,2])
employeesテーブルのidの値を指定してレコードを取得してみましょう

employeesテーブルのidの値が4のレコードを取得してみます。

idの値が4のレコードを取得

以下のコードをコンソールに記述し、実行しましょう。

コンソール | 主キー値が4のレコードを取得する
1
Employee.find(4)

上記を実行すると、以下のように裏側でSQL文が発行されます。SELECT構文で、employeesテーブルのidの値が4のレコードを抽出していることがわかりますね。

戻り値は、取得したデータの値がセットされているEmployeeモデルのインスタンスです。

Employeeモデルのインスタンス

find_byメソッド

find_byメソッドは、引数の条件に当てはまる最初の1件のレコードを取得するクラスメソッドです。

引数の条件は、ハッシュで指定します。

条件を指定して最初の1件のレコードを取得する
1
モデルのクラス名.find_by(条件)
田中太郎という名前を持つ最初の1件を取得する
1
User.find_by(name: '田中太郎')
条件を指定してemployeesテーブルから最初の1件のレコードを取得しましょう

employeesテーブルのnameの値が'鈴木二郎'のレコードを取得してみます。

employeesテーブルから最初の1件のレコードを取得

以下のコードをコンソールに記述し、実行しましょう。

鈴木二郎という名前を持つ最初の1件を取得する
1
Employee.find_by(name: '鈴木二郎')

上記を実行すると、以下のように裏側でSQL文が発行されます。SELECT構文で、employeesテーブルのnameの値が'鈴木二郎'のレコードを抽出していることがわかりますね。

戻り値は、取得したデータの値がセットされているEmployeeモデルのインスタンスです。

鈴木二郎という名前を持つ最初の1件を取得する

whereメソッド

whereメソッドは、引数の条件に当てはまるレコードを全て取得するクラスメソッドです。

引数の条件に当てはまるレコードを全て取得する
1
モデルのクラス名.where(条件)

find_byメソッドの場合は、引数の条件に当てはまる最初のレコードを1件だけ取得しますが、whereメソッドは、条件に当てはまる全てのレコードを取得します。

例えばUser.where(age: 20)User.find_by(age: 20)のように、引数に同じ条件を指定しても、取得するレコード数は違います。

年齢が20歳のレコードをusersテーブルから全て取得する
1
User.where(age: 20)
年齢が20歳の最初のレコードを1件取得する
1
User.find_by(age: 20)

以下のようにUser.where(age: 20)は、age20のレコードを全て取得します。

whereとfind_byの違い

引数の条件に当てはまるレコードをemployeesテーブルから全て取得しましょう

employeesテーブルのdepartment_idの値が2のレコードをすべて取得してみます。

department_idが2のレコードを全て取得する

以下のコードをコンソールに記述し、実行しましょう。

コンソール | department_idの値が2のレコードを全て取得する
1
Employee.where(department_id: 2)

上記を実行すると、以下のように裏側でSQL文が発行されます。SELECT構文で、employeesテーブルのdepartment_idの値が2のレコードを抽出していることがわかりますね。

戻り値には、条件に当てはまるemployeesテーブルの全てのレコードが設定されています。

戻り値

Update

Updateは、「テーブルのレコードを更新する」というデータ操作の基本処理を指します。

Railsの場合は、ActiveRecordが提供するupdateメソッドなどを利用することで、テーブルのレコードを更新することができます。

メソッド 説明
update 既存のレコードを引数に指定するプロパティ値に更新する

saveメソッド

更新対象のオブジェクトを取得すると、オブジェクトのプロパティを変更し、その結果をデータベースに保存できるようになります。

例えば、以下のようにfind_byメソッドで更新対象のオブジェクトを取得し、nameプロパティ値を'田中太郎'から'田中航太郎'に変更します。変更後、saveメソッドを呼び出して、データベースに保存します。

saveメソッドを利用して更新処理を行う場合
1
2
3
user = User.find_by(name: '田中太郎') #更新対象のオブジェクトを取得
user.name = '田中航太郎' #プロパティを変更
user.save #データベースに保存する

saveメソッドを利用する場合

updateメソッドを利用すると、上のコードを短く書けるよ!

ぴかわかさん

updateメソッド

updateメソッドを利用する場合も、あらかじめfindメソッドやfind_byメソッドなどで、オブジェクトを取得してから更新処理を行います。

updateメソッドの引数にプロパティ名と設定したい値をハッシュで対応付けます。

更新対象のオブジェクトのプロパティを引数の値に更新する
1
更新対象のオブジェクト.update(プロパティ名: )

先ほどのsaveメソッドと同様に'田中太郎''田中航太郎'に更新する場合は、更新対象のオブジェクトを取得後、以下のようにupdateメソッドの引数を指定します。

updateメソッドを呼び出すと、更新対象のオブジェクトのプロパティを引数の値に更新し、その結果をデータベースに保存します。

updateメソッドを利用して更新処理を行う場合
1
2
user = User.find_by(name: '田中太郎')
user.update(name: '田中航太郎')

updateメソッド

updateメソッドを利用して、既存のレコードのカラムを新しい値に更新しましょう

employeesテーブルにある山口冬子さんの生年月日、birthdayの値を1993-05-12から1993-12-12に更新してみます。

updateメソッドで更新する

以下のコードをコンソールに記述し、実行しましょう。

コンソール | birthdayの値を1993-05-12から1993-12-12に更新する
1
2
employee = Employee.find_by(name: '山口冬子')
employee.update(birthday: '1993-12-12')

上記を実行すると、以下のように裏側でSQL文が発行されます。SQLの章で学習したように、既存のレコードのカラムを新しい値に更新するには、UPDATE構文が使用されます。

SET句には変更するカラム名と新しい値、WHERE句にはどのレコードを更新するかを識別する条件が指定されており、id6のレコードのbirthday'1993-12-12'に更新していることがわかりますね。

updateメソッド2

また、レコードの更新時に現在の日時を設定するupdated_atの値も自動的に更新されます。データが更新されると、戻り値としてtrueを返します。

更新後のオブジェクトを確認してみましょう

オブジェクトのプロパティ値が更新されていることを確認してみましょう。更新対象のオブジェクトを保持するemployeeをコンソールに記述し、実行しましょう。

コンソール | オブジェクトのプロパティが更新されていることを確認する
1
employee

上記を実行すると、以下のように更新後のbirthdayのプロパティ値は、更新前と比べてupdateメソッドに指定した値に変更されていることがわかりますね。

update3

データベースも更新されているか確認してみましょう

phpMyAdminを再読み込みして、employeesテーブル内の山口冬子さんのbirthday1993-05-12から1993-12-12に更新されていることを確認しましょう。

また、updated_atの値も更新した日時に変更されたかを確かめてみてください。

update4

Delete

Deleteは、「テーブルのレコードを削除する」というデータ操作の基本処理を指します。

Railsの場合は、ActiveRecordが提供するdestroyメソッド、destroy_allメソッドなどを利用することで、テーブルのレコードを削除することができます。

メソッド 説明
destroy 既存のレコードを削除する
destroy_all 指定した条件のレコードと関連しているレコードをすべて削除する

destroyメソッド

destroyメソッドを利用する場合、あらかじめfindメソッドやfind_byメソッドなどで、オブジェクトを取得してから削除処理を行います。

削除対象のオブジェクトに対して、destroyメソッドを呼び出します。

destroyメソッドでオブジェクトを削除する
1
削除対象のオブジェクト.destroy

例えば、usersテーブルのnameカラムの値が'田中太郎'のレコードを削除するには、以下のように、まずオブジェクトを取得します。そして、取得したオブジェクトに対してdestroyメソッドを呼び出し、データベースからレコードを削除します。

destroyメソッドでレコードを削除する
1
2
user = User.find_by(name: '田中太郎') #削除対象のオブジェクトを取得する
user.destroy #レコードを削除する

destroyメソッド

destroyメソッドを利用して、既存のレコードを削除してみましょう

employeesテーブルにあるnameカラムの値が'山口冬子'のレコードを削除してみます。

レコードを削除する

以下のコードをコンソールに記述し、実行しましょう。

コンソール | nameカラムの値が'山口冬子'のレコードを削除する
1
2
employee = Employee.find_by(name: '山口冬子')
employee. destroy

上記を実行すると、以下のように裏側でSQL文が発行されます。SQLの章で学習したように、テーブル内の既存のレコードを削除するには、DELETE構文が使用されます。

既存のレコードを削除

テーブル内のレコードが削除されているかを確認しよう

phpMyAdminを再読み込みして、employeesテーブルにあるnameの値が'山口冬子'のレコードが削除されていることを確かめてみましょう。

テーブルを確認する

destroy_allメソッド

モデルのクラスに対してdestroy_allメソッドを呼び出すと、モデルに対応するテーブルのレコードをすべて削除します。

モデルに対応するテーブルのレコードを全て削除する
1
モデルのクラス名.destroy_all

usersテーブルのレコードを全て削除する場合は、User.destroy_allと記述します。

usersテーブルの全てのレコードを削除する
1
User.destroy_all

usersテーブルの全てのレコードを削除する

destroy_allメソッドを利用して、テーブルのレコードを全て削除しましょう

以下のコードを上から順番にコンソールで実行し、employeesテーブルのレコードから削除するようにしましょう。

employeesテーブル内にあるレコードを全て削除する
1
Employee.destroy_all
departmentsテーブル内にあるレコードを全て削除する
1
Department.destroy_all
テーブル内のレコードが全て削除されたかを確認しましょう

phpMyAdminを再読み込みして、departmentsテーブルとemployeesテーブル内のレコードが全て削除されたかを確認しましょう。

departmentsテーブル

employeesテーブル

バリデーション(検証)

バリデーション(validation)とは、入力されたデータが有効であるかを検証する仕組みのことです。Webアプリケーションでは、さまざまな場所からデータを受け取りますが、バリデーションはデータベースに書き込む前に「データが有効かどうか」を検証します。

例えば、お問い合わせフォームの必須項目に対して、データの存在性(データがあるべき箇所に存在しているか)を検証することができます。1項目でも未入力があった場合は、以下のように送信されず、データベースに保存されません。

お問い合わせフォーム

このような検証処理を1から実装するのは大変です。Active Recordのバリデーション機能を利用することで、シンプルなコードで検証を定義することができます。

Active Recordのバリデーション機能

CRUDで学習しましたが、RailsではActiveRecordが提供するメソッドを利用することで、オブジェクトを操作するようにデータ操作が可能でしたよね。

SQLの場合
1
INSERT INTO users(name, age) VALUES('山田花子', 34);
Railsの場合
1
2
user = User.new(name: "山田花子", age: 34)
user.save

saveメソッドは、モデルのインスタンスをデータベースに保存するメソッドですが、以下のようにActive Recordのバリデーション機能を利用すると、モデルのインスタンスがデータベースに書き込まれる前に、インスタンスの状態を検証することができます。

saveメソッドを実行する際に、自動的にインスタンスの状態が検証されて、インスタンスが有効な場合のみデータベースに保存されます。

インスタンスの状態を検証

例えば、Userモデルクラスのname属性とage属性に対して「値が空ではないか」という存在性のバリデーションを設定した場合でみてみましょう。

saveメソッドを実行する際、以下のようにインスタンスの状態が検証されます。インスタンスのname属性とage属性の値が空ではない場合、インスタンスが有効となり、データベースに保存されます。

インスタンスの状態を検証

インスタンスのname属性とage属性の値が空の場合、バリデーションは失敗となり、インスタンスは無効となります。無効なインスタンスは、データベースに保存されません。

例えば、インスタンス生成時にname属性とage属性に対して、空であるnilをセットした場合は、以下のようにデータベースにインスタンスは保存されません。

空の場合

バリデーションが設定される場合は、saveメソッドを実行する際に自動的にインスタンスの状態が検証されます。saveメソッドの他にも、createメソッドやupdateメソッドを実行する際に行われます。

バリデーションを行う主なメソッド 無効な場合の挙動
saveメソッド INSERT操作を行わず、falseを返す
createメソッド INSERT操作を行わず、そのインスタンスを返す
updateメソッド UPDATE操作を行わず、falseを返す

それでは、バリデーションを設定する方法を学んでいきましょう

ぴかわかさん

バリデーションの定義

バリデーションは、モデルのクラスに定義します。

モデルクラス内にvalidatesメソッドを利用して、検証対象の属性と検証内容を設定することができます。

モデルのクラス
1
2
3
class クラス名 < ApplicationRecord
validates(:属性名, 検証名: true)
end

validatesメソッドの検証名: trueのように、メソッドの最後の引数がハッシュの場合は、括弧を付けなくても問題ありません。

多くの場合は、以下のように括弧を外して記述します。

モデルのクラス
1
2
3
class クラス名 < ApplicationRecord
validates :属性名, 検証名: true
end

先ほどの具体例のように、Userモデルのname属性とage属性に対して「値が空ではないか」という存在性(presence)の検証を行う場合は、以下のように定義します。

例 | app/models/user.rb
1
2
3
4
class User < ApplicationRecord
validates :name, presence: true
validates :age, presence: true
end

上記のバリデーションをUserモデルのクラスに定義すると、以下のようにsaveメソッドを実行する際、自動的にインスタンスのname属性とage属性の値が空ではないか検証を行うことができます。

インスタンスの状態を検証

Userモデルのクラスに存在性のバリデーションを定義していない場合、saveメソッドを実行したとしても、インスタンスの属性に対して値が空ではないかの検証は行われません。

例 | バリデーションを何も設定していない場合
1
2
class User < ApplicationRecord
end

よく使われる「存在性」「一意性」「長さ」の検証を設定します。

ぴかわかさん

存在性の検証

存在性(presence)は、指定した属性の値が空ではないことを検証します。
Departmentモデルのname属性、Employeeモデルのname属性に設定してみます。

属性には、以下のいずれかの値がセットされる場合にインスタンスが無効になります。

  • nil
  • 空文字(''
  • 空白文字(' '
コンソールを終了している場合は、rails cコマンドで起動させましょう

以下のコマンドを実行して、コンソールを起動してください。

employee_managementディレクトリへ移動
1
cd ~/environment/employee_management
~/environment/employee_management | 通常モードのコンソールを起動
1
rails c

上記のコマンドを実行すると、以下のように表示されます。

コンソールを起動させる

departmentsテーブルのnameカラムにNULLを格納できないことを確認しましょう

Departmentモデルにバリデーションを設定する前でも、name属性にnilをセットした場合、データベースに保存されません。

以下のコードをコンソールに記述し、実行しましょう。

コンソール
1
2
department = Department.new(name: nil)
department.save

上記を実行すると、以下のようなエラーが発生します。

非NULL制約

departmentsテーブルのnameカラムには「非NULL制約」が設定されるので、そもそもNULLを格納することができません。

ぴっかちゃん

非NULL制約を設定してるのに、モデルのバリデーションって必要なの?

非NULL制約はNULLを禁止するけど、空文字や空白文字は保存されてしまうんだ。だからモデルのバリデーションは必要だよ!

ぴかわかさん
非NULL制約を設定するnameカラムに空白文字が保存されることを確認しましょう

以下のコードをコンソールに記述し、実行しましょう。

コンソール
1
2
department = Department.new(name: "     ")
department.save

上記を実行すると、以下のようにINSERT操作が行われてtrueが返ります。

空白文字が保存される

phpMyAdminを再読み込みして、departmentsテーブルにnameカラムの値が空白文字のレコードが1件挿入されていることを確認しましょう。

空白文字が保存される2

Departmentモデルのname属性にpresence: trueを設定しましょう

空文字や空白文字もデータベースに保存できないように、Departmentモデルのname属性にpresence: trueを設定しましょう。

app/models/department.rb
1
2
3
class Department < ApplicationRecord
validates :name, presence: true
end
コンソールにreload!と打ち込み、モデルの最新情報を反映させましょう

コンソールを立ち上げている最中にモデルの内容を変更した場合、コンソールにはすぐに反映されません。コンソールにreload!と打ち込むことで、再起動せずに最新のモデル情報を反映させることができます。

コンソールにreload!と打ち込み、実行しましょう。

コンソール | 再読み込みする
1
reload! 

以下のようにtrueを返せば、再読み込みできています。

コンソールを再読み込み

name属性の値が空の場合、データベースに保存されないことを確認しましょう

以下のコードをコンソールに記述し、実行しましょう。

コンソール
1
2
department = Department.new(name: "     ")
department.save

上記を実行すると、以下のようにINSERT操作が行われず、falseが返ります。

Departmentモデルのname属性にpresence: trueを設定したことで、saveメソッドが実行される際に自動でインスタンスのname属性の値が空ではないかを検証します。

name属性には空白文字をセットしたので、インスタンスは無効となり、データベースに保存されません。

Employeeモデルのname属性にもpresence: trueを設定しましょう
app/models/employee.rb
1
2
3
class Employee < ApplicationRecord
validates :name, presence: true
end

起動中のコンソールにEmployeeモデルの最新情報を反映させるために、reload!と打ち込み、実行しましょう。

コンソール | 再読み込みする
1
reload! 

以下のコードをコンソールに記述し、実行しましょう。

コンソール
1
2
employee = Employee.new(name: '')
employee.save

Employeeモデルのインスタンスには、name属性の値を空文字('')でセットしたので、インスタンスは無効となり、INSERT操作が行われずfalseが返ります。

Employeeモデルの検証

一意性の検証

一意性(uniqueness)は、指定した属性の値が一意で重複しないことを検証します。このバリデーションは、「指定した属性と同じ値を持つ既存のレコード」がモデルに対応するテーブルにあるかどうかを調べてくれます。

一意性の検証は、validatesメソッドの引数にuniqueness: trueを指定します。

モデルのクラス | 一意性の検証を指定する
1
2
3
class クラス名 < ApplicationRecord
validates :属性名, uniqueness: true
end

それでは、Departmentモデルの部署名に対して、一意性の検証が行われるように設定してみます。

ぴかわかさん
Departmentモデルのname属性にuniqueness: trueを追加しましょう

まずは、以下のコードをコンソールで実行し、部署名が'営業'の新しいレコードをdepartmentsテーブルに挿入しましょう。

コンソール
1
2
department = Department.new(name: '営業')
department.save

次にDepartmentモデルのname属性にuniqueness: trueを追加し、一意性の検証が行われるように設定しましょう。

app/models/department.rb
1
2
3
class Department < ApplicationRecord
validates :name, presence: true, uniqueness: true
end

起動中のコンソールにDepartmentモデルの最新情報を反映させるために、reload!と打ち込み、実行しましょう。

コンソール | 再読み込みする
1
reload! 

最後に部署名の一意性の検証が行われるか確認してみます。

departmentsテーブルには、部署名が'営業'のレコードがすでに存在するので、同じ値を保存しようとすると、一意性の検証により、インスタンスは無効になり、データベースには保存されないはずです。

以下のコードを実行し、一意性の検証が行われるか確かめてみましょう。

コンソール
1
2
department = Department.new(name: '営業')
department.save

上記を実行すると、以下のように裏側では「departmentsテーブルに同じ値を持つレコードがあるかどうか」を調べるSQL文が発行されます。

すでに'営業'を持つレコードは存在しているので、インスタンスは無効となり、データベースに保存されずに、falseを返します。

長さの検証

長さ(length)の検証は、指定した属性の値の長さを検証します。

長さの検証は、validatesメソッドの引数にlength: {オプション}を指定します。

モデルのクラス | 長さの検証を指定する
1
2
3
class クラス名 < ApplicationRecord
validates :属性名, length: { オプション }
end

オプションは、長さの制限を設けることができます。

オプション 説明
:minimum 指定した値より小さな値を取れない length: { minimum: 2 }
2より小さな値を取れない(1は取れない)
:maximum 指定した値より大きな値を取れない length: { maximum: 50 }
50より大きな値を取れない(51は取れない)
is 指定した値と等しいこと length: { is: 15 }
15と等しいこと(16は取れない)

departmentsテーブルとemployeesテーブルのnameカラムには、文字列の最大幅として30が指定されています。各モデルのname属性にlength: { maximum: 30 }を追加し、長さの検証が行われるように設定してみます。

Departmentモデルのname属性にlength: { maximum: 30 }を追加しましょう

Departmentモデルのname属性にlength: { maximum: 30 }を追加し、長さの検証が行われるように設定しましょう。

app/models/department.rb
1
2
3
class Department < ApplicationRecord
validates :name, presence: true, uniqueness: true, length: { maximum: 30 }
end

起動中のコンソールにDepartmentモデルの最新情報を反映させるため、reload!と打ち込み、実行しましょう。

コンソール | 再読み込みする
1
reload! 
31文字の部署の名前が長すぎることを検証しましょう

31文字の文字列は、'a' * 31で作成します。

lengthメソッドを呼び出して、'a' * 31が31文字の文字列を作ることを確認してみます。コンソールで以下のコードを記述し、実行しましょう。

コンソール
1
2
'a' * 31
('a' * 31).length

上記を実行すると、以下のように'a' * 31が31文字の文字列の長さであることが確認できます。

Departmentモデルのインスタンスに対して、name属性の値を31文字の文字列をセットして、検証してみましょう。

コンソールで以下のコードを記述し、実行しましょう。

コンソール
1
2
3
exceed_max_validation_length = 'a' * 31
department = Department.new(name: exceed_max_validation_length)
department.save

上記を実行すると、31文字の部署の名前が長すぎることが検証されます。インスタンスは無効となり、データベースに保存されずに、falseを返します。

Employeeモデルのname属性にもlength: { maximum: 30 }を追加しましょう

Employeeモデルのname属性にもlength: { maximum: 30 }を追加し、長さの検証が行われるように設定しましょう。

app/models/employee.rb
1
2
3
class Employee < ApplicationRecord
validates :name, presence: true, length: { maximum: 30 }
end

Employeeモデルの最新情報を反映させるため、コンソールにreload!と打ち込み、実行しましょう。

コンソール | 再読み込みする
1
reload! 

Employeeモデルのインスタンスに対して、name属性の値を31文字の文字列をセットして、検証してみましょう。

コンソールで以下のコードを実行し、falseを返すことを確かめましょう。

コンソール
1
2
3
exceed_max_validation_length = 'a' * 31
employee = Employee.new(name: exceed_max_validation_length)
employee.save

エラーメッセージ

バリデーションが行われた後にerrorsメソッドを利用すると、検証エラーに関する情報を取得することができます。

バリデーションが行われてインスタンスが無効だった場合、そのモデルのインスタンスに対してerrors.full_messagesメソッドを呼び出すと、全てのエラーメッセージを配列で取得できます。

全てのエラーメッセージを配列で取得する
1
モデルクラスのインスタンス.errors.full_messages

例えば、Userモデルのname属性とage属性に「値が空ではないか」という存在性の検証を定義していたとします。

例 | app/models/user.rb
1
2
3
4
5
class User < ApplicationRecord
  #name属性とage属性の値が空ではないか検証するように設定
  validates :name, presence: true
  validates :age, presence: true
end

Userモデルのインスタンスに対して、name属性とage属性の値をnilにセットし、saveメソッドを呼び出すとバリデーションが自動で実行されてfalseを返します。

インスタンスは無効なので、以下のようにerrors.full_messagesメソッドを呼び出すと、エラーメッセージを取得できます。

例 | コンソール
1
2
3
4
5
6
user = User.new(name: nil, age: nil) #nilでインスタンスを生成する
user.save #バリデーションが自動で実行される
=> false #インスタンスが無効なので、エラーメッセージが追加される

user.errors.full_messages #エラーメッセージを取得する
=> ["Name can't be blank", "Age can't be blank"]

有効なインスタンスに対してerrors.full_messagesメソッドを呼び出した場合、以下のようにエラーメッセージがない空の配列([])を返します。

例:インスタンスが有効な場合 | コンソール
1
2
3
4
5
6
user = User.new(name: '山田花子', age: 34)
user.save #バリデーションが自動で実行される
=> true #インスタンスは有効
user.errors.full_messages #エラーメッセージを取得する
=> [] #インスタンスは有効なので、空配列が返る

それでは、エラーメッセージを取得してみましょう!

ぴかわかさん
errors.full_messagesメソッドを利用して、エラーメッセージを取得してみましょう

Departmentモデルのname属性では、以下のバリデーションを設定していましたね。

app/models/department.rb
1
2
3
class Department < ApplicationRecord
  validates :name, presence: true, uniqueness: true, length: { maximum: 30 }
end

まずは、有効になるインスタンスをデータベースに保存し、エラーメッセージが[]を返すことを確認しましょう。

以下のコードをコンソールで実行してください。

コンソール | 有効なインスタンスのエラーメッセージを取得する
1
2
3
department = Department.new(name: '開発')
department.save
department.errors.full_messages

上記のdepartmentは有効なのでdepartment.saveを実行すると、以下のようにtrueを返します。有効なインスタンスでは、エラーメッセージがなく[]を返します。

有効なインスタンスの場合

無効なインスタンスに対してerrors.full_messagesメソッドを呼び出した場合、エラーメッセージを含む配列を返します。

以下のコードをコンソールで実行して、エラーメッセージを取得しましょう。

コンソール | 無効なインスタンスのエラーメッセージを取得する
1
2
3
department = Department.new(name: nil)
department.save
department.errors.full_messages

上記のdepartmentのname属性はnilで、インスタンスは無効です。department.saveを実行すると、以下のようにfalseを返します。無効なインスタンスでは、エラーメッセージを含む配列を返します。

アソシエーション(関連付け)

Active Recordには、「アソシエーション」と呼ばれる機能があります。アソシエーションとは、モデル同士の関連付けを行う機能のことです。

テーブル同士の関連付けは「外部キー制約」の設定によって、すでに定義されています。

外部キー制約の設定

このデータベース側のテーブル同士の関連付けは、Railsのモデル側には対応していません。

テーブル同士の関連付けだけではなく、アソシエーションでモデル同士の関連付けまで行うと、複数のテーブルにまたがる操作が直感的なコードで記述できるようになります。

開発の部署に所属している社員の情報を取得する場合で比較してみます。

アソシエーションでモデル同士の関連付けは行わず、テーブル同士の関連付けだけの場合は、以下のようにdepartmentsテーブルに対応するDepartmentモデルでfindメソッドを利用して、id1の開発のレコードを取得します。

department.idは、開発のレコードの主キーの値(1)を取得できます。whereメソッドに外部キーを指定してid1の開発部に属している社員を取得します。

アソシエーションを使用しない場合(モデル同士の関連付けなし)
1
2
department = Department.find(1)
employees = Employee.where(department_id: department.id)

アソシエーションでモデル同士の関連付けを行うと、以下のように外部キーを機にすることなくdepartment.employeesだけで、開発の部署に所属している社員を全て取得することができます。

アソシエーションを使用する場合(モデル同士の関連付けあり)
1
2
department = Department.find(1)
employees = department.employees

アソシエーションの定義

アソシエーションは、モデルのクラスに定義します。モデル同士の関連付けの種類はいくつかありますが、belongs_tohas_manyがよく使われます。

has_manyの場合、:関連モデル名複数形にします。

モデルのクラス
1
2
3
class クラス名 < ApplicationRecord
  belongs_to :関連モデル名
end
モデルのクラス
1
2
3
class クラス名 < ApplicationRecord
  has_many :関連モデル名
end

アソシエーションを定義する場合、テーブル同士の関係性を整理しておく必要があります。

belongs_toの関係性

employeesテーブルとdepartmentsテーブルの関係性は、以下のように複数の社員が各部署に必ず属しています。社員は部署がなければ存在しません。

外部キー制約の設定

社員は必ずどれか1つの部署に属している(Employee belongs to a Department)関係性です。このような関係性の場合、Employeeモデルのクラスにbelongs_toを使います。belong toは「〜に属する」という意味です。

belong toを利用して、EmployeeモデルとDepartmentモデルを関連付けましょう

Employeeモデルは、Departmentモデルに属す関係です。この関係性でモデル同士を関連付けるには、Employeeモデルのクラスにbelongs_to :departmentを指定します。

Employeeモデルのクラスにbelongs_to :departmentを追加しましょう。

app/models/employee.rb
1
2
3
4
class Employee < ApplicationRecord
belongs_to :department
validates :name, presence: true, length: { maximum: 30 } end

has_manyの関係性

belongs_toのときは、複数の社員は1つの部署に属すという関係でしたが、部署側からはどのようにみえるでしょうか。

外部キー制約の設定

部署側側から社員をみると、1つの部署は複数の社員を持つ(Department has many Employees)関係性です。このような関係を1:nと呼びます。部署1つに対して社員は0以上あるので、nと表現しています。

1:nの関係性の場合、Departmentモデルのクラスにhas_manyを使います。

has_manyを利用して、DepartmentモデルとEmployeeモデルを関連付けましょう

1つのDepartmentモデルは複数のEmployeeモデルを持つ関係です。この関係性でモデル同士を関連付けは、Departmentモデルのクラスにhas_many :employeesを指定します。

Departmentモデルのクラスにhas_many :employeesを追加しましょう。

app/models/department.rb
1
2
3
4
class Department < ApplicationRecord
has_many :employees
validates :name, presence: true, uniqueness: true, length: { maximum: 30 } end

has_manyの関連モデル名は、複数形になるので注意してください。

Departmentモデル経由でEmployeeモデルの情報を取得

アソシエーションで、DepartmentモデルとEmployeeモデルの関連付けをしたことで、Departmentモデル経由でEmployeeモデルの情報を取得することができます。

モデル同士の関連付け
1
2
3
4
5
6
7
class Department < ApplicationRecord
  has_many :employees
end

class Employee < ApplicationRecord
  belongs_to :department
end
rails db:migrate:resetを利用して、データベースを作り直しましょう

ターミナルで以下のコマンドを実行して、テーブルを作り直しましょう。
rails cで起動したコンソールではなく、ターミナルで実行します。

ターミナル | カレントディレクトリをemployee_managementに変更する
1
cd ~/environment/employee_management
ターミナル | データベースを作り直す
1
rails db:migrate:reset

上記を実行すると、以下のようにデータベースの削除、作成、マイグレーション実行までされます。

コンソールを再読み込みしましょう

コンソールに最新情報を反映させるため、起動中のコンソールにreload!と打ち込み、実行しましょう。

※ターミナルではなく、rails cコマンドで起動させたコンソールで行います。

コンソール | 再読み込みする
1
reload! 
departmentsテーブルとemployeesテーブルのデータを入れ直しましょう

departmentsテーブルに挿入するデータは、以下の通りです。

id name
1 開発
2 営業

まずは、以下のコードをコンソールで実行し、departmentsテーブルに上記のレコードを挿入しましょう。

※ターミナルではなく、rails cコマンドで起動させたコンソールで行います。

コンソール
1
2
3
4
Department.create([
  { name: '開発' },
  { name: '営業' }
])

employeesテーブルに挿入するデータは、以下の通りです。

id name birthday department_id
1 田中太郎 1980-10-22 1
2 山田花子 1983-08-20 1
3 高橋一朗 1986-06-16 2
4 伊藤晴子 1987-10-01 1
5 鈴木二郎 1990-01-17 2
6 山口冬子 1993-05-12 2

employeesテーブルに複数レコードを挿入するために、以下のコードをコンソールに記述し、実行しましょう。

コンソール
1
2
3
4
5
6
7
8
Employee.create([
  { name: '田中太郎', birthday: '1980-10-22', department_id: 1 },
  { name: '山田花子', birthday: '1983-08-20', department_id: 1 },
  { name: '高橋一朗', birthday: '1986-06-16', department_id: 2 },
  { name: '伊藤晴子', birthday: '1987-10-01', department_id: 1 },
  { name: '鈴木二郎', birthday: '1990-01-17', department_id: 2 },
  { name: '山口冬子', birthday: '1993-05-12', department_id: 2 }
])
Departmentモデル経由でEmployeeモデルの情報を取得しましょう

コンソールで以下のコードを実行し、Employeeモデルの情報を取得しましょう。

コンソール | Departmentモデル経由でEmployeeモデルの情報を取得
1
2
department = Department.find(1)
department.employees

上記を実行すると、以下のように開発に所属する社員の情報が取得することができます。

開発に所属する社員は、以下のように田中太郎さん、山田花子さん、伊藤晴子さんです。

この記事のまとめ

  • ActiveRecordでは「モデル」と「テーブル」だけではなく、「インスタンス」と「レコード」、「プロパティ」と「カラム」も1対1で紐付けられる
  • バリデーションとは、入力されたデータが有効であるかを検証する仕組みのこと
  • アソシエーションとは、モデル同士の関連付けを行う機能のこと