WixStudioでVeloを使う Step 16 「コレクションを使う - プライマリ列と既存データの更新」
はじめに
Veloでコレクションを使う目的としてHolidaysコレクションを作成し触ってきた。表やリピータを使って一覧を作成する事も出来た。次はアイテムページ。詳細ページなどとも呼ぶ。要は表やリピータは「祝日の一覧」で、アイテムページは「祝日ごとの(固有の)ページ」のこと。このアイテムページを作成するのは簡単。WixStudioから一瞬で作成出来る。Veloが出る幕はない。しかし、ここで問題が発生する。主にプライマリに関することで、この問題に対応するためにVeloを使うことにする。
アイテムページ
目的
Holidaysコレクションを使って祝日のアイテムページを作成したいが、先に問題点をVeloで解決する。次回の記事で祝日一覧から祝日のページを作成し、遷移できるようにする。
準備
Holidaysコレクションを利用する。また、一覧(リピータ)から遷移する流れとするためこれらが用意されている前提で作業を進める。step 15の続きとして作成します。
問題の確認
先に問題点を共有する。問題点はHolidaysコレクションの構造にある。
プライマリ列とその在り方
コレクションの列にはプライマリ列というのが存在する。通常はTitle列がこれに設定されている。プライマリに設定されている列には旗マークが付く。
プライマリ列
プライマリ列の用途としては、アイテムページを作成する際にパスの一部として標準的に利用されたりする。コレクションはたくさんのアイテムを格納することができる。格納されたアイテムの中から、特定のアイテムを一意に判別するための候補としてプライマリ列は機能されている。これをHolidaysコレクションの言葉で言い換えると「Holidays(コレクション)には、祝日の情報(アイテム)が格納されている。特定の祝日(アイテム)を識別するためにプライマリ列の情報を使う」って感じ。
しかし、これが大問題を引き起こす。通常データベースを考える際には「主キー」と呼ばれる列を設定する。これはコレクションにおけるプライマリ列と同じように使われる。「主キー」を「プライマリキー」とも呼ぶ。データベースでは主キーに対して「ユニーク制約」を設けるのが一般的な考え方。要は、主キー列に対する重複を無くす制約。この制約によって、主キーの情報は重複が発生しないし、一意にアイテムを特定することができるという仕組み。コレクションのプライマリ列にはこの制約が設けられていない。重複が発生し放題ということになる。だから、コレクション内のアイテムを一意に識別することはできない可能性もあるということになる。あくまで一意に判別するための候補に過ぎない。HolidaysコレクションではTitle列に祝日名が入るため、毎年同じ名前のデータ増えていくことになる。Title列は重複し放題と言うことになる。
祝日名は重複する
Title列をプライマリ列としたままアイテムページを作成するとパスも重複してしまう。
プライマリ列を使って作られたアイテムページのパスも重複する
_id列
コレクションにはWix(システム)によって管理されている_id列が存在する。恐らくこちらは重複しないように管理されている。値は参照のみができて変更は許されない。あくまで(システムが)内部的に使う情報という位置づけなんだと思う。あまり、表に出すべき情報ではない。
_id列
問題の解消
この問題を解消するために考えられることをいくつか考えて見る。まず「プライマリ列は変更することができる」ということ。要はTitle列ではない別の列にプライマリ列を指定することができる。HolidaysコレクションにはTitle列以外にDate列がある。Date列は現時点では重複が発生していないはず。しかし、実は無理。プライマリ列はフィールドタイプが「テキスト」である事が条件。
Holidaysコレクションは致命的な構造をしている事が確定した。なら「新しく列を作成する」。「新しい列には重複が発生しない情報を格納する」、「プライマリ列を新しい列にする」の合わせ技で解消を試みる。Veloが必要になるのは「新しい列には重複が発生しない情報を格納する」の部分。Holidaysコレクションには既に1000件以上の祝日が格納されている。手作業での対応はありえない。
新しく列を作成する
Holidaysコレクションに列を追加する。列の作成はCMS画面から行う。
新しい列を作成する
項目名 | 設定値 |
---|---|
フィールドタイプ | テキスト |
フィールド名 | Code |
フィールドキーはフィールド名から自動的に生成される。「Code」 -> 「code」
プライマリ列を新しい列にする
プライマリ列の設定もCMS画面から行う。プライマリ列に設定したい列(列名)の右端から「プライマリに設定する」を選択すれば良い。
プライマリ列を設定する
Code列がプライマリ列になる
新しい列には重複が発生しない情報を格納する
現在Code列には情報が入っていない。各アイテムのCode列に重複しない情報を格納する。そこで、重複しないデータを生成する方法を考えなければならない。例えば「日付_祝日名(例:2024-05ー05_こどもの日)」の様なデータなら基本的に重複が発生しない。今回はこの方法を採用する。ここでVeloの出番になる。
スクリプトを書く
まずはバックエンドコードからかく。既にholidays.jswがある。新しく関数setHolidaysCodeを定義する。
setHolidaysCode関数を定義する
setHolidaysCodeは、Holidaysコレクションのデータに対してcode列の値を設定する関数。
import wixData from 'wix-data';
export async function checkHoliday(date){...} // <-- 以前定義済
export async function getNextHoliday(date){...} // <-- 以前定義済
/* 新しく定義した setHolidaysCode */
export async function setHolidaysCode(){
return await wixData.query('Holidays')
.isEmpty('code')
.limit(100)
.find()
.then( results => {
return Promise.allSettled(
results.items.map( holiday => {
holiday.code = `${holiday.date}_${holiday.title}`
return wixData.save('Holidays',holiday)
})
)
})
}
全体的な流れとしては以下のようになる。
- Holidaysコレクションに対して、Code列が空の祝日データを最大100件抽出する。
- 抽出された各祝日データに対して、Date列とTitle列の値から「日付_祝日名(例:2024-01-01_元日)」を生成し、Code列に設定する。
- Code列が設定された祝日データは保存する。全ての保存(更新)結果を返す。
まず、処理を100件(.limit(100)
)と絞った点にこだわりはないが、多分丁度いい(後述)。更新対象の祝日データを「Code列が空の」に絞っている。これは.isEmpty('code')
で実現出来る。列を作成する際には「Code」と先頭大文字で指定しているが、フィールドキーは小文字で指定する。条件に一致した最大100件の祝日データに対して処理を行う。更新処理がやや複雑な記述になった。処理の内側(単純な部分)から段階的に確認する。最大件数である1000件にしても良いが、フロントからバックエンドコードを呼び出した際に14秒以上結果が返らなかった場合にエラーとなる(ステータスコード504)扱いとなってしまう。
return Promise.allSettled(
results.items.map( holiday => {
holiday.code = `${holiday.date}_${holiday.title}`
return wixData.save('Holidays',holiday)
})
)
holiday.code = `${holiday.date}_${holiday.title}`
によって「Date列とTitle列の値から「日付_祝日名(例:2024-01-01_元日)」を生成し、Code列に設定する。」が実現される
return wixData.save('Holidays',holiday)
で保存が行える。wixData.save()
は一つ目の引数にコレクション名、二つ目の引数に保存したいデータ(オブジェクト)を指定する。
results.items.map( holiday => {...})
からやや複雑。正直、実装に悩んだ。results.items
はCode列が空の祝日データ群。.map( holiday => { /*処理*/ })
を繋げることで、各祝日データにたいして処理を実施する。ここで言う処理は「holidays.code
の設定と保存」を指す。また、.map( holiday => { /*処理*/ })
は処理の結果を配列にまとめて返してくれる。return wixData.save()
としているので、保存結果のPromiseオブジェクトが配列としてまとめられる。
Promise.allSettled([..])
が最高に救われた。まず、Promiseオブジェクトの配列を引数で渡す。全ての結果を配列に返してくれる。配列の要素は各プロミスの結果をまとめたオブジェクト。
setHolidaysCode関数は、Holidaysコレクションから最大100件の祝日データにCodeを設定する。全ての更新結果を呼び出し元に返す関数になる。
setHolidaysCodeを呼び出す
setHolidaysCode関数を呼び出すための方法は幾らか考えられるが、単純にボタンのクリックイベントを使うことにする。まずは、新しくページを用意する。ページ名は「controlHolidays」としている。
新しいページ「controlHolidays」を用意する
さらに、ボタンとテキスト要素を配置する。
ボタンとテキスト要素を配置する
レイアウトにこだわりはない。テキストの内容は「ーー」としておいた。
ボタン(#button1)にイベントを追加する
import { setHolidaysCode } from "backend/holidays"
$w.onReady(function () {
$w('#button1').onClick( event => {
$w('#text4').text = "処理中"
setHolidaysCode()
.then( results => {
let success = results.filter( result => result.status === 'fulfilled' ).length
$w('#text4').text = `更新件数:${success}/${results.length}`
})
.catch( error => {
$w('#text4').text = "要確認"
})
})
})
setHolidaysCode().then(results => {...})
で更新処理の呼び出しとその結果の確認を行う。resultsにはwixData.save()の(Promiseの)結果が格納されている。results.filter( result => result.status === 'fulfilled' ).length
は結果のフィルタリングを実施している。Promiseは処理が成功した場合に状態がfulfilled
となるためresult.status === 'fulfilled'
を条件として結果をフィルタリング。.length
で更新成功件数を取得している。万が一、関数が正常に処理できなかった場合のために.catch(error => {...})
をつけておく。
動作確認
プレビューで動作確認ができる。「祝日コード設定」ボタンを押す。テキスト要素は「--」から「処理中」に変化する。
ボタンを押すと「処理中」と表示される
処理が完了すると「処理中」から「更新件数:100/100」と表示される。
処理結果が表示される
Holidaysコレクションを開くと、更新状況が確認出来る。
Code列が登録されたデータがある
ボタンを押す度に最大で100件分更新する。Holidaysコレクションの祝日データにはCode列が更新済のものと未更新のものが混在した状態になる。
後始末
controlHolidaysページは一般公開すべきページではない。サイトを管理する人だけが表示できれば良い。サイト共同管理者限定のページにしておく。
controlHolidaysページをサイト共同管理者限定ページにする
また、ページにパスを設定しておく。ページのパスは「controlholidays」にした。メニューに追加する必要はない。
ページにパスを設定
データフック
今後も祝日データが増えたときのためにデータフックを追加しておく。
Code列の値は他の列の値によって算出できる値。このような列の値は新しくデータが登録された際に自動的に設定されるようにしておけばいい。
export function Holidays_beforeInsert(item, context) {
item.code = `${item.date}_${item.title}`
return item
}
まとめ
アイテムページを作成する準備として、アイテムページ生成に必要な祝日固有の情報「Code」を作成した。Holidaysコレクションは1000件以上の祝日が記録されている。Code列の設定は1件ずつ手作業でもできる。しかし、手作業はミスが起きる可能性もある。...それ以前にめんどくさい。大量の単調作業を正確に高速に行うにはシステムの力を借りるのが一番。ここでVeloの出番になった。ただ、これは応急処置。実際にはコレクションを用意するときにしっかり検討しておいた方が良い。
次回は祝日固有ページを作成し、祝日一覧から固有ページに遷移する。
Discussion