スナップショットリンクの再利用

DevCon 2019の懇親会で社本さん、竹内さんと話していて、John Mark Osborneの話題になりました。Matt Petrowskyと共にScriptologyを書いた、FileMakerコミュニティの古株です。調べてみたところ、今でも活発に情報発信されていました。
彼は2019年7月からPodcastも公開しています。聞いてみると、分離モデルに反対したり、ライブコーディング(公開したままDBの設計を変更する)を認めていたりする様子が、とてもopinionatedな開発者だという印象です。
今回はJohn Markさんのサイトから、スナップショットリンクの活用方法に関する記事を紹介します。


Philosophy of FileMaker

Snapshot Saves

(元記事はこちら)

John Mark Osborne
2019/8/13

FileMakerのオンラインヘルプによると、スナップショットリンク機能は対象レコードのセットを保存します。しかし実際にはそれだけではなく、現在のレコード、レイアウト、ソート順、表示モードなども記憶します。基本的な考え方は、スナップショットリンクを保存し、同僚にメールで送信すれば、あなたが見ているものをそのまま再現できるというものです。これは、別の建物や別の州にいる誰かに向けて、対象レコードセットを再現する方法を説明するよりも、はるかに効率的な方法です。この記事では、スナップショットリンクを利用して、複数の対象レコードセットを保存して、将来それをまた取り出して復元する方法を紹介します。

snapshot save
 

ごめん!  

自分が対象レコードセットに関する連載記事を未完のままにしていたことを思い出しました。まったく忘れっぽい老人です(笑)。そして、実際にはこのシリーズにはさらに2つの記事があります。もう一つの記事は、実際の検索条件を復元する方法に関するもので、近々公開する予定です。これはシリーズの中で最も複雑なものなので、最後にとっておきたいと思います。もしあなたが前の3つの記事を見る前にこの記事を見つけたという場合は、以下のリンクから参照可能です。あらかじめそれらを読んでいないと、それらを参照している部分が理解できないかもしれません。

1) 対象レコードの保持

2) 対象レコードの保存

3) ユニークな値の収集

 

最初のステップ

まずスナップショットリンクの仕組みを理解してください。非常に簡単なものですが、一応確認しましょう。サーバで公開されているデータベースを開き、[ファイル]メニューから[名前を付けて保存/送信]を選択します。サブメニューに、3つの選択肢が表示されます。「スナップショットリンク…」を選択してください。FileMakerの保存ダイアログが開き、ファイルに名前を付けるように求められます。対象レコードのセットを表現する名称を付けて.fmpslファイルを作成します。

参考:ローカルで起動されたデータベースから.fmpslファイルを保存することは、リモートの受信者がFileMakerファイルにアクセスする方法がないため、意味がありません。

.fmpslファイルの実体は、スナップショットリンクが作成されたときのデータベースのステータスを記述したXMLドキュメントです。上で述べたように、ソート順、レイアウト、表示モード、現在のレコード、対象レコードのセットが含まれています。また、ステータスツールバーの状態と表示形式(フォーム、リスト、テーブル)も保存します。XMLコードを見たい場合は、.fmpslをテキストエディタで開きます。読むのはそれほど難しくはありません。

スナップショットリンクを開くと、合わせてファイルへのパスも保存されていることに気付くでしょう。注目すべき重要な点は、認証情報を保存しないので、受信者が.fmpslファイルを使用するためには有効なアカウントとパスワードを持っている必要があるということです。つまり、.fmpslファイルは安全であり、データベースを危険にさらす情報は何も含まれていません。

snapshot save
 

基本的な考え方

今回の手法の基本的な考え方は、スクリプトを介してスナップショットリンクを保存し、テーブルのオブジェクトフィールドに挿入するというものです。これにより、対象レコードセットが保持されるため、後日、別のスクリプトを使用してファイルをエクスポートして開くことでそのセットを復元することができます。簡単だと思いませんか? 実際には少し手間がかかりますが、特にスクリプトを使用して検索条件を復元する方法(これについては今後別の記事で説明します)と比較した場合、この手法はかなり単純であると言えます。

 

テーブル

対象レコードセットを保存するデータのテーブルは既に存在するはずです。この記事では、シンプルなCONTACTSテーブルを使用します。追加する必要があるのは、SNAPSHOTSテーブルです。ここで本当に必要なフィールドは2つだけです。

1) snapshot (オブジェクトフィールド)
2) name (テキストフィールド)

念のため、私はすべてのテーブルに常に主キーを設定するようにしています。スナップショットのレコードが誰によってまたはいつ作成されたかを追跡する必要が出てきた場合のために、レコードを作成あるいは変更したアカウントとタイムスタンプを記録するための管理用フィールドを追加しておくことも、悪い考えではありません。ダウンロード用のサンプルファイルには、この手法に集中するために、2つの基本的なフィールドのみが含まれています。

 

