理系公務員のプログラミング日記

【Laravel9×Vue3】本の貸し出しシステム7(本の個別ページ続き)

タグ:
Laravel
Vue

個別ページから本を借りる機能を呼び出す

個別ページで本の詳細を確認した後、気に入った場合は本を借りることができると良いと思います。

BookDetail のコンポーネントに本を借りる機能を追加します。

要件の整理

必要な要件としては、次のようなものが考えられます。

・本を借りることができるかを表示する機能
・本を借りることが出来る場合、いつまで借りることが出来るかを表示する機能
・本を借りることができない場合、いつから借りることが出来るかを表示する機能
・実際に本を借りることを決定する機能

モーダルの追加

BookDetail 本来の本の情報を表示する機能と、本を借りるための機能を同じ画面に収めるのは窮屈に感じるので、ボタンを押したらモーダルが表示されるようにし、モーダルのなかで本を借りるための機能を動作させることにしました。

モーダルは BootStrap からそのまま引っ張ってみます。

BootStrap5 modal

BookDetail.vue

<template>のみ抜粋

<template> <h2>書籍の詳細</h2> <div> <!-- 本の情報を受け取って表示させる --> <!-- P362 ルートパラメータを受け取る --> {{ bookData }} </div> <!-- Button trigger modal --> <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal"> この本を借りる </button> <!-- Modal --> <div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="exampleModalLabel">この本を借りる</h5> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body"> (2週間後の日付)まで借りる or (レンタル中のため)借りることが出来ないことを表示 </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">キャンセル</button> <button type="button" class="btn btn-primary" v-on:click="rentBook">決定</button> </div> </div> </div> </div> </template>

モーダルのサンプル画像

モーダルからバックエンドのAPIにリクエストを送る

上記は、BootStrapのサンプルからテキストを変更した上で、モーダル内のボタンのクリックイベントでメソッドを呼び出せるように、v-on:click="rentBook"を追加しています。

このイベントで動作させるrentBook()を下記のようにmethodsディレクティブに追加します。

BookDetail.vue

<script>内に追記

// 略 methods: { async rentBook() { // APIに対して、借りる処理を送る // loansテーブルのapiに、book_id,user_id,loan_date,return_dateを送信する処理 const url = "/api/loans"; const response = await axios.get(url); console.log(response.data); } }

リクエストを受け取るAPIの作成

フロントエンド側の準備は完了したので、バックエンド側を整備していきます。

要件定義より、本を借りたという情報はLoansテーブルに記録することになっているので、このテーブルとモデル、コントローラーを作成します。

作成の流れとしては、最初に作ったbooksテーブルと同じです。

マイグレーションの作成

php artisan make:migration create_loans_table

このloansテーブルは、booksテーブルとusersテーブルの中間テーブルとして存在するため、この二つのテーブルの主キーを外部キーとして持っています。

migrations/create_loans_table

public function up() { Schema::create('loans', function (Blueprint $table) { $table->increments('loan_id'); // 通常のキーとして追加 $table->unsignedInteger('book_id'); $table->unsignedBigInteger('user_id'); // 外部キー制約を追加 $table->foreign('book_id')->references('book_id')->on('books'); $table->foreign('user_id')->references('id')->on('users'); $table->date('loan_date'); $table->date('return_date'); $table->timestamps(); }); }

モデルの作成

php artisan make:model Loan

app/Models/Loan.php

class Loan extends Model { // 主キーの列名を指定 protected $primaryKey = 'loan_id'; // 入力可能な列名を指定(これら以外はDBで自動入力) protected $fillable = [ 'book_id', 'user_id', 'loan_date', 'return_date' ]; }

コントローラーの作成

php artisan make:controller LoanController

LoanController.php

<?php namespace App\Http\Controllers; use Illuminate\Http\Request; // 追加 use App\Models\Loan; class LoanController extends Controller { // 現在の貸出状況を表示 public function index() { $loans = Loan::all(); // eloquantをそのままreturnすると、jsonに変換してくれる。 return $loans; } // 新しい貸出状況を追加 public function store(Request $request) { // モデルの空のインスタンスを生成 $loan = new Loan(); // 受け取ったリクエストのデータを全て取得 $form = $request->all(); // 受け取ったデータをインスタンスに挿入し、DBに保存 $loan->fill($form)->save(); } }

ここまで書いたら詳細ページのモーダルからgetリクエストを送り、動作に問題ないか確認してください。

BookDetailからAPIにデータをpostする

詳細ページのモーダルからはgetリクエストではなく,この本を借りるというpostリクエストを送りたいです。

axios.postを利用してLoanテーブルに合うデータの形のオブジェクトを送信します。

ここでthis.$route.params.idは、この詳細ページの番号(ルーティングパラメータ)です。

日付については仮の値を入力し、次のページで解説します。

components/BookDetail.vue

// 略 methods: { async rentBook() { // loansテーブルのapiに、book_id,user_id,loan_date,return_dateを送信する処理 const url = "/api/loans"; // この本を借りることをAPIに送る const response = await axios.post( url, { book_id: this.$route.params.id, user_id: 2, loan_date: '2023/02/01', return_date: '2023/02/14' } ); } }