面談記録管理アプリ開発:データベース構築
はじめに
前回で開発環境の構築を行いました。
前回の記事はこちら↓
今回は、データーベースの実装を行っていきたいと思います!
フィーチャーブランチの作成
今回は、データベース構築部分の実装していきたいので、それ専用のブランチを作成します。
作成するには以下のコマンドを実行します。
ブランチ名はdatabase
にしています。
git flow feature start database
ブランチが作成されているか確認します。
git branch
develop
* feature/database
main
無事作成されていることがわかりました。
ブランチをGitHub上にアップロード
今のままでは、ローカル上にブランチが存在しているだけなので、GitHub上には反映していきます。
反映するには以下のコマンドを実行します。
git flow feature publish database
GitHub上で確認して、ブランチが追加さていれば成功です。
マイグレーションファイルの作成
続いて、データーベースを構築するためにマイグレーションファイルを作成して、テーブル内容を記述していきます。
今回作成したいテーブルは以下の4つ
- offices
- 事業所の情報を記録したテーブル
- members
- 施設利用者の情報を記録したテーブル
- meeting_logs
- 利用者さんとの面談記録をと保存するテーブル
- messages
- 職員間でのチャットメッセージを記録するテーブル
マイグレーションファイルを作成するには以下のコマンドを実行します。
php artisan make:migration create_offices_table
php artisan make:migration create_members_table
php artisan make:migration create_meeting_logs_table
php artisan make:migration create_messages_table
usersテーブルの設定
users
テーブルは、breeze
をインストールした際に、自動的に設定されているテーブルですが、アプリの仕様に合わせて少し修正する必要があります。
以下のように修正していきます。
////省略////
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('avatar')->nullable();
$table->string('email')->unique();
$table->foreignId('office_id')->constrained('offices')->onDelete('cascade');
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->boolean('is_archive')->default(false);
$table->boolean('is_admin')->default(false);
$table->boolean('is_global_admin')->default(false);
$table->timestamps();
});
////省略////
アイコン画像のパスを保存するためのavatar
カラム、所属する事業所を表す外部キーであるoffice_id
、管理者権限の有無を表すis_admin
とis_global_admin
カラムを追加しています。
officesテーブルの設定
続いて、事業所情報を保存するためのoffices
テーブルのマイグレーションファイルを記述していきます。
////省略////
public function up(): void
{
Schema::create('offices', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->integer('zip_code');
$table->string('address');
$table->string('phone_number')->nullable();
$table->boolean('is_archive')->default(false);
$table->timestamps();
});
}
////省略////
電話番号はnull
でも良いとしています。
membersテーブルの設定
また、施設利用者の情報を保存するためのmembers
テーブルのマイグレーションファイルを記述していきます。
////省略////
public function up(): void
{
Schema::create('members', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('sex');
$table->foreignId('office_id')->constrained('offices')->onDelete('cascade');
$table->string('status');
$table->string('characteristics');
$table->string('notes')->nullable();
$table->timestamps();
});
}
////省略////
性別を表すsex
はboolean
で表現しようと思いましたが、男女以外の表現方法を想定してstring
としています。
また、利用している事業所をと関連付けるためにoffices
テーブルからoffice_id
を外部キーとして取得しています。
meeging_logsテーブルの設定
さらに、面談内容を記録するためのmeeting_logs
テーブルのマイグレーションファイルを記述します。
////省略////
public function up(): void
{
Schema::create('meeting_logs', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
$table->foreignId('member_id')->constrained('members')->onDelete('cascade');
$table->integer('condition');
$table->longText('meeting_log');
$table->timestamps();
});
}
////省略////
面談を行った職員と受けた利用者とを関連付けるためにmembers
/users
テーブルからIDを外部キーとして持ってきています。
messagesテーブルの設定
最後に、職員間のチャットメッセージを記録するmessages
テーブルのマイグレーションファイルを記述します。
////省略////
public function up(): void
{
Schema::create('messages', function (Blueprint $table) {
$table->id();
$table->longText('message');
$table->foreignId('sender_id')->constrained('users');
$table->foreignId('meeting_logs_id')->constrained('meeting_logs')->onDelete('cascade');
$table->timestamps();
});
}
////省略////
送信者と受信者を判別するためにusers
テーブルからIDを外部キーとして取得しています。
また、各メッセージは各面談に紐付いているため、meeting_logs
テーブルからIDを外部キーとして持ってきています。
モデルとファクトリーの作成
Office
Officeモデルとファクトリーの生成には以下のコマンドを実行します。
php artisan make:Model Office -f
生成されたMember
モデルを以下のように修正します。
////省略////
class Office extends Model
{
use HasFactory;
protected $fillable = [
'name',
'zip_code',
'address',
'phone_number',
];
public function users()
{
return $this->hasMany(User::class);
}
public function members()
{
return $this->hasMany(Member::class);
}
}
$fillable
には、複数カラムのへの同時挿入を許可するカラムを指定します。
ここで指定されていないカラムへの挿入を他カラムとの同時挿入で行った場合、例外エラーが出たり、許可していないカラムのデータが正しく記録されなかったりします。
また、users
/members
テーブルとは1対多の関係になっているので、$this->hasMany()
を用いて関係性を表現しています。
この時点では、Member
モデルを作っていないのでエラーが出ていると思います。
ファクトリは以下のようにか記述していきます。
////省略////
class OfficeFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => 'ITスクール'. fake()->city(),
'zip_code' => fake()->postcode(),
'address' => fake()->address(),
'phone_number' => fake()->phoneNumber(),
'created_at' =>fake()->dateTimeBetween('-1 year', 'now'),
];
}
}
fake()
メソッドを使って、各カラムにランダムな値を生成するようにしています。
created_at
カラムには、dateTimeBetween()
メソッドを使って、生成日時から1年以内の日時を生成するようにしています。
User
breeze
インストール時点でモデルとファクトリーが生成されているので、それらを修正していきます。
まずはモデルから、
////省略////
class User extends Authenticatable
{
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'name',
'avatar',
'email',
'office_id',
'email_verified_at',
'password',
'is_admin',
'is_main_office',
];
////省略////
public function office()
{
return $this->belongsTo(Office::class);
}
}
User
はOffice
多対1の関係なので、belongsTo()
メソッドでそれを表現しています。
続いて、ファクトリです。
////省略////
public function definition(): array
{
$officeId = fake()->randomElement(\App\Models\Office::pluck('id')->toArray());
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'),
'office_id' => $officeId,
'remember_token' => Str::random(10),
];
}
////省略////
office_id
は、offices
テーブルに存在しているIDの中でランダムなものを割り当てるようにしています。
Member
続いて、Member
モデルとファクトリを生成していきます。
Office
生成時と同様に、以下のコマンドを実行します。
php artisan make:Model Member -f
生成されたモデルとファクトリを記述していきます。
まずはモデルから、
class Member extends Model
{
use HasFactory;
protected $fillable = [
'name',
'sex',
'office_id',
'status',
'characteristics',
'notes',
];
public function office()
{
return $this->belongsTo(Office::class);
}
public function meetingLogs()
{
return $this->hasMany(MeetingLog::class);
}
}
members
テーブルは、offices
テーブルに対して多対1の関係で、'meeting_logs'テーブルに対して1対多の関係なので、office()
/meetingLogs()
メソッドでその関係性を表現しています。
次に、ファクトリです。
////省略////
public function definition(): array
{
$officeId = fake()->randomElement(\App\Models\Office::pluck('id')->toArray());
return [
'name' => fake()->name(),
'sex' => fake()->randomElement(['男性', '女性', 'その他']),
'office_id' => $officeId,
'status' => fake()->randomElement(['利用中', '利用中止', '利用終了']),
'characteristics' => fake()->realText(10),
'notes' => fake()->realText(100),
'created_at' => fake()->dateTimeBetween('-1 year', 'now'),
];
}
////省略////
User
ファクトリと同様に、$officeId
はoffices
テーブルに存在するID
からランダムにIDを取得しています。
sex
/status
は、randomElement
メソッドを使用して、出力候補の中からランダムに値を取得します。
created_at
はOffice
ファクトリと同様に、dateTimeBetween
メソッドを用いて、シード実行時から1年以内のに日時を取得します。
MeetingLog
さらに、MeegingLog
モデルとファクトリを生成していきます。
生成には、以下のコマンドを実行します。
php artisan make:Model MeetingLog -f
////省略////
class MeetingLog extends Model
{
use HasFactory;
protected $fillable = [
'title',
'user_id',
'member_id',
'condition',
'meeting_log',
'last_message_id',
];
public function user()
{
return $this->belongsTo(User::class);
}
public function member()
{
return $this->belongsTo(Member::class);
}
public function messages()
{
return $this->hasMany(Message::class);
}
}
meeting_logs
テーブルは、users
/members
テーブルとそれぞれ多対1の関係なので、user()
/member()
メソッドで関係性を表現しています。
また、messages
テーブルと多対1の関係なので、messages()
メソッドで関係性を表現しています。
次に、ファクトリーです。
public function definition(): array
{
$officeId = fake()->randomElement(\App\Models\Office::pluck('id')->toArray());
$userId = fake()->randomElement(\App\Models\User::where('office_id', '=', $officeId)->pluck('id')->toArray());
$memberId = fake()->randomElement(\App\Models\Member::where('office_id', '=', $officeId)->pluck('id')->toArray());
$createdAt = fake()->dateTimeBetween('-1 year', 'now')->format('Y-m-d');
$title = fake()->randomElement(\App\Models\Member::where('id', '=', $memberId)
->pluck('name')
->toArray()) . '-' . $createdAt;
return [
'title' => $title,
'user_id' => $userId,
'member_id' => $memberId,
'condition' => fake()->randomElement([1, 2, 3, 4, 5]),
'meeting_log' => fake()->realText(200),
'created_at' => $createdAt,
];
}
Message
最後に、Message
モデルとファクトリを生成します。
これまでと同様に、以下のコマンドを実行します。
php artisan make:Model Message -f
生成されたモデルとファクトリを記述していきます。
まずモデルを以下のように記述します。
class Message extends Model
{
use HasFactory;
protected $fillable = [
'message',
'sender_id',
'meeting_logs_id',
];
public function sender()
{
return $this->belongsTo(User::class);
}
public function meetingLog()
{
return $this->belongsTo(MeetingLog::class);
}
}
次に、ファクトリを以下のように記述します。
class Message extends Model
{
use HasFactory;
protected $fillable = [
'message',
'sender_id',
'meeting_logs_id',
];
public function sender()
{
return $this->belongsTo(User::class);
}
public function meetingLog()
{
return $this->belongsTo(MeetingLog::class);
}
}
messages
テーブルは、meeting_logs
/users
テーブルと多対1の関係なので、その関係性をsender()
/meetingLog()
メソッドで表現しています。
シーダーの作成
ここまでで、各モデルとファクトリを実装することができたので、ダミーデータを実際に生成するためのシーダーを作成していきます。
シーダーの作成は、breeze
インストール時にデフォルトで生成されているDatabaseSeeder.php
を使っていきます。
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
Office::factory()->count(10)->hasUsers(10)->create();
User::factory()->create([
'name' => 'Kenberu',
'email' => 'kenberu@example.com',
'password' => bcrypt('12345678'),
'office_id' => 1,
'is_admin' => true,
'is_global_admin' => true,
'email_verified_at' => time(),
]);
User::factory()->create([
'name' => 'Hiroshi Akutsu',
'email' => 'hakutsu@example.com',
'password' => bcrypt('12345678'),
'office_id' => 1,
'is_admin' => true,
'is_global_admin' => false,
'email_verified_at' => time(),
]);
Member::factory()->count(50)->create();
MeetingLog::factory()->count(200)->create();
Message::factory()->count(20000)->create();
}
}
マイグレーション
ここまでで、データベースとダミーデータ生成の準備ができたので、実際にマイグレーションしていきます。
マイグレーションの実行には、以下のコマンドを実行します。
php artisan migrate:fresh --seed
今のままだと、おそらく以下のようなエラーが出ると思います。
0001_01_01_000000_create_users_table ................ 65.92ms FAIL
Illuminate\Database\QueryException
SQLSTATE[HY000]: General error: 1005 Can't create table `laravel`.`users` (errno: 150 "Foreign key constraint is incorrectly formed") (Connection: mysql, SQL: alter table `users` add constraint `users_office_id_foreign` foreign key (`office_id`) references `offices` (`id`) on delete cascade)
これは、users
テーブルを生成するときに、外部キーになっているoffices
テーブルのIDをがないために起きています。
laravelのマイグレーションは、database/factories
ディレクトリ内のファイルを上から順番に実行していきます。
なので、users
テーブルのマイグレーションファイルがoffices
テーブルのマイグレーションファイルよりあとに実行されるようにすれば解決します!
users
テーブルのマイグレーションファイルはデフォルトでdatabase/migrations/0001_01_01_000003_create_users_table.php
というような名前になっています。
一方、offices
テーブルのマイグレーションファイルは、2024_08_28_021056_create_offices_table.php
となっています。
なので、このoffices
のマイグレーションファイルよりあとに配置されるように、users
のファイル名を変えれば良いことになります。
しかし、users
はmeeting_logs
にて外部キーとして引っ張ってきているので、meeting_logs
よりも前に配置する必要があります。
私の場合、meeting_logs
のファイル名は2024_08_28_021215_create_meeting_logs_table.php
となっているので、users
のファイル名を2024_08_28_021200_create_users_table.php
にしました。
これで、再度マイグレーションを実行すると無事成功すると思います。
おわりに
DBの構築とダミーデータの生成ができました。
次回から、各種CRUD処理の実装を進めていきたいと思います!
ではでは!
Discussion