スナップショットを保存する

FileMakerには、スナップショットリンクの保存を自動化するための、対応する「レコードをスナップショットリンクとして保存」と呼ばれるスクリプトステップが存在します。他のいくつかの補助的なスクリプトステップを合わせて、スナップショットリンクをテーブルに保存する基本的なスクリプトは簡単に作成できます。

snapshot save

最初のステップは、グローバルフィールド「xname」の初期化で、フィールド設定ステップの後、カスタムダイアログの表示スクリプトステップで指定しています。これによって、以前に保存された値や、開発者が最後にシングルユーザモードで使用した値を確認する必要がなくなります。ユーザが現在の対象レコードのセットを保存することを選択した場合、If構文内の最初のステップは「変数を設定」です。ここで、この後保存されるスナップショットリンクファイルの場所を、Get(テンポラリパス)関数で指定しています。これにより、FileMakerを閉じるごとに削除される隠しフォルダにファイルが作成されます。これは、ハードドライブが乱雑になるのを防ぐために、ユーザが見る必要のないファイルを一時的に置くのに最適な場所です。

参考: マルチユーザ環境でホストされたソリューションを誰かが開いたときのグローバルフィールドの初期値は、常に開発者がグローバルフィールドに残した最後の値になります。そのため、すべてのグローバルフィールドを、ファイルを開くときに実行するスクリプトで、あるいはそれらが使用される直前に、初期化することをお勧めします。

場所が指定されると、「レコードをスナップショットリンクとして保存」を使って.fmpsl形式のXMLファイルが出力されます。「現在のレコード」ではなく「対象レコード」を指定します。そうしないと、結果が対象レコードのセットになりません。また、「出力ファイルの指定」で$Path変数を指定して、スナップショットリンクがテンポラリパスに保存されるようにします。

snapshot save

次のステップで、スナップショットリンクのファイルをSNAPSHOTSテーブルに挿入します。このプロセスは、SNAPSHOTSテーブルのレコードを表示するレイアウトにコンテキストを変更することから始めます。このタスクを実行するには多くの方法がありますが、私は新しいウィンドウをカードウィンドウとして表示する方法を選択しました。以前は標準のドキュメントスタイルウィンドウを使用していましたが、表示されたり消えたりするときにステータスツールバーが再描画される傾向がありました。「新規ウインドウ」スクリプトステップで「親ウインドウを淡色表示」オプションをオフにしている限り、新しいウィンドウがカードウインドウ形式で開いていることに気付くことはほとんどありません。ここでわざをさらに進めて、新しいウィンドウの上と左の位置を-10,000にして、画面の外に開きます。また、SNAPSHOTテーブルに基づいたレイアウトを指定することを忘れないでください(私は情報の詰まったこのダイアログでそれをよくやり忘れます)。

snapshot save

参考: 現在のウィンドウでコンテキストを変更することができるので、新しいウィンドウを開く必要はありませんが、カーソル、タブペイン、ポータル行のフォーカスが失われます。フォーカスを失わないよう新しいウィンドウを開く方が簡単です。

コンテキストが変更された後、新しいレコードを作成し、グローバルフィールドから名前を取得してフィールドに設定し、スナップショットリンクファイルを挿入します。これらのスクリプトステップは簡単なものであり、この記事を読んでいるあなたであれば既にツールベルトに入っているはずなので、説明する必要はないでしょう。最後に、ウィンドウを閉じて、ユーザを元のコンテキストに戻し、そこでボタンをクリックして対象レコードセットを保存し、プロセスが成功したことを知らせるメッセージが表示されます。

 

スナップショットの重複を防ぐ

そのままでスクリプトはうまく機能しますが、既に存在する対象レコードセットの名称を誰かが入力しようとするとどうなるでしょうか? スクリプトは、重複した対象レコードセットをSNAPSHOTSテーブルに追加しますが、これにより問題が発生します。まず、どのレコードにどちらの対象レコードセットが入っているかがわからなくなります。それらを復元して対象レコードセットを比較する以外に方法はありません。第二に、復元する対象レコードセットを選択する仕方に応じて、さまざまなレベルの問題が発生します。対象レコードセットを復元する方法についてはまだ説明していませんが、先に考える必要があるため、ここでこの問題に言及しておきます。SNAPSHOTSテーブルの内容に基づいてポップアップメニューを使用するときに、同じ名前の対象レコードセットが2つある場合、1つしか表示されません。データピッカーを使用すれば2つが表示されますが、それにしてもどちらが目的の対象レコードセットかは確認することができません。

