CRUD
ActiveRecordは、CRUDの機能を提供します。
CRUDとは、Create(作成)・Read(読み込み)・Update(更新)・Delete(削除)の頭文字を繋げた用語で、データ操作の基本となる4つの処理のことでしたね。
SQLでは、CRUDに対応するデータ操作言語のキーワードとしてINSERT
・SELECT
・UPDATE
・DELETE
が用意されており、これらを利用してデータ操作を学習しました。
CRUD | SQL文 | SQL文の説明 |
---|---|---|
Create(作成) | INSERT文 | テーブルにレコードを挿入する |
Read(読み込み) | SELECT文 | テーブルのレコードを抽出する |
Update(更新) | UPDATE文 | テーブルのレコードを更新する |
Delete(削除) | DELETE文 | テーブルのレコードを削除する |
Railsでは、ActiveRecordによってCRUDに対応する便利なメソッドがたくさん用意されており、これらのメソッドを利用してデータ操作することができます。
ActiveRecordが提供するメソッドやクラスを利用することで「オブジェクトを操作するようにデータ操作できる」と説明しましたね。データ操作のメソッドを学習する前に、イメージとして理解しておきましょう。
オブジェクトを操作するようにデータ操作できる
ActiveRecordの規約で少し触れましたが、ActiveRecordでは「モデルのクラス」と「データベースのテーブル」が1対1で紐付けられます。
例えば、以下のようにモデルのUser
クラスの場合は、データベースのusers
テーブルに紐付きます。
1
2
class User < ApplicationRecord
end
ActiveRecordでは「モデル」と「テーブル」だけではなく、「インスタンス」と「レコード」、「プロパティ」と「カラム」も1対1で紐付けられます。
モデルクラスのインスタンスは、テーブルのレコード1件に対応するオブジェクトになり、オブジェクトのプロパティは、テーブルのカラムに対応します。
Rubyのクラスとインスタンスの章で学習しましたが、インスタンスを生成するには、クラスに対して「newメソッド」を呼び出します。
1
クラス名.new
モデルクラスの場合でも、同じように「newメソッド」を呼び出してインスタンスを生成します。しかし、通常のクラスとは違って生成されたインスタンスは、「テーブルのレコード1件」に対応するオブジェクトになります。
1
2
モデルのクラス名.new
#レコード1件に対応するオブジェクトになる
モデルクラスのインスタンス(オブジェクト)に対して、ActiveRecordが提供するメソッドを利用することで、テーブルにレコードを挿入したり、更新や削除などが行えます。まさにオブジェクトを操作するように、データ操作ができます。
例えば、Userモデルクラスのインスタンスに対して、ActiveRecordが提供するsaveメソッドを呼び出せば、テーブルにデータを挿入することができます。
1
2
3
4
5
# Userモデルのインスタンスをuserに代入する
user = User.new(name: "山田花子", age: 34)
# saveメソッドでインスタンスをデータベースに保存する
user.save
メソッドの箇所で詳しく説明しますが、モデルのクラスに対してnewメソッドを呼び出した時点では、「テーブルのレコード1件」に対応するオブジェクトが生成されただけです。実際にデータベースに保存するのは、saveメソッドが行います。
SQLではINSERT構文を使用して、テーブルに新しいレコードを挿入しましたが、RailsではActiveRecordのお陰で、オブジェクトを操作するようにデータ操作が可能になります。
1
INSERT INTO users(name, age) VALUES('山田花子', 34);
1
2
user = User.new(name: "山田花子", age: 34)
user.save
ちなみにモデルのインスタンスに対してsave
メソッドを呼び出すと、裏側では以下のようなSQL文が発行されます。実際にはINSERT構文を使用して、テーブルにレコードを挿入していることがわかりますね。
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_at
やupdated_at
の値は、発行されるSQL文により、「レコード作成時に現在の日時を自動的に設定する」ということを確認できます。
CRUD系のメソッドってたくさんありそう。理解できるかな・・・。
ここでは基本的なメソッドだけを押さえるよ。SQLは学習済みだから、裏側で発行されるSQL文を確認すると、そのメソッドが何をしているのか理解しやすくなるかも!
準備:コンソールを起動しよう
データ操作は、コンソール上で行います。Railsでは、console
コマンドでコンソールを起動させて、Railsアプリケーションとコマンドラインでやり取りすることができます。
Webにアクセスすることなく、サーバーのデータを変更するときやメソッドを試すときに便利です。内部ではirbが使われているので、同じように操作を行えます。
1
2
rails console
# rails c でも可能
他にもオプションの-sandbox
を付けることで、終了時にコンソールで変更したデータを元に戻すことができます。
1
2
rails console -sandbox
# rails c -s でも可能
上記のコマンドを実行すると、下の画像のようにin sandbox
が表示されます。
コンソールを終了するには、exit
かquit
を実行します。
1
2
3
exit
#もしくは、以下を実行する
quit
今回は、実際にデータベースにデータを反映させていくので、Sandboxモードではなく、通常モードでコンソールを起動させます。
以下のコマンドを実行して、コンソールを起動してください。
1
cd ~/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)
1
User.create(name: "山田花子", age: 34)
以下をコンソールで実行し、nameカラムの値が"開発"
のレコードをdepartments
テーブルに挿入しましょう。
1
Department.create(name: "開発")
上記を実行すると、以下のように裏側でSQL文が発行されます。SQLの章で学習したようにINSERT構文で、テーブルに新しいレコードを挿入していることがわかりますね。
created_at
やupdated_at
には、現在の日時が自動的に設定されます。
phpMyAdminで確認すると、以下のようにdepartmentsテーブルにnameカラムの値が開発
のレコードが1件挿入されています。また、idも自動的に値が入っていますね。
newメソッド/saveメソッド
save
メソッドは、モデルのインスタンスをデータベースに保存します。
createメソッドとは違い、saveメソッドはインスタンスを生成しません。そのため、インスタンス生成を行うnewメソッドとセットで利用します。
1
2
3
4
5
# Userモデルのインスタンスを生成して、userに代入する
user = User.new(name: "山田花子", age: 34)
# saveメソッドでインスタンスをデータベースに保存する
user.save
モデルのクラスに対してnewメソッドを呼び出した時点では、「テーブルのレコード1件」に対応するオブジェクトが生成されただけです。実際にデータベースに保存するのは、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
nameカラムの値が"営業"
のレコードをdepartmentsテーブルに挿入してみます。
まずは、newメソッドを利用して、Departmentモデルのインスタンスを生成します。以下のコードをコンソールに記述し、実行しましょう。
1
department = Department.new(name: "営業")
上記を実行すると、以下のようにDepartmentモデルのインスタンスが生成されます。この時点では、newメソッドの引数で指定したname属性のみ値が設定されており、idやcreated_atとupdated_atの属性値は、いずれもnil
で値が何も入っていない状態です。
続いて、生成したDepartmentモデルのインスタンスをsaveメソッドでデータベースに保存します。以下のコードをコンソールに記述し、実行しましょう。
1
department.save
上記を実行すると、以下のように裏側でSQL文が発行されます。INSERT構文で、テーブルに新しいレコードを挿入していることがわかりますね。
created_at
やupdated_at
には、現在の日時が自動的に設定されます。
phpMyAdminで確認すると、以下のようにdepartmentsテーブルにnameカラムの値が営業のレコードが1件挿入されています。また、idも自動的に値が入っていますね。
そして、Departmentモデルのインスタンスが入るdepartment
をコンソールで実行すると、以下のようにnewメソッドを呼び出した時点では、nil
だった属性値に値が設定されていることがわかります。
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テーブルに挿入するデータは、以下の通りです。
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件挿入していることがわかりますね。
phpMyAdminでemployeesテーブルを表示して、createメソッドで指定した6件のレコードがテーブルに挿入されていることを確認しましょう。
Read
Readは、「テーブルのレコードを抽出する」というデータ操作の基本処理を指します。
Railsの場合は、ActiveRecordが提供するall
メソッド、find
メソッドなどを利用することで、テーブルのレコードを抽出することができます。
メソッド | 説明 |
---|---|
all | 全てのレコードを取得する |
find | 「ID」に当てはまるレコードを全て取得する |
find_by | 「条件」を指定して最初の1件を取得する |
where | 「条件」に当てはまるレコードを全て取得する |
allメソッド
モデルのクラスに対してall
メソッドを呼び出すと、モデルに対応するテーブルのレコードをすべて取得します。
1
モデルのクラス名.all
例えば、usersテーブルのレコードを全て取得する場合はUser.all
と記述します。
1
User.all
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のレコードを取得します。
1
User.find(1)
以下のように指定すると、id
の値が1
と2
のレコードを取得します。
1
User.find([1,2])
employeesテーブルのidの値が4
のレコードを取得してみます。
以下のコードをコンソールに記述し、実行しましょう。
1
Employee.find(4)
上記を実行すると、以下のように裏側でSQL文が発行されます。SELECT構文で、employeesテーブルのidの値が4
のレコードを抽出していることがわかりますね。
戻り値は、取得したデータの値がセットされているEmployeeモデルのインスタンスです。
find_byメソッド
find_byメソッドは、引数の条件に当てはまる最初の1件のレコードを取得するクラスメソッドです。
引数の条件は、ハッシュで指定します。
1
モデルのクラス名.find_by(条件)
1
User.find_by(name: '田中太郎')
employeesテーブルのname
の値が'鈴木二郎'
のレコードを取得してみます。
以下のコードをコンソールに記述し、実行しましょう。
1
Employee.find_by(name: '鈴木二郎')
上記を実行すると、以下のように裏側でSQL文が発行されます。SELECT構文で、employeesテーブルのname
の値が'鈴木二郎'
のレコードを抽出していることがわかりますね。
戻り値は、取得したデータの値がセットされているEmployeeモデルのインスタンスです。
whereメソッド
whereメソッドは、引数の条件に当てはまるレコードを全て取得するクラスメソッドです。
1
モデルのクラス名.where(条件)
find_byメソッドの場合は、引数の条件に当てはまる最初のレコードを1件だけ取得しますが、whereメソッドは、条件に当てはまる全てのレコードを取得します。
例えばUser.where(age: 20)
とUser.find_by(age: 20)
のように、引数に同じ条件を指定しても、取得するレコード数は違います。
1
User.where(age: 20)
1
User.find_by(age: 20)
以下のようにUser.where(age: 20)
は、age
が20
のレコードを全て取得します。
employeesテーブルの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
メソッドを呼び出して、データベースに保存します。
1
2
3
user = User.find_by(name: '田中太郎') #更新対象のオブジェクトを取得
user.name = '田中航太郎' #プロパティを変更
user.save #データベースに保存する
updateメソッドを利用すると、上のコードを短く書けるよ!
updateメソッド
updateメソッドを利用する場合も、あらかじめfind
メソッドやfind_by
メソッドなどで、オブジェクトを取得してから更新処理を行います。
updateメソッドの引数にプロパティ名と設定したい値をハッシュで対応付けます。
1
更新対象のオブジェクト.update(プロパティ名: 値)
先ほどのsaveメソッドと同様に'田中太郎'
を'田中航太郎'
に更新する場合は、更新対象のオブジェクトを取得後、以下のようにupdateメソッドの引数を指定します。
updateメソッドを呼び出すと、更新対象のオブジェクトのプロパティを引数の値に更新し、その結果をデータベースに保存します。
1
2
user = User.find_by(name: '田中太郎')
user.update(name: '田中航太郎')
employeesテーブルにある山口冬子さんの生年月日、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句にはどのレコードを更新するかを識別する条件が指定されており、id
が6
のレコードのbirthday
を'1993-12-12'
に更新していることがわかりますね。
また、レコードの更新時に現在の日時を設定するupdated_at
の値も自動的に更新されます。データが更新されると、戻り値としてtrue
を返します。
オブジェクトのプロパティ値が更新されていることを確認してみましょう。更新対象のオブジェクトを保持するemployee
をコンソールに記述し、実行しましょう。
1
employee
上記を実行すると、以下のように更新後のbirthday
のプロパティ値は、更新前と比べてupdateメソッドに指定した値に変更されていることがわかりますね。
phpMyAdminを再読み込みして、employees
テーブル内の山口冬子さんのbirthday
が1993-05-12
から1993-12-12
に更新されていることを確認しましょう。
また、updated_at
の値も更新した日時に変更されたかを確かめてみてください。
Delete
Deleteは、「テーブルのレコードを削除する」というデータ操作の基本処理を指します。
Railsの場合は、ActiveRecordが提供するdestroy
メソッド、destroy_all
メソッドなどを利用することで、テーブルのレコードを削除することができます。
メソッド | 説明 |
---|---|
destroy | 既存のレコードを削除する |
destroy_all | 指定した条件のレコードと関連しているレコードをすべて削除する |
destroyメソッド
destroyメソッドを利用する場合、あらかじめfind
メソッドやfind_by
メソッドなどで、オブジェクトを取得してから削除処理を行います。
削除対象のオブジェクトに対して、destroyメソッドを呼び出します。
1
削除対象のオブジェクト.destroy
例えば、users
テーブルのname
カラムの値が'田中太郎'
のレコードを削除するには、以下のように、まずオブジェクトを取得します。そして、取得したオブジェクトに対してdestroyメソッドを呼び出し、データベースからレコードを削除します。
1
2
user = User.find_by(name: '田中太郎') #削除対象のオブジェクトを取得する
user.destroy #レコードを削除する
employeesテーブルにある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
と記述します。
1
User.destroy_all
以下のコードを上から順番にコンソールで実行し、employees
テーブルのレコードから削除するようにしましょう。
1
Employee.destroy_all
1
Department.destroy_all
phpMyAdminを再読み込みして、departmentsテーブルとemployeesテーブル内のレコードが全て削除されたかを確認しましょう。
バリデーション(検証)
バリデーション(validation)とは、入力されたデータが有効であるかを検証する仕組みのことです。Webアプリケーションでは、さまざまな場所からデータを受け取りますが、バリデーションはデータベースに書き込む前に「データが有効かどうか」を検証します。
例えば、お問い合わせフォームの必須項目に対して、データの存在性(データがあるべき箇所に存在しているか)を検証することができます。1項目でも未入力があった場合は、以下のように送信されず、データベースに保存されません。
このような検証処理を1から実装するのは大変です。Active Recordのバリデーション機能を利用することで、シンプルなコードで検証を定義することができます。
Active Recordのバリデーション機能
CRUDで学習しましたが、RailsではActiveRecordが提供するメソッドを利用することで、オブジェクトを操作するようにデータ操作が可能でしたよね。
1
INSERT INTO users(name, age) VALUES('山田花子', 34);
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)の検証を行う場合は、以下のように定義します。
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
コマンドで起動させましょう以下のコマンドを実行して、コンソールを起動してください。
1
cd ~/environment/employee_management
1
rails c
上記のコマンドを実行すると、以下のように表示されます。
Departmentモデルにバリデーションを設定する前でも、name
属性にnil
をセットした場合、データベースに保存されません。
以下のコードをコンソールに記述し、実行しましょう。
1
2
department = Department.new(name: nil)
department.save
上記を実行すると、以下のようなエラーが発生します。
departmentsテーブルのname
カラムには「非NULL制約」が設定されるので、そもそもNULL
を格納することができません。
非NULL制約を設定してるのに、モデルのバリデーションって必要なの?
非NULL制約はNULL
を禁止するけど、空文字や空白文字は保存されてしまうんだ。だからモデルのバリデーションは必要だよ!
以下のコードをコンソールに記述し、実行しましょう。
1
2
department = Department.new(name: " ")
department.save
上記を実行すると、以下のようにINSERT操作が行われてtrue
が返ります。
phpMyAdminを再読み込みして、departments
テーブルにname
カラムの値が空白文字のレコードが1件挿入されていることを確認しましょう。
presence: true
を設定しましょう空文字や空白文字もデータベースに保存できないように、Departmentモデルのname属性にpresence: true
を設定しましょう。
1
2
3
class Department < ApplicationRecord
validates :name, presence: true
end
reload!
と打ち込み、モデルの最新情報を反映させましょうコンソールを立ち上げている最中にモデルの内容を変更した場合、コンソールにはすぐに反映されません。コンソールにreload!
と打ち込むことで、再起動せずに最新のモデル情報を反映させることができます。
コンソールにreload!
と打ち込み、実行しましょう。
1
reload!
以下のようにtrue
を返せば、再読み込みできています。
以下のコードをコンソールに記述し、実行しましょう。
1
2
department = Department.new(name: " ")
department.save
上記を実行すると、以下のようにINSERT操作が行われず、false
が返ります。
Departmentモデルのname
属性にpresence: true
を設定したことで、saveメソッドが実行される際に自動でインスタンスのname属性の値が空ではないかを検証します。
name属性には空白文字をセットしたので、インスタンスは無効となり、データベースに保存されません。
presence: true
を設定しましょう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
が返ります。
一意性の検証
一意性(uniqueness)は、指定した属性の値が一意で重複しないことを検証します。このバリデーションは、「指定した属性と同じ値を持つ既存のレコード」がモデルに対応するテーブルにあるかどうかを調べてくれます。
一意性の検証は、validatesメソッドの引数にuniqueness: true
を指定します。
1
2
3
class クラス名 < ApplicationRecord
validates :属性名, uniqueness: true
end
それでは、Departmentモデルの部署名に対して、一意性の検証が行われるように設定してみます。
name
属性にuniqueness: true
を追加しましょうまずは、以下のコードをコンソールで実行し、部署名が'営業'
の新しいレコードをdepartmentsテーブルに挿入しましょう。
1
2
department = Department.new(name: '営業')
department.save
次にDepartmentモデルのname
属性にuniqueness: true
を追加し、一意性の検証が行われるように設定しましょう。
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 }
を追加し、長さの検証が行われるように設定してみます。
length: { maximum: 30 }
を追加しましょうDepartmentモデルのname属性にlength: { maximum: 30 }
を追加し、長さの検証が行われるように設定しましょう。
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
を返します。
length: { maximum: 30 }
を追加しましょうEmployeeモデルのname属性にもlength: { maximum: 30 }
を追加し、長さの検証が行われるように設定しましょう。
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属性に「値が空ではないか」という存在性の検証を定義していたとします。
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 #エラーメッセージを取得する
=> [] #インスタンスは有効なので、空配列が返る
それでは、エラーメッセージを取得してみましょう!
Departmentモデルのname
属性では、以下のバリデーションを設定していましたね。
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
メソッドを利用して、id
が1
の開発のレコードを取得します。
department.id
は、開発のレコードの主キーの値(1
)を取得できます。whereメソッドに外部キーを指定してid
が1
の開発部に属している社員を取得します。
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_to
とhas_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
を追加しましょう。
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
を追加しましょう。
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
で起動したコンソールではなく、ターミナルで実行します。
1
cd ~/environment/employee_management
1
rails db:migrate:reset
上記を実行すると、以下のようにデータベースの削除、作成、マイグレーション実行までされます。
コンソールに最新情報を反映させるため、起動中のコンソールにreload!
と打ち込み、実行しましょう。
※ターミナルではなく、rails cコマンドで起動させたコンソールで行います。
1
reload!
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 }
])
コンソールで以下のコードを実行し、Employeeモデルの情報を取得しましょう。
1
2
department = Department.find(1)
department.employees
上記を実行すると、以下のように開発に所属する社員の情報が取得することができます。
開発に所属する社員は、以下のように田中太郎さん、山田花子さん、伊藤晴子さんです。
この記事のまとめ
- ActiveRecordでは「モデル」と「テーブル」だけではなく、「インスタンス」と「レコード」、「プロパティ」と「カラム」も1対1で紐付けられる
- バリデーションとは、入力されたデータが有効であるかを検証する仕組みのこと
- アソシエーションとは、モデル同士の関連付けを行う機能のこと
この記事で学んだことをTwitterに投稿して、アウトプットしよう!
Twitterの投稿画面に遷移します