コレクションをまとめて編集してDBへ、アイテムのトラッキング

コレクションのクライアント側トラッキングについて

Reactでコレクションの複数のアイテムをまとめて編集したい。
リスト系扱うだけならEntityAdaptorやRTK Query(こっちは試してない)などありそうですが、
いまいち要件を満たせてない(単に知らないだけかも)ので自分で作ってみました。

.NETのEntityFrameworkやPHPのDoctrineっぽい感じのローカル用の簡易ライブラリです。
アソシエーションとか一切考慮してません。

DBと逐次連動するのではなく、
フォームで一通りの編集(追加、削除、変更)を終えてまとめてDBに反映することを想定してます。

おおまかな機能

コレクションを編集する際フォームで行う作業は、

  • 新しいアイテムをコレクションに追加する
  • コレクションからアイテムを削除する
  • アイテムの内容を変更する

があります。

これらの変更は最終的にデータベースに保存することが前提になっていることが多いはず。
その時必要な情報は、

  • 新しく追加するアイテムのリスト
  • 削除するアイテムのリスト
  • 変更されたアイテムのリスト

なのですが、それを管理するのは思いのほか面倒です。

例えば、

  • 「A」を追加したあと「A」を削除した結果、DB上そのAはINSERTもDELETEもする必要がない。
  • 「B]に変更を加えた後、「B」を削除した結果、DB上はUPDATEする必要はない、DELETEするだけ。
  • 「C」に変更を加え、いろいろ変更したけっか元の内容になった。DB上UPDATEする必要がない。
  • 「D」を追加したけど、やっぱり「D」を削除した。DB上、そのDをINSERTもDELETEもする必要がない。
  • 「E」を削除したけど、なんだか「E]を再び追加した。DB上、DELETEもINSERTもする必要がない。

などなど。

これらの状態を追跡することで最低限の変更だけを検出して、それをDBへ更新できるようにします。

考え方

コレクションのアイテムは3つの状態を持ちます。

  • ATTACHED はアイテムがトラッキングされた状態です(何も操作をしていない状態)。
  • REMOVED はアイテムが削除されたものであることを意味します。。
  • ADDED はこのアイテムが追加されたものであることを意味します。

ATTACHEDはすでにDBに存在するアイテムなのが条件であり、
そのコレクションがトラッキング開始時に設定されるデフォルトの状態です。

また変更の場合はCHANGEDUPDATEDなどの状態は持ってません。

トラッカーにアイテムをセットすることでアイテムはトラッキングされます。
トラッカーはトラッキングされた時点で

  • アイテムのインスタンス
  • トラッキング時点のアイテムのコピー
  • アイテムの状態(初期値はATTACHED)

を持ちます。

変更を知りたいときは?

そのアイテムとアイテムのトラッキング時のコピーを比較することで変更を検出します。
変更を状態として持つのではなく、都度比較することで設計がシンプルになります。

注意点

理論上うまくいくはず!

と思ってますが、

ワタクシ、結構アホなので穴があるかもしれません。

そもそもまだテストしてません。

あくまでアホが考えた理論だと割り切ってください。

変更のシナリオ

実際にアイテムのトラッキングのされ方を見ていきます。

Attach

最初に「AAA, BBB, CCC, DDD, EEE」の5つのアイテムがあるコレクションをトラッキングします。
トラッキングされたので状態はすべてATTACHEDです。

Remove

次にAAABBBを削除します。
それらはREMOVEDに変更されます。
トラッキングから排除されるのではなく「削除される予定ですよ」というマークが付けられたと考えてください。

Add

次に新しくFFFGGGを追加します。
これらは新規追加なので状態はADDEDになります。

AddとRemoveの逆転

ここでちょっと踏み込んだ内容です。
BBBを追加し、FFFを削除します

状態がREMOVEDのアイテム(BBB)を「追加」するとどうなるでしょう?
答えはATTACHEDに戻ります。
「削除して追加」はDB上なにもする必要はないことになります。

逆にADDEDのアイテム(FFF)を削除はどうなるでしょう?
なんとトラッキングがら除外されてしまいます

考え方はシンプルです。
新しくアイテムを追加して、そのアイテムを削除しました。
これは「追加したいけど、やっぱやめた」わけでDB上は最初から何もなかったことになります。
なのでトラッキングから除外されるだけです。

ちょっとわかりにくいと思いますが、
「BBB」はもともとDBに存在するアイテムなのでトラッキングされたまま状態が変化します。
「FFF」はそもそもDBに存在しないので単純にトラッキングから外されます。

Change

最後に「AAA, BBB, DDD, GGG」を変更します。

トラッキング上CHANGEDという状態は用意してありません。
なのでトラッカーとは別にCHANGEDを作り出してください。
トラッキング時のコピーと現在の値を比較して違えばCHANGEDになるように作ります。

変更はアイテムの状態がATTACHEDの時だけ検出するようにします。
この例ではもともとATTACHEDだった「BBB, DDD」だけをCHANGEDとして扱います。

なぜかというと、

  • 状態がREMOVEDのアイテムを変更したところでDB上ではどうせDELETEされるからUPDATEする必要はないわけです。

  • 状態がADDEDのアイテムについても、DB上ではその時点でのアイテムをINSERTすればいいだけなので変更を検出する意味がありません。

ということです。

なので変更されたアイテムを取得するのは状態がATTACHEDであり、なおかつ変更が検出されたものに限ります。

実装はGitHubで公開してます。

ソースコード:
デモページ:

BlockEditor certificate css DataGrid Docker Gutenberg Hyper-V iframe MUI openssl PHP React ReduxToolkit REST ubuntu WordPress オレオレ認証局 フレームワーク