どう見ても、重複する名前を入力できないようにする方向で解決する必要がありそうです。まず思いつくのは、[データベースの管理]の[入力値の制限]オプションを使用して、ユニークな値のみを許可する方法です。これは簡単なチェックボックスによる設定で、機能をオン/オフで簡単に切り替えることができます。間違いなくエントリーレベルのソリューションですが、多くの状況でうまく機能するでしょう。しかし残念ながら、このソリューションには、重複する可能性のあるレコードを追加するスクリプトも含まれていて、スクリプトと検証を連携させることが困難です。スクリプト内で検証エラーをキャプチャできると考えた方もいると思います。

504 – Value in field is not unique, as required in validation entry options (フィールドの値が入力値の制限オプションで要求されているように固有の値になっていません)

このアプローチの問題点は、重複があるかどうかを判断するために、SNAPSHOTSテーブルに一旦レコードを追加する必要があることです。これはつまり、重複がある場合はそのレコードを削除する必要があることを意味します。私はその代わりにloop構造を使うことにしました。以下のスクリーンショットでは、重複チェックを可能にするために追加したスクリプトステップにマークを付けました。ステップの2、3、6、7、8、17、18、19、20、22がそれに当たります。

snapshot save

ステップ2は単なるコメントです。ステップ3および22で、ユニークな値が入力されるまでスクリプトをループ状態に保つために、loop構造を追加しています。ステップ6は、CONTACTSテーブルのxnameグローバルフィールドとSNAPSHOTSテーブルのnameフィールドを使用して、リレーション全体を検査してユニークかどうかを判断します。通常、私はリレーションシップグラフが乱雑になるのを避けるためリレーションシップを単一の目的で使用するようにしていますが、今回はこのリレーションシップが別の目的で後ほど再度使用されます。

snapshot save

参考: ExecuteSQLを使用してSNAPSHOTSテーブルの既存の値を確認すればテーブルオカレンスとリレーションシップを追加する必要はありませんが、ExecuteSQLではこの後リレーションシップを使って行うもう一つの処理ができません。

このloopスクリプトの基本的な考え方は、スクリプトが名前を確認して終了できるようになるまでループを続けることです。ループを終了するのは、Exit Loop Ifスクリプトステップです。Exit Loop Ifステップの式が単に1であることに気付くでしょう。Exit Loop Ifスクリプトステップの計算ダイアログの下部をよく見ると、ブール値の結果が必要とあります。ここでは単純にTrueの結果を与えるので、Exit Loop Ifスクリプトステップに到達するとスクリプトは終了します。Exit Loop Ifスクリプトステップに到達したかどうかを判断するのは、リレーションシップ全体にわたる評価を行うIfスクリプトステップです。

 

対象レコードセットの復元

保存済みの対象レコードセットを含んだポップアップメニューを表示するのに今回はポップオーバーを使用することにしましたが、保存済みの対象レコードセットを選択する方法はたくさんあります。その一つとして、SNAPSHOTSテーブルのレコードを表示して、ユーザがレイアウトから検索できるようにすることもできます。あるいはデータピッカーの手法を採用して、リレーションシップとポータルを使って利用可能な選択肢を表示することもできます。それぞれにメリットがありますが、今回は作業量が最も少ない方法を採用しました。もっとも簡単なものが常に最良の選択であるとは限りませんが、今回はそうだと思います。

参考: 保存する対象レコードセットの数が多い場合、またはそれらをユーザごとに表示する必要がある場合は、検索またはフィルタリングしたポータルの方がインターフェースとして適しています。

まず最初に、グローバルフィールドが必要です。幸いなことに、既存の「xname」フィールドを再利用できます。これを、以下に説明するスクリプトを実行するボタンと一緒に、ポップオーバーに配置するだけです。ユーザの利便性のために、ポップオーバーを開くときにグローバルフィールドを初期化します。これは、ポップオーバーペイン(ボタンではなく)に関連づけられたOnObjectEnterスクリプトトリガーと、「フィールド設定」を使用して、実行できます。これを行わないと、前に選択した値か、FileMaker Serverにアップロードする前に開発者がグローバルフィールドに残した値が表示されてしまいます。

snapshot save

スナップショットリンクを復元するスクリプト自体は、それを保存するスクリプトと比較すると、かなり単純です。本当に必要なのは、SNAPSHOTSテーブルで目的のレコードを検索して、「関連レコードへ移動」ステップ(GTRR)を実行することだけです。他にもいくつかの管理用の予備的なステップがありますが、それについては以下をご覧ください。

スナップショット保存

最初のステップでは、復元可能な対象レコードセットのリストを表示するポップアップメニューから、選択が行われたことを確認します。値が選択されたら、ポップオーバーが閉じられ、レコードがコミットされます。GTRRの前には常に、リレーションのキャッシュをディスクに書き込むために、レコードをコミットすることをお勧めします。次のステップは、ポップアップメニューからの選択に対応するSNAPSHOTSに含まれるレコードを表示するGTRRです。レコード/レイアウトは、画面の再描画を防ぐために新しいカードウィンドウで開くので、[親ウィンドウを淡色表示]オプションをオフにしてください。また、無駄な表示が残るのを避けるため、表示画面の外でウィンドウを開くようにします。

