laravelの認証機構を見てみよう(11) : userのcrudを仕上げる(3) - ユーザーの削除

ユーザーの削除は

でもやった通りソフトデリートを検討するというかここではソフトデリートにする

migrationの変更

これはsoftDeletes()というのを加える。まだアプリリリース前なのでmigration本体を変更可能であろう。なのでリリースする前にある程度構造を来めておくというのは非常に重要である。

        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->timestamp('last_login_at')->nullable();
            $table->softDeletes(); // これ
            $table->rememberToken();
            $table->timestamps();
        });
    public function down(): void
    {
        $table->dropSoftDeletes();
        Schema::dropIfExists('users');
    }

そしたらfreshする

% ./vendor/bin/sail artisan migrate:fresh --seed

  Dropping all tables ........................................................ 160ms DONE

   INFO  Preparing database.

  Creating migration table .................................................... 36ms DONE

   INFO  Running migrations.

  2014_10_12_000000_create_users_table ........................................ 52ms DONE
  2014_10_12_100000_create_password_reset_tokens_table ........................ 69ms DONE
  2019_08_19_000000_create_failed_jobs_table .................................. 51ms DONE
  2019_12_14_000001_create_personal_access_tokens_table ...................... 100ms DONE
  2023_09_18_101626_create_permission_tables ................................. 657ms DONE
  2023_09_21_024514_create_activity_log_table ................................. 92ms DONE
  2023_09_21_024515_add_event_column_to_activity_log_table .................... 19ms DONE
  2023_09_21_024516_add_batch_uuid_column_to_activity_log_table ............... 21ms DONE


   INFO  Seeding database.

モデルの変更

<?php

namespace App\Models;
use Spatie\Activitylog\Models\Activity;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use Spatie\Permission\Traits\HasRoles;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; // これ 

のようにuseしてーの

class User extends Authenticatable  implements MustVerifyEmail
{
    use HasApiTokens, HasFactory, Notifiable, HasRoles, SoftDeletes;

のような。

tinkerで確認

> User::all()
[!] Aliasing 'User' to 'App\Models\User' for this Tinker session.
= Illuminate\Database\Eloquent\Collection {#7502
    all: [
      App\Models\User {#7503
        id: 1,
        name: "Admin 1",
        email: "admin1@example.com",
        email_verified_at: "2023-09-26 13:22:26",
        #password: "$2y$10$/uMyEgV5GLHNV/mX2wXMm.oOsbbwGMshnbnFhbR6209n2.w1jMCAC",
        last_login_at: null,
        deleted_at: null,
        #remember_token: "nbo0AGtzcD",
        created_at: "2023-09-26 13:22:26",
        updated_at: "2023-09-26 13:22:26",
      },
      App\Models\User {#7504
        id: 2,
        name: "Admin 2",
        email: "admin2@example.com",
        email_verified_at: "2023-09-26 13:22:26",
        #password: "$2y$10$HMZQTTs0crWFSp8LPqr9re2KXQUBnZwC9mJjUM2scS9hLJSHUhR92",
        last_login_at: null,
        deleted_at: null,
        #remember_token: "3moQw2IOcT",
        created_at: "2023-09-26 13:22:26",
        updated_at: "2023-09-26 13:22:26",
      },
    ],
  }

id=2を消してみる

> User::find(2)->delete()
= true

> User::all()
= Illuminate\Database\Eloquent\Collection {#7478
    all: [
      App\Models\User {#7488
        id: 1,
        name: "Admin 1",
        email: "admin1@example.com",
        email_verified_at: "2023-09-26 13:22:26",
        #password: "$2y$10$/uMyEgV5GLHNV/mX2wXMm.oOsbbwGMshnbnFhbR6209n2.w1jMCAC",
        last_login_at: null,
        deleted_at: null,
        #remember_token: "nbo0AGtzcD",
        created_at: "2023-09-26 13:22:26",
        updated_at: "2023-09-26 13:22:26",
      },
    ],
  }

> User::withTrashed()->get()
= Illuminate\Database\Eloquent\Collection {#7516
    all: [
      App\Models\User {#7517
        id: 1,
        name: "Admin 1",
        email: "admin1@example.com",
        email_verified_at: "2023-09-26 13:22:26",
        #password: "$2y$10$/uMyEgV5GLHNV/mX2wXMm.oOsbbwGMshnbnFhbR6209n2.w1jMCAC",
        last_login_at: null,
        deleted_at: null,
        #remember_token: "nbo0AGtzcD",
        created_at: "2023-09-26 13:22:26",
        updated_at: "2023-09-26 13:22:26",
      },
      App\Models\User {#7518
        id: 2,
        name: "Admin 2",
        email: "admin2@example.com",
        email_verified_at: "2023-09-26 13:22:26",
        #password: "$2y$10$HMZQTTs0crWFSp8LPqr9re2KXQUBnZwC9mJjUM2scS9hLJSHUhR92",
        last_login_at: null,
        deleted_at: "2023-09-26 13:25:17",
        #remember_token: "3moQw2IOcT",
        created_at: "2023-09-26 13:22:26",
        updated_at: "2023-09-26 13:25:17",
      },
    ],
  }

消えていない事が確認できるし、deleted_atが散見される。って感じでrestoreはやらないから、あとはマニュアルとか見といて。

削除リンク

これはDropdownに生やす


このa@a.aを消しこみたいとする。つーかね、本当はid=1を消せたりすると問題なんすけどね。まあそれは思い思い実装してみてくださいな。

resources/js/Pages/Users/Index.jsx

<Dropdown>
    <Dropdown.Trigger>
        <button>
            <VscEllipsis />
        </button>
    </Dropdown.Trigger>
    <Dropdown.Content>
        <Link href={route('users.edit', user.id)} className="block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:bg-gray-100 transition duration-150 ease-in-out">
            {t("Edit")}
        </Link>
    </Dropdown.Content>
</Dropdown>

この部分に

<Dropdown>
    <Dropdown.Trigger>
        <button>
            <VscEllipsis />
        </button>
    </Dropdown.Trigger>
    <Dropdown.Content>
        <Link href={route('users.edit', user.id)} className="block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:bg-gray-100 transition duration-150 ease-in-out">
            {t("Edit")}
        </Link>
    </Dropdown.Content>
</Dropdown>

単純にコピペすると

こうなるってうぉっ、厳しいねw マージンをもうちょい取ってもいいかも。

ただ、今Deleteボタンを押しても作用しない。これをdestroyに向ける前に、destroyを例によってdumpしとく

