LoginSignup
8
4

More than 1 year has passed since last update.

改めてFirestoreのドキュメント更新について調べてみて、そしてESLintのカスタムルールを作りました!

Posted at

はじめに

Firestoreのドキュメントを更新するには、updateDoc, setDocの2種類がありますが、2種類あるだけに用法が違います。

結構注意しないと危ないので、実際に動かして挙動を確認しながら使い方を見ていきたいと思います。

あと、注意点に関してはESLintのカスタムルールを作ったので、それで防げることも確認しようと思います。

updateDoc

v8であれば、DocumentReference.updateですね。

この関数は純粋に更新を目的とした関数です。
よって、存在するドキュメントでのみ更新が可能で、引数に指定したオブジェクトのキーのフィールドを上書き更新する操作が行えます。
存在しないドキュメントでこの関数を使った場合は例外が発生します。

Uncaught (in promise) FirebaseError: No document to update

updateDocでは、「ドット表記」を使用することでネストしたデータを追加することができます。

updateDoc(
  doc(getFirestore(), "messages", "すでにあるID"),
  { 'content.message': 'test' },
);

この結果、データはjson記法で書くと以下の形になります。

{
  "content": {
    "message": "test"
  }
}

この「ドット表記」を使えば、事前にあるオブジェクトを壊さずにデータを追加できます。
たとえば、すでにデータが以下のような場合は、

{
  "content": {
    "message": "aaaa"
    "createdAt": "2023/01/22 10:10:10"
  }
}

messageの部分のみが更新されます。
逆に「ドット表記」を使わない場合はどうなるのでしょう。

updateDoc(
  doc(getFirestore(), "messages", "すでにあるID"),
  { content: { message: 'test' }},
);

このように記載した場合は、以下のようにcontentのところが置き換わってしまいます。

{
  "content": {
    "message": "test"
  }
}

updateDocはこれくらい押さえておくと大丈夫だと思います。
そして、個人的には更新することが目的であれば、こちらを採用しています。

setDoc

v8であれば、DocumentReference.setです。

こちらは、更新だけでなく追加もできます。
なので、ドキュメントのidが同じものがあれば更新、なければ追加が実行されます。

ただ、大事なのはこれからでupdateDocと違う動作になります。

単純にupdateDocと同じように書くと、setDocは更新というより、ドキュメントをすべて上書きします。
例えば、以下の状態のドキュメントがあるとして、setDocを実行してみます。

{
  "content": {
    "message": "aaaa"
    "createdAt": "2023/01/22 10:10:10"
  }
}
setDoc(
  doc(getFirestore(), "messages", "すでにあるID"),
  { content: {key: 'test'} },
);

このsetDocを実行すると以下の結果になります。

{
  "content": { "key": "test"}
}

完全に新しいものになってしまいました。
これはもはや更新とは言わないですね・・・。
ただ、これは防ぐ方法はあって、第三引数にオプションを追加することで更新の動きに変わります。
さきほどの関数を書き換えてみます。

setDoc(
  doc(getFirestore(), "messages", "すでにあるID"),
  { content: {key: 'test'} },
  { merge: true }
);

これを実行すると、以下の形になります。

{
  "content": {
    "key": "test"
    "message": "aaaa"
    "createdAt": "2023/01/22 10:10:10"
  }
}

すでに存在するオブジェクトの中に追加されました!
これはupdateDocではできません。
updateDocで同じようにオブジェクトの形で指定するとcontent自体が上書きされてしまいます。
オプションの名前の通り、setDocはオブジェクトをマージするような更新が行えます。
このような動作になるためなのか、setDocで「ドット表記」を使うと、ドット表記がそのままキーとなって追加されてしまうので、注意が必要です。

またsetDocmergeFieldsというキーを使うことができます。
これは、更新する場所を特定するオプションで、指定したキーのみが更新されます。
例えば、以下のように書くと、contentのkeyのところだけが更新されます。

setDoc(
  doc(getFirestore(), "messages", "すでにあるID"),
  { content: {key: 'test', other: 'aaaa'} },
  { mergeFields: ['content.key'] }
);

なかなか使うことがなさそうなオプションです。
ただ、仕様によってはオブジェクトの中の一部だけを書き換えたいときがあるかもしれないので、このオプションが有効に使えるかもしれません。

ここまで、説明してきましたが、よっぽどじゃない限り、setDocを更新で使う必要がないと思われます。

setDocが有効になるケースは、追加もしくは更新を同時に満たしたい時、もしくはすでにあるデータを綺麗に上書きしたいときのみに有効だと思います。

ただ、前者である場合は、更新をしたいわけなので、merge: trueを忘れると危険なことになります!

ESLintでsetDocの危険を察知する

setDocを使いたい時、使ってしまった時であっても、merge: trueをつけたくないというときはほとんどないと思います。
そこでESLintで警告できるようにしましょう!

私が作ったものです!

mergeもしくはmergeFieldssetDocに入れずに使ってた場合に警告を出すことができます。
修正も提案するようにしていて、merge: trueを追加するかupdateDocに変更するかを提案してくれます。
Admin SDKでも利用できるようにdoc().set()の形でも動くようにしています!

実際のVSCodeで動かしたのが以下の画像です。

vscode_preview

これで間違って全データを上書きしてしまうことはなくなるはず!

終わりに

ほとんどは、公式ドキュメントに書いていますが、mergeFieldsなんかは書かれていないので私も最近知った機能です。
Firestoreの基本的なところですが、深く触ってると必要になる知識だと思います!

また、setDocの用途は、私が書いた以上にあるのかが気になっているので、こんなときはsetDocを使った方がいいよ!というのがあれば、ぜひコメントしていただけると助かります!

あとはESLint使ってもらえるとうれしいです!不具合があればissueも出していただければ対応します!

8
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4