【Laravel】 一括編集での新規追加と削除処理の挙動

一括編集での新規追加と削除処理の挙動記事アイキャッチ プログラミング

学習の備忘録です。タスク管理アプリを作っています。

編集画面について、複数のタスクを編集して「編集」ボタンにてその結果を反映する機能を実装中ですが、+ボタンでタスクカードを新たに追加した時と、×ボタンでタスクカードを消した時の挙動が未実装でした。

それらの挙動の実装について、学習記録を以下に残します。

本記事は個人の学習記録です。内容の正確性を保証するものではありませんので、あらかじめご了承ください。

現在のコントローラーの記述

//タスク編集
    public function update(Request $request) {
        $user = User::findByToken($request);
        $targetDate = str_replace('/', '-', $request->task_date);

        foreach ($request->task_ids as $index => $id) {
            $task = Task::find($id);
            $task->update([
                'title' => $request->titles[$index],
                'priority' => $request->priorities[$index],
            ]);
        }

        return redirect()->route('tasks.index', ['date' => $targetDate]);
    }

現状では、既にあるタスクカードの中身を編集した際(タスクのタイトルを変更するなど)は、その編集結果が反映されるコードになっています。

新規追加と削除の機能を追加していく

+ボタンでの新規追加については、task_idの情報が含まれていないタスクカードを選別して、それを新しいタスクとしてデータベースに登録すればOK。

×ボタンに関しては、編集画面を表示した時と、「編集」ボタンを押した時の”画面の中に含まれているtask_idの差(消されたIDはどれなのかを特定)”を選別して削除処理を行えばOKです。

最終的にtry-catchなども加えて以下のようになりました。

    //タスク編集
    public function update(Request $request) {
        $user = User::findByToken($request);
        $targetDate = str_replace('/', '-', $request->task_date);

        //編集ボタンを押した段階で、画面に残されているIDのリストを取得
        $remainingIds = $request->input('task_ids', []);

    try {
            // トランザクション開始
            DB::beginTransaction();
            //画面から消されたタスクをDBから削除
            Task::where('user_id', $user->id)
                ->whereDate('task_date', $targetDate)
                ->whereNotIn('id', $remainingIds)
                ->delete();

            foreach ($request->titles as $index => $title) {
                if (empty($title)) continue;

                $taskId = $request->task_ids[$index] ?? null;
                $priority = $request->priorities[$index] ?? 2;

                if ($taskId) {
                    Task::updateTask($taskId, $title, $priority);
                } else {
                    //+ボタンで新規追加した時の挙動
                    Task::createTask($user->id, $targetDate, $title, $priority);
                }
            }

            DB::commit();
            return redirect()->route('tasks.index', ['date' => $targetDate])
                            ->with('success', '更新しました');
        
        } catch (\Exception $e) {
            DB::rollback();
            return redirect()->route('tasks.index', ['date' => $targetDate])
                            ->with('error', '更新に失敗しました。');
        }
    }

モデルはこう。

    //タスク登録
    //1件のタスクを登録する(共通部品)
    public static function createTask($userId, $date, $title, $priority) {
        if (empty($title)) return null;

        return self::create([
            'user_id'   => $userId,
            'task_date' => $date,
            'title'     => $title,
            'priority'  => $priority,
        ]);
    }
    // 一括登録
    public static function registerTask($request, $user) {
        foreach ($request->titles as $index => $title) {
            if (!empty($title)) {
                self::createTask(
                    $user->id, 
                    $request->task_date, 
                    $title, 
                    $request->priorities[$index] ?? 2
                );
            }
        }

        return true;
    }

    //タスク編集
    public static function updateTask($id, $title, $priority) {
        $task = self::find($id);
        if (!$task) return null;

        $task->update([
            'title'    => $title,
            'priority' => $priority,
        ]);

        return true;
    }

動作を分解してみます。

画面上に残っているタスクのIDを取得

        //編集ボタンを押した段階で、画面に残されているIDのリストを取得
        $remainingIds = $request->input('task_ids', []);

「編集」ボタンで送信されてきたリクエストの中から、task_idsを取得。こうすることで、編集ボタンを押す前と後でtask_idsを比較して、×ボタンで削除されたタスクを特定することができます。

削除処理

try-catchとDB::beginTransaction();の解説は割愛します。

            //画面から消されたタスクをDBから削除
            Task::where('user_id', $user->id)
                ->whereDate('task_date', $targetDate)
                ->whereNotIn('id', $remainingIds)
                ->delete();

