WixStudioでVeloを使う Step 23 「フィールドタイプ『複数の参照先』がキモい?」
はじめに
フィールドタイプ「複数の参照先」がキモい。いや衝撃的だった。参照タイプのフィールドは何度か記事でも取り上げてきた。複数の参照先タイプはまだ扱ってない。でも、なんとなく想像していた。「きっと複数の参照先タイプは非参照アイテムのIDを配列で扱えばいいのかな」と。でも、大間違いだった。ポイントはinsertReference( )
(やqueryReferenced()
やremoveReferences()
、removeReference()
)。その他色々。
フィールド:複数の参照先
準備
前回の続き。複数の参照タイプを検証するためにコレクションにフィールドを追加し、機能を拡張する。
目標
拡張する機能はメッセージの削除機能。単純にメッセージを削除するのは簡単。これは過去の記事で取り扱い済。今回は送信者と受信者がメッセージを削除できるようにする。
- 指定したメッセージを削除する機能を作成する。
- 削除した人は自身のメッセージ一覧からそのメッセージが消える。
この機能で注意したいのは、削除したメッセージは削除した人の一覧からのみ消える。要は、相手のメッセージ一覧には影響を与えないということ。あくまで、自分のメッセージ一覧から消えるだけ。
仕組みの検討
削除の仕組みとしては論理削除を採用。データに対する操作を考えるとき、削除は悩むポイントの一つ。データを削除する行為は結構怖い行為。何故かと言えば単純で消えたものは取り返せないから。だからデータを消したフリをする手法をとることがある。データ自体を消すのではなくて 「消しました」という印を付ける イメージ。データは消さない。データを消さない「論理削除」に対して、本当にデータを消してしまうのは「物理削除」と呼ばれる。
コレクションに複数の参照先タイプのフィールドを追加する
Messagesコレクションにフィールドを追加する。フィールドタイプは今回の複数の参照先。今回の主役。
複数の参照先フィールドを追加する
追加フィールドの設定内容は次の通り。
フィールドタイプ | フィールド名 | フィールドキー | 参照先コレクション |
---|---|---|---|
複数の参照先 | DeletedMembers | deletedMembers | Members/PublicData |
DeteledMembersフィールドが追加された
さらに、コレクションの権限を変更しておく。具体的には コンテンツの更新権限。「サイト会員」に変更しておく。
Messagesコレクションの権限を変更
コレクションの準備はこれで終わり。
deletedMembersフィールドの用途
deletedMembersフィールドは論理削除を実現する為に用意したフィールド。このフィールドに登録された会員は削除操作を行った会員にする。
- 会員がメッセージの削除を行った場合は、そのメッセージのdeletedMembersフィールドに会員を登録する。
- メッセージ一覧を描画するときには、deletedMembersフィールドに自分が登録されていないものだけを一覧する。
って感じ。
画面に削除ボタンを追加する
メッセージ一覧に削除ボタンを追加する。メッセージ毎に削除操作が可能になるから、リピーターのアイテムに追加することになる。
メッセージの削除ボタン
画面の変更はこれで終わり。
スクリプトを書く
書かなければならない処理は2つ。
- メッセージを(論理)削除する処理
- メッセージ一覧から削除されたメッセージを除外する処理
メッセージを(論理)削除する処理
修正箇所は2箇所ある。
- 実際に削除する処理はバックエンドコードに記載する(backend/Messages.jsw)。
- バックエンドコードに記載した削除処理を、削除ボタンが押されたときに呼び出す。これはページコード。
実際に削除する処理はバックエンドコードに記載する(backend/Messages.jsw)
前回の記事までに作成したバックエンドコードbackend/Messages.jswに関数deleteMessage(messageId)
を追記する。
import wixData from 'wix-data';
import { currentMember } from 'wix-members-backend';
/*省略*/
export async function deleteMessage(messageId){
const me = await currentMember.getMember()
return wixData.insertReference('Messages','deletedMembers',messageId,me)
}
関数deleteMessage(messageId)
は引数として削除対象となるメッセージのIDを渡す。insertReference()
の処理結果(Promise
)をそのまま返す。
関数deleteMessage(messageId)
で注目したいのはwixData.insertReference('Messages','deletedMembers',messageId,me)
の部分。記事冒頭の 「複数の参照先」がキモい と関連する。通常、アイテムの更新ではwixData.save(collectionId,itemObj)
を使用する。wixData.save(collectionId,itemObj)
は引数で渡されたオブジェクト(itemObj
)が、既にコレクション(collectionId
)に登録されているかどうか確認する。既存アイテムならそのアイテムは更新される。そうでなければ新規アイテムとして登録される。
deletedMembersフィールドもitemObj.deletedMembers = ['member1-id','member2-id',...]
といった感じで削除操作した会員のIDを配列で指定すれば良いかと思った。残念ながらそれはダメ。
複数の参照先タイプのフィールドに、参照先を追加するときはwixData.insertReference(collectionId,field,referringItem,referecendItem)
を使用する。これも使い方は簡単だった。一つ目の引数には参照コレクションのID。二つ目の引数は参照フィールドのID(キー)。三つ目の引数は参照アイテム(か参照アイテムのID)、四つ目の引数は被参照アイテム(か被参照アイテムのIDか、それらの配列)。
バックエンドコードに記載した削除処理を、削除ボタンが押されたときに呼び出す(ページコード)
$w('#repeater1').onItemReady(($item, itemData, index) => {
if (itemData.fromMember._id == me._id) {
$item('#box3').customClassList.add('from-me')
} else {
$item('#box3').customClassList.add('to-me')
}
$item('#text7').text = itemData.body
$item('#text6').text = me._id == itemData.fromMember._id ? null : `From:${itemData.fromMember.nickname}`
$item('#text5').text = me._id == itemData.toMember._id ? null : `To:${itemData.toMember.nickname}`
/* ここから追加 */
$item('#button2').onClick(event => {
deleteMessage(itemData._id)
.then(result => {
if (result) {
initData()
$w('#text4').text = 'メッセージが削除されました'
} else {
$w('#text4').text = 'メッセージが削除されませんでした'
}
})
})
/* ここまで追加 */
})
まず、関数deletedMessage()
を使うためにimport文は修正しておく(import {sendMessage,getMyMessages,deleteMessage} from 'backend/Messages'
)。
$item('#button2')
はリピータに追加した削除ボタン。削除ボタンが押されたときの処理を定義してる。deletedMessage(itemData._id)
でメッセージのIDを引数に渡す。結果は成功(true
)か失敗(false
)で返ってくる。成否に応じてメッセージを出し分ける。成功した場合にはinitData()
を呼び出し、リピータを更新しておく。
(論理)削除処理はこれで終わり。
メッセージ一覧から削除されたメッセージを除外する処理
処理はバックエンドコードに施す。具体的にはbackend/Messages.jswのgetMyMessages()
を修正する。
export async function getMyMessages() {
const me = await currentMember.getMember()
return wixData.query('Messages').eq('fromMember', me._id).include('fromMember').include('toMember')
.or(
wixData.query('Messages').eq('toMember', me._id).include('fromMember').include('toMember')
)
.not(
wixData.query('Messages').hasSome('deletedMembers', me._id)
)
.descending('_createdDate')
.find().then(results => {
console.log(results.items)
return results.items
}).catch(error => [])
}
抽出条件に新しく条件を追加すれば良い。
具体的には.not(wixData.query('Messages').hasSome('deletedMembers', me._id))
を追加しただけ。内側から確認する。wixData.query('Messages').hasSome('deletedMembers', me._id)
はdeletedMembersに(ログイン中の)会員の情報が含まれているメッセージを抽出している。.hasSome(fieldKey, values)
は指定したフィールド(fieldKey
)に、指定した何れかの値(values
)が含まれているものを抽出する。今回の場合deletedMembers
は削除操作を行った会員。values
には(ログイン中の)会員。だから、機能の目的としては該当するメッセージを抽出するのではなく、除外しなければならない。だから、この抽出条件を.not()
で否定する。結果として deletedMembers
に(ログイン中の)会員情報が含まれていないメッセージ を抽出する条件が完成する。
スクリプトの修正はこれで完了。
動作確認
動作を確認する。二人の会員を用意した。左が会員1、右が会員2。
削除前のメッセージ
会員1が最後に送った悪口メッセージを慌てて削除する。会員2側は会員1が削除操作を行った後にブラウザを更新しておく。削除した会員1側では、削除操作後にリピータが読み込み直される。
会員1のメッセージ一覧からは削除される
会員1のメッセージ一覧からは削除操作を行ったメッセージが削除されます。会員2のメッセージ一覧には削除されずに残る。この操作を行ったあと、コレクションも確認する。
コレクションの状態
DeletedMembersフィールドに会員1が登録されているので削除操作を行ったことが確認出来る。そして、アイテム自体はコレクションに残っている。
まとめ
複数の参照先タイプのフィールドに対して、値を更新するのは一手間必要。wix-data.save()
は使えない。ドキュメントにも書いてあった。このフィールドに対する操作はwix-data.insertReference()
など専用の関数を利用する事になる。これにはちょいとビックリした。またwix-data.queryReferenced()
やwix-data.isReferenced()
なども用意されていることから、フィールドの値を参照するにも一手間必要になるかと思ったらそんな事は無かった。wix-data..hasSome()
が普通に使えたし、記事にはしてないけれどwix-data.include()
も使えた。この更新処理の一手間は一体何者?と思った。が、ちょっとわかった気がする。実は複数の参照先フィールドをコレクションに追加すると、被参照コレクションには参照コレクションに対する複数の参照先コレクションが作成された。
被参照コレクションに作成された参照コレクションへの参照
これは結構、衝撃的。複数の参照先フィールドは双方向で参照し合えるようになってる。だから、複数の参照先フィールドの値を変更するときには、被参照コレクション(被参照アイテム)でも更新しなければならない。wix-data.save()
で自由に更新されては困るわけだ。参照情報の整合性を保つためにも専用の関数を経由する感じになっているのかな。と、勝手に解釈した。
もう1点。コレクションの更新権限をサイト会員にしたこと。自由度を高めようとすると、権限を緩くしてしまう。Hookなど利用して更新を許すかどうかはしっかりと制御しなければいけないと思った。今回はしてない。
つづき
Discussion