    public function destroy(Request $request, User $user)
    {
        dd($user); // 削除対象ユーザー
    }

そしたらdeleteメソッドを発行する、んだけど、これをこのまんまLinkで遅るのはキツそう

<Dropdown>
    <Dropdown.Trigger>
        <button>
            <VscEllipsis />
        </button>
    </Dropdown.Trigger>
    <Dropdown.Content>
        <Link href={route('users.edit', user.id)} className="block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:bg-gray-100 transition duration-150 ease-in-out">
            {t("Edit")}
        </Link>
        <button onClick={() => handleDelete(user.id)} className="block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:bg-gray-100 transition duration-150 ease-in-out">
            {t("Delete")}
        </button>
    </Dropdown.Content>
</Dropdown>

上部に関数を用意した

export default function UserIndex({ auth, users }) {
    const { t } = useLaravelReactI18n();
    const dayjs = useConfiguredDayjs();

    const handleDelete = () => {
        alert("clicked");
    }


そしたらば、deleteメソッドをとりあえず発行してみよう。

これは今までのuseFormと違ってrouterを使う

import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head, Link, router } from '@inertiajs/react';
import { useLaravelReactI18n } from 'laravel-react-i18n';
import { useConfiguredDayjs } from '@/hooks/useConfiguredDayjs';
import Dropdown from '@/Components/Dropdown';
import {
    VscVerifiedFilled,
    VscUnverified,
    VscInfo,
    VscPersonAdd,
    VscEllipsis
} from "react-icons/vsc";

このようにrouterを呼びこんでおいて以下のように発行する

    const handleDelete = (id) => {
        router.delete(route('users.destroy', id));
    }

destroyを実装してみよう

destroy

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(Request $request, User $user): RedirectResponse
    {
        $user->delete();
        return redirect(route('users.index'))
            ->with('success', __('User Deleted'))
        ;
    }

これであっさり消える。ここで注意しておく必要があるのはエントリーは消えてるようにみせかけているだけなので実際は消えていないということ。だから今emailにunique indexを貼ってるけど、これは依然有効だからね?つまりa@a.aでメールアドレスを登録する事はできないということ(完全に消しこめば別だが)
これは一つ運用上で気をつけときたい。

いきなり消えるのはヤバい

まあそうだろう。最低限alertを付けとこう。そんな難しくはない。

    const handleDelete = (id) => {
        if (confirm(t('Are you sure you want to delete this user?'))) {
            router.delete(route('users.destroy', id));
        }
    }


全体的に言語カタログを付けとく


と思ったんだけどja.jsonに

    "Are you sure you want to delete your account?": "アカウントを削除しますか?",
    "Action": "操作",
    "Cancel": "キャンセル",
    "Click here to re-send the verification email.": "確認メールの再送はこちら",
    "Confirm": "確認",
    "Confirm Password": "パスワード(確認用)",
    "Current Password": "現在のパスワード",
    "Create": "作成",
    "Create New User": "新規ユーザー作成",
    "Dashboard": "ダッシュボード",
    "Delete Account": "アカウント削除"

つーのがあったので、これ使っとこ…

最終的なコード

resources/js/Pages/Users/Index.jsx

<Dropdown>
    <Dropdown.Trigger>
        <button>
            <VscEllipsis />
        </button>
    </Dropdown.Trigger>
    <Dropdown.Content>
        <Link href={route('users.edit', user.id)} className="block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:bg-gray-100 transition duration-150 ease-in-out">
            {t("Edit")}
        </Link>
        <button onClick={() => handleDelete(user.id)} className="block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:bg-gray-100 transition duration-150 ease-in-out">
            {t("Delete Account")}
        </button>
    </Dropdown.Content>
</Dropdown>


    const handleDelete = (id) => {
        if (confirm(t("Are you sure you want to delete your account?"))) {
            router.delete(route('users.destroy', id));
        }
    }

この辺の文言が気にいらなければまあそれは適当に直しといてくださいなっと。


いいなと思ったら応援しよう!