エロクアントを用いたメソッドチェーンです。

where(‘user_id’, $user->id)……該当テーブルの指定のユーザーIDで絞り込みます。

->whereDate(‘task_date’, $targetDate)……whereとやっていることは同じですが、datetime型で条件と一致するデータを探して絞り込みます。時分秒を無視して日付部分だけを比較してくれる便利なメソッドです。

->whereNotIn(‘id’, $remainingIds)……$remainingIdsで取得したID以外のものを探して絞り込みます。ここで、画面から×ボタンで削除されたIDを絞り込んでいます。

->delete();……絞り込んだ結果抽出されたデータを削除します。

アップデート処理と新規追加処理

            foreach ($request->titles as $index => $title) {
                if (empty($title)) continue;

                $taskId = $request->task_ids[$index] ?? null;
                $priority = $request->priorities[$index] ?? 2;

                if ($taskId) {
                    Task::updateTask($taskId, $title, $priority);
                } else {
                    //+ボタンで新規追加した時の挙動
                    Task::createTask($user->id, $targetDate, $title, $priority);
                }
            }

foreachで回してアップデートなのか新規追加なのか判別して登録します。

foreach ($request->titles as $index => $title)

はじめはtask_idsで回していましたが、それだとtask_idsが空のまま送られてくる新規追加分の登録処理ができないため、titlesで回しています。

HTMLのこの箇所で送られているデータです。

<input type="text" name="titles[]" value="{{ $task->title }}" required>

$indexは「設定した覚えないよ!」みたいになりますが、これは配列の背番号としてPHP側で自動的に作られます。

配列なので、「0番目に入っているのはこのデータ、1番目に入っているのはこのデータ……」とデータごとに番号を割り振る必要があり、その割り振られた番号が$indexに入る、という仕組みです。

                $taskId = $request->task_ids[$index] ?? null;
                $priority = $request->priorities[$index] ?? 2;

ここは普通に送信されてきたリクエストを変数に入れています。

$taskIdはもしデータが入っていたらそのままそのデータを入れて、データがないならnullを入れます。ここで、既存のタスクなのか新規タスクなのかを選別しています。

$priorityはタスクの優先度で、デフォルト値は2(中レベル)です。優先度に「低」「高」などが選択されているのならそれを変数に入れ、設定されていないなら2を入れます。

                if ($taskId) {
                    Task::updateTask($taskId, $title, $priority);
                } else {
                    //+ボタンで新規追加した時の挙動
                    Task::createTask($user->id, $targetDate, $title, $priority);
                }

ここでアップデート処理か、新規追加処理か分岐させて処理しています。

if ($taskId)で、task_idがある、つまり既存タスクの処理を行います。

Task::updateTask($taskId, $title, $priority);で、モデルに書いてあるアップデートの処理を呼び出して実行しています。以下がそのモデルの該当部分です。

    //タスク編集
    public static function updateTask($id, $title, $priority) {
        $task = self::find($id);
        if (!$task) return null;

        $task->update([
            'title'    => $title,
            'priority' => $priority,
        ]);

        return true;
    }

public static functionだからコントローラー側でインスタンスを生成しなくてもOK!

また、elseでそれ以外の処理(新規追加処理)を実行します。

Task::createTask($user->id, $targetDate, $title, $priority);も同様に、モデルの処理を呼び出しています。

    //タスク登録
    //1件のタスクを登録する(共通部品)
    public static function createTask($userId, $date, $title, $priority) {
        if (empty($title)) return null;

        return self::create([
            'user_id'   => $userId,
            'task_date' => $date,
            'title'     => $title,
            'priority'  => $priority,
        ]);
    }

まとめ

削除については、編集ボタンを押す前と後で差分を取る→その差分から削除するIDを特定し削除処理。

アップデートと新規追加処理については、送信されてきたタスクのタイトルでループを回し、task_idがあるかないかで既存のタスクか新規タスクか判別、それぞれの処理をif分岐で実行する。

try-catchは処理の途中でエラーが発生した際にすべての動作をキャンセルして送信ボタン押下前の状態に戻すために追加。

これで編集についてのだいたいの処理はできました。

しかし編集については、「当日の操作の場合は”変更チケット”を消費しないと編集ができない、もし”変更チケット”がないなら編集画面への遷移もできない」という機能も付け加えたいので、ここからさらに改造していくことになると思います。

コメント

タイトルとURLをコピーしました