
九保すこひです(フリーランスのITコンサルタント、エンジニア)
さてさて、数日前Togetter
でこのページが話題になっているのを発見しました。
ぼく「嫌な予感がするから警告いっぱい出したれ」データ削除は三重確認設計に→??「なんかデータ消えたんですけど?」
開発者にとっては「あるある」ネタだと思うのですが、この中で衝撃だったのは、「削除したらほんとに消えたんですけど?」の部分です
(うーん、逆に削除できなくても苦情になりますし・・・)
そして、この投稿者さんもおっしゃっているとおり論理削除(ソフトデリート)というのはとても重要なんだな、と再認識しました。
※ソフトデリートとは、実際にデータは削除せず「削除したものとする」というテクニックです。Laravel
ではdeleted_at
で判別をします。
そこで!
今回は、間違ってデータ削除しても復活できるように、「ゴミ箱」ページをLaravel
で作ってみることにしました。
ぜひ皆さんのお役に立てると嬉しいです
開発環境: Laravel 7.x
目次 [非表示]
やりたいこと
テストとして以下2つのテーブルを用意し、ソフトデリートされたデータを復活させる「ゴミ箱」ページをつくります。
- posts
- products
※今回は2つですが、応用すればいくつでも復活ができるようになります。
では、実際にやってみましょう!
ソフトデリート設定する
ソフトデリートを設定する方法は、Laravel・データベースのデータ操作をご覧ください。
また、データベースは次のような形で用意してください。
データ復活する「ゴミ箱」ページをつくる
ルートをつくる
/routes/web.php
Route::get('/trash_box', 'TrashBoxController@index');
Route::get('/trash_box/restore/{table}/{id}', 'TrashBoxController@restore')->name('trash_box.restore');
内容としては上の行が「ゴミ箱」ページで、ここで削除済データをリスト表示します。そして、下の行が実際にデータを復活させるルートです。
コントローラーをつくる
次のコマンドで専用のコントローラーをつくりましょう。
php artisan make:controller TrashBoxController
そして中身を変更してください。
/app/Http/Controllers/TrashBoxController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class TrashBoxController extends Controller
{
public function index() { // 一覧ページ ・・・ ①
$query_1 = \DB::table('posts')
->select(
'id',
\DB::raw("title as label"),
\DB::raw("'posts' as table_name"),
'deleted_at'
)
->whereNotNull('deleted_at');
$query_2 = \DB::table('products')
->select(
'id',
\DB::raw("name as label"),
\DB::raw("'products' as table_name"),
'deleted_at'
)
->whereNotNull('deleted_at');
$trash_boxes = $query_2->union($query_1)
->orderBy('deleted_at', 'desc')
->orderBy('id', 'desc')
->paginate();
return view('trash_box')->with('trash_boxes', $trash_boxes);
}
public function restore($table, $id) { // データ復活 ・・・ ②
\DB::table($table)->where('id', $id)->update([
'deleted_at' => null
]);
return back();
}
}
ここは少し複雑なのでひとつずつ紹介していきます。
① 一覧ページ
削除されたデータを取得するのですが、取得するテーブルは「posts」と「products」の2つです。
そのため、union()
を使って一気にデータ取得するわけですが、その際に気をつけないといけないのが「フィールド名を合わせる」ということです。
つまり、2つのテーブルのフィールド名は一致していないため、as
を使って同じフィールド名を指定しているわけです。
最終的に取得するフィールドは、
- id ・・・ ここは共通
- label ・・・ 「products.name」か「posts.title」の内容
- table_name ・・・ テーブル名「posts」か「products」
- deleted_at ・・・ ここも共通
の4つになります。
② データ復活
テーブル名とIDでデータを特定し、deleted_at
をnull
に更新します。
(Laravel
では、deleted_at
に日付が入っていれば削除されたものとみなされます)
ビューをつくる
最後にindex()
で使うビューをつくります。
/resources/views/trash_box.blade.php
<html>
<head>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
</head>
<body>
<div class="p-5">
<table class="table">
<thead class="bg-light">
<tr>
<th>ID</th>
<th>テーブル</th>
<th>ラベル</th>
<th></th>
</tr>
</thead>
@foreach($trash_boxes as $trash_box)
<tbody>
<tr>
<td>{{ $trash_box->id }}</td>
<td><span class="badge badge-light">{{ $trash_box->table_name }}</span></td>
<td>{{ $trash_box->label }}</td>
<td class="text-right">
<a href="{{ route('trash_box.restore', [$trash_box->table_name, $trash_box->id]) }}" class="btn btn-secondary" type="button">元に戻す</a>
</td>
</tr>
</tbody>
@endforeach
</table>
{{ $trash_boxes->links() }}
</div>
</body>
</html>
この中では、TrashBoxController
で取得した削除データをループさせながら一覧を作ることになります。
テストしてみる
では、テーブルposts
とproducts
を全てソフトデリートした状態にしてテストしてみましょう!
では、一番上のposts
の10
番を元に戻してみます。
削除リストから消えました。
念のため、データベースも確認してみましょう。
10番のデータだけdeleted_at
がnull
になっています。
成功です
おわりに
ということで、今回はLaravel
で「ゴミ箱」機能をつくってみました。
ゴミ箱機能があれば、間違って削除しても簡単に復活させることができますし、なによりユーザー側もビクビク使わなくてよくなるので、きっと喜ばれることでしょう
また、今回は2つのテーブルだけに「ゴミ箱」機能をつくりましたが、union()
する部分で3つでも4つでも追加できるのでフレキシブルに対応ができると思います。(ただし、データ数が増えると速度が遅くなるので、その場合は「ゴミ箱」専用テーブルをつくる方がいいです)
地味に便利な機能なので、ぜひ皆さんもやってみてくださいね。
ではでは〜!