はじめに
「更新前」のサーバスクリプトと「更新前」の拡張 SQL は、どちらが先に動くのでしょうか。フォームを送信したときは、通常の新規作成と同じ条件になるのでしょうか。
実行条件の一覧だけでは判断しにくいので、まず一覧を開き、レコードを編集して保存するところから本体の処理を追ってみます。前編では、一覧・編集・作成・更新とフォーム送信を扱います。後編のテーマは、入力途中の処理・一括操作・APIです。
プリザンター 1.5.9.0 のソースを対象にしています。実機での動作確認は行っていません。図は主要なフックの相対順を示し、呼び出し回数を固定するものではありません。
サーバースクリプトと拡張サーバースクリプトは、同じ実行条件で動きます。同じ条件では拡張サーバースクリプト → サイトのサーバースクリプトの順です。拡張 SQL は、それとは別に SQL を組み立てたり DB を更新したりする位置で適用されます。
「更新前」という名前だけでは、計算式・入力チェック・DB 更新との前後関係までは分かりません。この記事では画面操作を起点に、どの条件がどこで呼ばれるかを追います。
ソースで追う範囲
公式のサーバスクリプトの条件と拡張サーバスクリプト設定に対して、この記事では複数の機能をまたぐ呼び出し順を補います。
図は記録テーブル(Results)の通常の処理をソースから整理したものです。期限付きテーブル(Issues)も、作成・更新の主要なフックの前後関係は同じです(IssueModel.cs の作成、更新)。Wiki・フォルダは対象に含めません。この前編では、通常の一覧・編集画面とフォームの経路を扱います。
図中の「SS」は、条件に合う拡張サーバースクリプトとサイトのサーバースクリプトの両方を表します。有効なスクリプトと拡張 SQL が登録されている前提です。通知・サマリ・添付ファイルなどの細部は省略しています。図は実機ログではなく、ソースで確認した主要なフックの相対順です。リンク項目などによる追加取得や再計算があるため、呼び出し回数を固定する図ではありません。
「いつ」と「どの操作」を分ける
| 判定するもの | 意味 | 例 |
|---|---|---|
context.Condition |
今どのフックで実行されているか |
BeforeOpeningPage、BeforeUpdate
|
context.Controller |
どのコントローラへのリクエストか |
items、forms
|
context.Action |
リクエストの操作名 |
index、edit、update、create
|
Controller・Action はルートの値を小文字にしたものです。画面の見た目ではなく、現在のリクエストを表します(Context.cs)。
| 操作 | Controller |
Action |
まず着目する SS の条件 | 保存用の拡張 SQL |
|---|---|---|---|---|
| 一覧画面を開く | items |
index |
WhenViewProcessing、BeforeOpeningPage、各行の WhenloadingRecord・BeforeOpeningRow
|
作成・更新系は呼ばれない |
| 既存レコードの編集画面を開く | items |
edit |
WhenViewProcessing、WhenloadingRecord、BeforeOpeningPage
|
作成・更新系は呼ばれない |
| 新規作成画面を開く | items |
new |
BeforeFormula・AfterFormula、BeforeOpeningPage
|
作成系はまだ呼ばれない |
| 新規レコードを作成する | items |
create |
BeforeCreate、AfterCreate
|
OnCreating、OnCreated
|
| 既存レコードを更新する | items |
update |
BeforeUpdate、AfterUpdate
|
OnUpdating、OnUpdated
|
| フォームを開く | forms |
new |
BeforeFormula・AfterFormula、BeforeOpeningPage
|
作成系はまだ呼ばれない |
| フォームを送信する | forms |
create |
BeforeCreate、AfterCreate
|
OnCreating、OnCreated
|
| 送信完了画面に移動する | forms |
thanks |
WhenloadingSiteSettings |
再度の作成は行わない |
上の表は着目点の早見表です。各経路でサイト設定を読み込む WhenloadingSiteSettings があり、保存時には入力の反映・計算式・レコードの再取得もあります。全条件が1回ずつ動くという意味ではありません。
操作名は ItemsController.cs、新規・編集、作成・更新、FormsController.csで確認できます。
同じ条件での実行順
本体は拡張サーバースクリプトを先に並べ、サイトのスクリプトを後ろに連結します。その一覧から、現在の実行条件に合うものを選びます(SiteSettings.cs、ServerScriptUtilities.cs)。
拡張側の Controllers・Actions は対象リクエストを絞る条件です。Actions に update を指定するだけでは実行タイミングは決まりません。BeforeUpdate なども指定します。Controllers・Actions の肯定指定は大文字・小文字を区別するので、リクエストと同じ小文字で指定します。また、肯定指定と除外指定を同じリストに混ぜると、除外側の判定で対象が広がる場合があります。操作を限定するときは、肯定指定でそろえて実行条件と組み合わせます(条件の評価)。
一覧画面を開いたとき
操作手順は「記録テーブルの一覧を開く」です。通常の一覧表示では items/index になります。
一覧データの取得は、BeforeOpeningPage より先です。一覧の検索条件を変えるなら WhenViewProcessing を使います。 BeforeOpeningPage では取得済みの gridData を扱えますが、そこでビューを変更するだけで、先に行われた一覧取得をやり直すわけではありません。
WhenloadingRecord は一覧全体に対して1回ではなく、行のモデルを作る際にも呼ばれます。その後に BeforeOpeningRow があり、行の見た目を組み立てます。表示行が多いほど、行ごとの処理も増えます。
根拠は ResultUtilities.cs の一覧取得から画面フックまで、GridData.cs の SELECT、行モデルと行フック、レコード読み込みフックです。
選択系の拡張 SQL は何をするか
| 設定 | 適用する場所 |
|---|---|
OnSelectingWhere |
SELECT の WHERE 条件 |
OnSelectingOrderBy |
SELECT の ORDER BY |
OnSelectingColumn |
SELECT の列式 |
これらは、作成・更新系のように独立した SQL を前後に流すフックとは性格が異なり、本体の SELECT に組み込む SQLです。設定名に「Selecting」があっても、別の SQL が3本順番に実行されるという意味ではありません(View.cs の WHERE、ORDER BY、Rds.cs の列式)。
WhenViewProcessing は一覧専用ではありません。編集対象の取得も View.SetColumnsWhere を通るため、編集時や保存中の再取得でも呼ばれます。一覧だけを対象にするなら、操作名も確認します(ResultModel.Get)。
フィルター・ソートで一覧を読み直す
初回の一覧表示の後に、フィルターやソートを変更して行を取得し直す items/gridrows の経路もあります。ここでは一覧データを取得した後に BeforeOpeningPage を呼び、返す行を描画します。index だけに絞った処理は、この経路に適用されません。一覧の検索条件を継続して適用するなら、gridrows も対象に含めます(ItemsController.GridRows、ResultUtilities.GridRows)。
編集画面を開いたとき
操作手順は「一覧で既存レコードを選び、通常の編集画面を開く」です。items/edit で、作成・更新はまだ行いません。
BeforeOpeningPage には、取得済みの編集対象モデルが渡されます。一覧で各行に使う BeforeOpeningRow は、編集対象1件の通常のエディタ描画には使いません。ただし編集画面内のリンク先一覧などが行を描画する場合は、追加の行フックがあり得ます。
根拠は 編集モデルの生成、取得してから入力を反映するコンストラクタ、画面フックへのモデルの受け渡し、リンク先の行フックです。
編集画面を保存したとき
「保存」は、新規なら作成、既存なら更新に分かれます。まず、既存レコードの値を変更して更新する場合を見ます。
押さえたい順序は BeforeUpdate → OnUpdating → 本体更新 → OnUpdated → AfterUpdate です。
標準の更新時検証は BeforeUpdate より先に行われます。「更新前なら、すべての検証より前」ではありません。入力値を反映する SetByForm は、その中で計算式の処理を呼びます。したがって、BeforeFormula・AfterFormula も BeforeUpdate より前に現れます。プロセス処理などによる追加の計算は図では省略しています(ResultUtilities.Update、入力反映、計算式フック)。
OnUpdating は本体更新の SQL リストの先頭側に追加されます。OnUpdated は、その最初の更新処理が終わった後、関連情報を更新する別の SQL リストに追加されます。前後の拡張 SQL と保存全体を、単一のトランザクションで囲んでいるわけではありません。 後続処理のエラーで、既に終わった DB 更新まで一括で戻ると想定しないでください(更新前と本体更新、関連更新と更新後フック、関連更新内の SQL)。
保存後の画面表示も同じリクエストに含まれる
通常の更新応答は、編集画面を再描画する経路を通ります。このとき BeforeOpeningPage の Action は update のままです。BeforeOpeningPage と Action = edit を組み合わせると、最初に編集画面を開いたときに絞れますが、更新後の再描画は対象から外れます。
General.json の UpdateResponseType の既定値は 0 です。通常の未ロックのレコードで 1 にすると項目差分を返す経路になり、同じ再描画フックを必ず通るとは限りません。ダイアログ編集も一覧行の置換という別経路です(General.json、ResponseByUpdate、EditorResponse)。
新規作成を保存する場合
新規作成画面を開く操作は items/new、作成ボタンを押す操作は items/create です。画面を開いただけでは BeforeCreate は呼ばれません。
更新の名前を単に置き換えるだけでなく、ID が確定する位置が異なります。作成後のレコード ID を拡張 SQL で使うなら OnCreated を選びます。OnCreating の ID プレースホルダーは 0 です(Rds.cs)。
作成の入力反映・検証は ResultUtilities.Create、保存前フックと INSERT の準備は ResultModel.Create、保存後の SQL とフックは 同メソッドの後半で確認できます。
フォームを開いたとき・送信したとき
フォームは forms コントローラを使います。通常の新規作成画面と同じ new、通常の作成と同じ create という操作名なので、フォームだけを対象にする場合は Controller も確認します。
フォームを開く
操作手順は「フォーム機能で発行した URL を開く」です。公開期間内で、かつテナントのフォーム登録数が上限に達していなければ、FormsController.New が ItemModel.New を呼び、記録テーブルの EditorNew に進みます。
既存レコードを開く操作ではないので、標準の新規フォーム表示に「保存済みの送信レコードを読む段階」はありません。入力反映時に計算式処理を通ることと、作成フックをまだ通らないことを分けて考えます。参照コピーなどの追加条件はこの図から除いています。登録数の上限に達している場合は、図の途中で上限超過の画面を返し、以降の段階へは進みません(送信時も同様に、作成処理の前に上限超過のエラーを返します)。
根拠は FormsController.New、ItemModel.New、EditorNew、新規モデルの入力反映です。
フォームを送信する
操作手順は「フォームに入力して送信する」です。FormsController.Create は公開期間、CAPTCHA、フォーム登録数の上限を順に検証した後に ItemModel.Create を呼びます。送信に成功すると、登録数のカウントを加算します。以降の作成フックは、先ほどの新規作成の図と同じ順です。ただし Controller は forms、Action は create です。
送信完了画面は別のリクエストです。thanks はサイト設定を読み込んで完了メッセージを描画し、新たにレコードを作成しません。そのため AfterCreate が「送信完了画面を開いたとき」の条件になるわけではありません。また、完了画面の経路は通常のエディタ描画を通らないので、そのメッセージ描画に BeforeOpeningPage は使いません(FormsController.Create・Thanks、送信成功時の応答、ItemModel.FormThanks)。
Controllers を items に限定した拡張サーバースクリプト・拡張 SQL は、フォームの forms リクエストには一致しません。通常画面では動いてフォームで動かない場合、実行フックに加えて適用対象の絞り込みも確認します。
目的から条件を選ぶ
| やりたいこと | 条件・絞り込みの選び方 |
|---|---|
| 一覧の取得条件を変える | SS の WhenViewProcessing。初回の items/index と行再取得の items/gridrows を考慮する |
| 一覧の行ごとに表示を調整する | SS の BeforeOpeningRow
|
| 編集画面を初めて開くときに表示を調整する | SS の BeforeOpeningPage と items/edit
|
| 新規画面・フォームの初期表示を調整する | SS の BeforeOpeningPage と new。通常画面とフォームは Controller で分ける |
| 計算式へ渡す値を調整する | SS の BeforeFormula。保存専用と考えず、呼び出し元の操作も確認する |
| 保存するモデルの値を作成・更新前に変更する | SS の BeforeCreate・BeforeUpdate。標準検証との前後関係に注意する |
| 保存後の処理を行う | SS の AfterCreate・AfterUpdate。同じリクエストで再取得も起こることを考慮する |
| DB に作成された ID を使って SQL を流す | 拡張 SQL の OnCreated
|
| フォーム送信だけに処理を適用する |
forms/create と BeforeCreate または AfterCreate。SQL なら OnCreating または OnCreated
|
モデルの値を変える場合も、表示用の評価とDB保存を区別します。通常の更新で保存前のモデルを変更するなら BeforeUpdate、入力途中の表示を評価し直すなら BeforeOpeningPage というように、ここまでの呼び出し順に合わせて置き場所を選びます。
自分の設定で確認するときの手順
ここまでの図はソース上の順序です。実機で確認するときは、テスト用の記録テーブルを使い、次の手順でリクエストと条件を照合します。
- テーブルの管理のサーバースクリプトで、調べる条件を選びます。まずは
BeforeOpeningPage、BeforeOpeningRow、BeforeCreate・AfterCreate、BeforeUpdate・AfterUpdateを操作ごとに確認します。 -
context.Logを使う既存のログ出力にCondition・Controller・Actionと、拡張側かサイト側かを区別できる名前を含めます。まず1つの条件で記録し、次に同じ操作の前後の条件を追加すると、ログの順序を比較しやすくなります。 - ブラウザの開発者ツールの Network を開き、一覧を開く → 既存レコードを開く → 更新する → 新規画面を開く → 作成する、の順に操作します。
- フォーム URL を開き、送信します。
forms/new、forms/create、成功後のforms/thanksを別のリクエストとして確認します。 - スクリプトの実行を記録したログは、リクエストごとに区切って読みます。次の表と照合し、追加取得・再計算で同じ条件が増えていないかも見ます。
| 操作 | 重点的に照合する組み合わせ |
|---|---|
| 一覧を開く |
index と BeforeOpeningPage、index と各行の BeforeOpeningRow
|
| 既存レコードを開く |
edit と WhenloadingRecord、edit と BeforeOpeningPage
|
| 更新する |
update と BeforeUpdate・AfterUpdate、再描画経路の update と BeforeOpeningPage
|
| 新規画面を開く |
new と BeforeOpeningPage。BeforeCreate はまだ呼ばれない |
| 作成する |
create と BeforeCreate・AfterCreate
|
| フォームを開く |
forms・new と BeforeOpeningPage
|
| フォームを送信する |
forms・create と BeforeCreate・AfterCreate
|
context.Log はブラウザのコンソールではなく、サーバー側の LogBuilder に追加します(ServerScriptModelContext.cs)。拡張 SQL の実行は SS のログだけでは観測できないので、DB 側の実行ログとソース上の呼び出し箇所も照合します。
呼び出し回数を固定して考えない
-
WhenloadingRecordは、画面を最初に開くときだけでなく、保存後の再取得でも呼ばれます。OnCreated・OnUpdatedがある場合の追加再取得もあります。 - 計算式処理は入力反映の中で呼ばれます。
SetByFormDataとSetByFormの両方に呼び出しがあるため、BeforeFormula・AfterFormulaを「保存1回につき各1回」と扱えません(SetByFormData の末尾、SetByForm の末尾側)。 -
WhenViewProcessingはViewインスタンスごとに実行済みを記録します。新しいビューで取得し直す場合は再び呼ばれます(View.cs)。 - 権限・入力・CAPTCHA などの検証で途中終了した場合、後続の保存フックまで到達しません。
まとめ
一覧の取得条件は WhenViewProcessing、行の表示調整は BeforeOpeningRow、編集画面の表示調整は BeforeOpeningPage が起点です。作成・更新では、サーバスクリプトと拡張 SQL の前後の条件を、入力反映・検証・DB保存の順序に合わせて確認します。
同じ BeforeOpeningPage でも、初回の edit と保存後の update は別の操作です。フォームも forms/new と forms/create を分けて読み、条件名と Controller・Action を組み合わせると、対象の処理を選びやすくなります。