最後に、最初に対象レコードを保存したスクリプトと同じように、変数にテンポラリパスを設定します。パスは、[自動的に開く]オプションを使用すると、[フィールドコンテンツのエクスポート]に渡されます。最後のステップで、カードウィンドウを閉じて、コンテキストの変更をクリアします。最後に、スナップショットリンクの標準機能に従って、対象セットが新しいウィンドウに復元されます。

 

保存されたウィンドウサイズ

スナップショットリンクによって生成されるウィンドウのサイズと位置は、変更された最後に閉じられたウィンドウに基づいています。ほとんどの場合、これは問題ではありません。私は、この記事のサンプルファイルを作成していたときに、この問題に気づきました。スナップショットリンクの復元によって生成されたウィンドウが、すごく小さなサイズでした。そしてこれが、ファイルを開いたときに作成するスプラッシュスクリーンと同じサイズだとわかりました。それだけでなく、ファイルを閉じるときに実行するスクリプトを使ってスプラッシュスクリーンのサイズを制御する方法を組み合わせて、開いたときにウィンドウのサイズ変更が起きないようにしていました。

残念ながら、これにはデフォルトのウィンドウサイズを変更してしまうという副作用がありましたが、それはデータベースを閉じる前に変更が加えられた場合のみです。少し混乱していましたが、3〜4回試みて挙動を理解し、スクリプトで回避することができました。私は、(サンプルファイルの)ウィンドウのサイズを変更するスクリプトステップを無効にし、単純な版を提示することにしました。この手順はそれほど革新的なことをしているわけではないので、オンにしても大した問題にはなりません。無効にしたのは、ソリューションを単純なものにしたかったのと、とにかくありそうもないケースだったからです。

snapshot save

ステップ6、7、8、9は、新しいウィンドウを開き、ウィンドウを[収まるようにサイズ変更]し、フィールドを元からある値に再度設定してからウィンドウを閉じるだけです。一連の4つのステップでデフォルトのウィンドウサイズが設定されるため、スナップショットリンクの復元がどのように動作するかが確実になります。ありえる唯一の問題が、ウィンドウロックレコードロックです。これらの2つの問題を回避するためのテクニックについては説明しませんが、この実は奥の深い問題を掘り下げたい場合に役立つリンクを示しました。個人的には、ごくたまにウィンドウがおかしなサイズで開くのは、それほど大問題だとは思いません。80/20ルールだと考えてください。不必要な20%の機能のために、プログラミング作業の無駄な80%を費やさないでください(今回の場合は、99/1ルールにより近いかも知れません)。

 

複数のテーブル

複数のテーブルに対応させる場合は、既存の対象レコードセットの名称とスナップショットリンクを保存する2フィールドに加えて、テーブル名を格納するフィールドを追加する必要があります。追加によるプログラミングの変更箇所は2つありますが、これらはサンプルファイルには示されていません。まず、使用するテーブルを決定する条件をプログラムする必要があります。これにより正しいGTRRスクリプトステップに分岐できます。このために、サポートされるすべてのテーブルのテーブルオカレンスとリレーションシップを作成します。したがって、複数のテーブルに対応させる場合、対象レコードセットの名称の重複を判別するExecuteSQLの手法と、正しいSNAPSHOTレコードを特定するための検索の手法を再検討してください。私の経験では、大抵の場合1つのテーブルの対象レコードセットを保存/復元できればことが足りるので、このソリューションをわざわざ複雑にする必要はありません。

次に、現在のテーブルに関連した対象レコードセットのみを表示する、動的値一覧のメニューが必要です。この手法は、ユーザごとに異なる対象レコードセットを表示するためにも使用できます。私が考えるところではこちらの方がよりありがちなケースですが、実装はそれほど難しくはなく、Get(アカウント名)関数を含む非保存の計算フィールドとして定義した主キーフィールドに基づくリレーションを使用して解決することができます。

 

あなたのツールベルトに

このテクニックは、私のツールベルトの中でも好んで使うツールの1つです。理由はシンプルで、シリアル番号を収集するのに比べて、簡単で高速だからです。次の記事では、検索条件の保存について説明しますが、実際のソリューションでこれを実装したことはありません。私がそれを提示するのは主に、「頭の体操」として、まったく別のソリューションをプログラムしようとしているときに、どこかで絶対に役立つだろうという思いからです。

 

(John Mark Osbornのサイトから月$5の寄付をした方は、元記事のリンクからサンプルファイルにアクセス可能です。)

Leave a Reply