スキーマ情報とデータの境界をぼかす

プログラムのコードをプログラムで動的に生成することを「メタプログラミング」といいます。
今回はFileMakerによるメタプログラミングの可能性についてのFileMaker HacksのKevin Frankさんの記事を紹介します。


FileMaker Hacks logo

スキーマ情報とデータの境界をぼかす
( Blurring the Distinction between Schema and Data part 1, part 2 )

Kevin Frank
2014/10/31 (Part 1)
2014/11/1 (Part 2)

先日、オレゴン州PortlandのAce Hotelのこの窓の内側で、「コードの動的生成」あるいは「スキーマ情報とデータの境界をぼかす」と題してPause On Errorのプレゼンテーションを行いました。

poe-600

プレゼンテーションの目的は、ビジネスロジックやプレゼンテーション層のロジックを、通常の場所であるスキーマ層から、a) データ層、あるいは b) 他のスキーマ領域に移動させるいくつかの方法を探ることでした。aの例としては、オブジェクト名や計算式をデータとしてテーブルに保存する方法、bの例としてはオブジェクト(フィールドやスクリプト)自体の名前を変更することによって振る舞いを変える方法があります。

GetFieldName() と Get(スクリプト名)

私たちはまず最初に、FM 11のリリース直後に私が書いた記事「GetFieldName: New in 10, Improved in 11(バージョン10で導入され11で改良されたGetFieldName関数)」に添付したデモファイルを見ることから始めました。

全体の話は元記事に書いてありますが、デモの基本的なアイデアは、フィールドを定義するときにGetFieldName(Self)を使うことでそのフィールドが自分自身の名前を認識できるようになり、それをうまく使うことによって、たとえば今回の例では最近3年間の情報を横並びに表示させることができます。

フィールド定義は同じですが、(当然ながら)フィールド名は違います。

フィールド名を1年ごとに増やすときには、単純に以下を行います。

  • pax_2013をpax_2014に名称変更する
  • pax_2012をpax_2013に名称変更する
  • pax_2011をpax_2012に名称変更する

さらに同じ場所に、ユーザがファイルをFM 10でも開くことができるようにしながら、10以前のバージョンで開いた場合にはデモが正しく動作しないことを警告したいので、以下のように起動時にFileMakerのバージョンをチェックするサブスクリプトを挿入しました。

ここではGet(スクリプト名)関数を利用しました。もちろんこの手法は、作り込みたければ13.0 v2のようなマイナーバージョンも含むようにもできます。今回はメインバージョンだけをチェックしていますが、いずれにしても必要な最低限のバージョンを変更したければ、スクリプト内のコードを更新するよりも、単にスクリプト自体の名称を変更するのが簡単でしょう。

コードの自動生成、XML検証、etc.

次にColleen Hammersleyが提供してくれた、複雑なFM 11のソリューションからのスクリーンショットを紹介します。一つ目のサンプルでは、コスト計算のロジックが「Formula」フィールドに保存され、それぞれの項目が正しく計算されるようになっています。この手法のメリットの一つは、権限を持ったユーザがポータル内のデータを編集することで簡単にコスト計算のロジックを修正できる点にあります。

Compound Quoting 2

次に示すのは、見積もりの生成中に動的に組み立てられたXMLです。

Quote XML a

これがさらにHTMLに変換され、Webビューア内に見積もりが表示されます(すばらしい!!!)。

Quote XML 2

彼女はXMLをデータ検証のためにも利用しています。

Validation via XML a

そして最後に、ここではグルーピング(“Hem”, “Pole Pocket”, “Lining”, “Grommet”)によって、項目ごとの複数の属性を用いた動的で複雑な処理で混乱しがちなところを、わかりやすく整理しています。もちろん属性はデータですが、この例では属性のラベル自体(Width、Location、など)もデータであることに注目してください。これは、ポータルのすぐれた使い方です。

Fabrication Options 2

国際郵便の住所表記(コードの動的生成)

ここからは、10年前にあるクライアント向けのソリューションで使用した手法のデモに移ります。

解決しようとしていた問題は、発送先の国の郵便制度に合った形式で送付ラベルをフォーマットするというものです。例えば、アメリカ、カナダ、オーストラリアでは、住所を以下のように表記します。

label 1

しかしメキシコでは郵便番号を市と州の前に書きます。

label 2

イギリスでは州(statesやprovinces)が存在せず、郵便番号は独立した行に配置します。

label 3

結局住所の市の行の表記方法は少なくとも22通りあるということがわかりました(詳しくはFrank da CruzのCompulsive Guide To Postal Addressesを参照してください)。またUS postal serviceによると、「国」として分類されるものはおよそ250あります。結局私がとった方法は、「international_addressing」テーブルに22レコードを作成し、市の行の22通りの表記ルールを保持しました。

「countries」テーブルには、250件のレコードが以下のように入っています。

このテーブルは、「contacts」と「international_addressing」の結合テーブルとしての役目を持ちます。

最後に宛名ラベルを定義します。ポイントとなる部分をハイライトしています。

内部レイアウトID, パート1

次に複数ファイルからなるデモを紹介しましたが、これは元々は2002年にDIG-FMユーザグループで発表し、その後.fp7ファイル形式に更新し、最近再度ナビゲーションサブシステムに少し改良を加えたものです。

ところで「メイキング」のドキュメンタリーの方が元の映画よりも面白かったという経験はないでしょうか? ここで紹介するケースも同じような例です。デモの当初の目的(FileMaker 6の値一覧の挙動を詳しく調べること)は今回は関係なく、FileMakerをPowerPointの代わりにプレゼンテーションツールとして使って、自由に箇条書きの項目をその場で表示させたしくみが、今回取り上げた理由です。もちろんその当時はタブパネルの機能はまだなかったので、このタブコントロールに似せた仕組みで15個の別々のレイアウトを使用しました。そのうちの13個で箇条書きを表示する必要がありました。

なお、親ファイルには1レコードしかありません。(はい、「ファイル」で間違いありません。このソリューションは元々はFileMaker 6で作成されたものであり、その当時はテーブル毎にファイルを作成する必要がありました。)そこで各レイアウトの内部IDをリレーションキーとして使用することにしました。カスタム関数を使うという選択もありましたが、式は再帰ではなくFileMaker 6の時代にすでに必要なデザイン関数がすでに存在していました。現バージョンの文法で書くと、現在のレイアウトのIDを取得する式は以下のようになります。

GetValue ( LayoutIDs ( Get( FileName ) ) ; Get ( LayoutNumber ) )

そしてMikhail Edoshinが先日FMP Expertsのメーリングリストで指摘したように、Get(ファイル名)の代わりに空の引用句を使うことができるので、以下のように書いても同じように動作します。

GetValue ( LayoutIDs ( "" ) ; Get ( LayoutNumber ) )

なぜ内部レイアウトIDをわざわざ使うのか? レイアウト名やレイアウト番号を使えばいいのではないか? その理由は、その方がより堅牢だからです。レイアウト名やレイアウト番号は簡単に変更できますが、レイアウトIDは変わることはありません(たとえファイルを複製しても)。そこで、私は箇条書きテキストを表示したいレイアウトの内部IDをすべて控えました。

そして、以下のように箇条書きテキストを関連テーブルに保存しました。

次のステップで、メインテーブルを「箇条書きテキスト」テーブルと関係づけます。

そして最後に、必要なレイアウトにポータルを配置します。

ところで、上の例は内部レイアウトIDの「標準的な使い方」ではありません。一般的にはレイアウトの切り替えのために使われるものなので、デモファイルをそのように作り変えることにしました。上で書いたように、このデモのタブコントロールを真似たものは15の別々のレイアウトからできています。これらのレイアウトの内部IDをすでに確認してわかっているので、簡単なレイアウト切り替え用のスクリプトを作成しました。

そしてこのスクリプトを各ナビゲーションボタンから呼び出します。

標準の機能では、レイアウトをIDで指定することはできず、名前かレイアウト番号を用いる必要があります。そこでnavスクリプトが実行時に以下のカスタム関数を使ってIDをレイアウト番号に変換します。

このサンプルではレイアウトIDは各ボタンのスクリプト引数に決め打ちされていますが、次の例では動的に処理されます。

内部レイアウトID, パート2

次に見たソリューションでは、「nav配列」を使って、a) ボタンのラベル、 b) 内部レイアウトIDを保持します。

そして、c) ソリューションの起動時にふさわしい名前でグローバル変数を生成します。例えば、上の図のハイライトされたレコードは、「$$Admin.0.4」という名前の複数行からなるグローバル変数を生成し、それぞれにボタンラベルとターゲットのレイアウトの内部IDを保持させます。

さらにボタンパレット上の各ボタンは、引数としてコード中に直打ちされた内部レイアウトIDを渡すのではなく、その代わりに自分自身のボタン番号を渡すようにしました。

ボタンをクリックすると「navigation」スクリプトが起動し、このコード部品を他の情報と一緒に使用して対象となるレイアウトIDを選択します。

結果としてボタンパレットを複数のレイアウトから再利用することができ、もし特定のボタンの振る舞いを変えたければ、nav配列のデータを編集することで対応可能です。

多言語対応 (動的な変数のインスタンス化)

上のソリューションはMatthew LeeringのMultilingual LabelMakerも利用していて、そこではグローバル変数の名前と対応するテキストを以下のように保持しています。

通常グローバル変数は「変数を設定」スクリプトステップで生成し、変数名は決め打ちになります。しかし今回の例では、言語設定が変更されたら対象レコードをループして各レコード毎にこのコードを起動して変数が動的に生成・更新されます。

(この手法の詳細については、Dynamic Variable Instantiationを参照してください)

結果的に複数の(動的に生成された)変数に、正しいテキストが設定された状態になります。

それらはマージ変数としてレイアウトに配置されます。

最終的には以下のようになります。


*******
(元の記事は2回に分けて公開されました。以下が後半です。)

ScriptMakerを作り直す

引き続き、「実行コード」をデータとして保存できるということについて、紹介していきます。私が今までに見たなかでもっとも極端なサンプルは、DevCon 2006でDr. Ray Cologonがプレゼンテーションした、コンセプトを証明するために作成されたText Script Interpreter (TSI)です。

免責事項: TSIは、元々スクリプト機能の拡張性をデモして議論する目的で開発されたものです。具体的な要求を解決するため、あるいはスクリプトのベストプラクティスの一般的な規範として提案されるものではありません。

基本的な考え方は、FileMakerスクリプトはすべてテキストで表現することおよびテキストから変換することが可能(あくまで「可能」であり、そうすべきということではない)だということです。
この例ではテーブル内のレコードとして保持しています。

こちらは単純なテキストの場合です。

コードを解釈して実行する「メタスクリプト」を設計するのに要した時間とエネルギーは相当なものであったと思います。(FileMaker 8.5の機能でこれを実現したことを考えるとさらに驚かされます。)

わざわざこのようなことをするメリットのひとつは、すべてのスクリプトの実行ログを記録できることです。

Dr. Cologonに補足の説明があるか確認したところ、以下のようなコメントをいただきました。

このデモの元々の目的はコンセプトを証明するため(挑発する、という意味も)ではありますが、手法のひとつひとつは実際の現場での応用先があり、ソリューションの中でスクリプトの数や複雑さを大幅に削減しつつ、全体の見通しをよくして運用の柔軟性を上げてくれるものです。TSIを作成した動機は、ひとつには実際にどこまで可能なのかの限界を示すことでした。具体的には、メインスクリプト1つ(とそれに加えて多少の補助的なスクリプト)だけのソリューションです。そしてそうすることによって、どの程度であればこの方法を採用してもよさそうかを開発者に検討してもらうきっかけになればと考えました。

この手法の応用例の1つは、エンドユーザー向けに簡略化されたスクリプト環境(と簡略化された文法)を提供して、簡単なコマンドの実行順序を指定するとそれが自動的に実行されるというシステムです。それによって、ユーザが使うかもしれない組み合わせや手順を開発者があらかじめすべて予測して作りこんでおく必要がなくなり、また開発者なら習得している大量で複雑なスクリプト体系をユーザがわざわざ覚えて使いこなす必要もなくなります。ユーザは、開発者によって定められた限られたスクリプトの語彙と機能の制約の範囲内で(生のスクリプトエンジンにはアクセスすることなく)、ユーザ用スクリプトをデータとして作成・保存することができます。デモで示したTSIのフル実装は必要ないかもしれませんが、ビジネスの現場での応用例は確実にあると思います。

「URLを開く」でスクリプトを実行する

次に見たのは、John Renfrewが現在作成中のもので、すべてのスクリプトが「スクリプトを実行する」ではなく「URLを開く」から呼び出されます。かつては (FileMaker 13より古いバージョンでは)、スクリプトを名前で呼び出す場合はプラグインを使う必要がありました。しかし今では100% FileMakerの標準機能で実現できます。(この手法を、すべてのプラットフォームでかつローカルファイルとホストされたファイルの両方で同じように動作させるには、FileMaker 13.0 v2以降が必要です。)

この一つ目の例では、スクリプト名(「GoToTraineeDetail」)が直接コードに書き込まれていて、URLの構造がよくわかります。

スクリプトが直接コードに埋め込まれている

次の例も「URLを開く」を使っていますが、こちらは動的に処理を行っています。

以下がレイアウトモードでの表示です。それぞれのボタンは透明なポータルの1行になっています。そしてボタンは実際には繰り返しフィールドの1個目に入っているボタンラベルのテキストで、同じ繰り返しフィールドの2個目にはスクリプト名が入っています。

ユーザがボタンをクリックすると、クリックされた行のデータからスクリプト名が次のように生成されます。

最後にこのバージョンがグローバルフィールドの内容をチェックして、グローバルフィールドが空であればコードに埋め込まれたスクリプト(“GoTo THIS record”)が実行されます。そうでなければ、グローバルフィールドに入っているスクリプト名のスクリプトが実行されます。

これによって、1つのレイアウト上の1つのボタンで、状況によって違うことを実行させることができます。

スクリプト名かスクリプトIDか?

ここまでのところで、スクリプトを実行するときは名称ではなくIDを指定するべきだと説明してきました。FileMakerにはScriptNames関数とScriptIDs関数があるので、これらの関数を使ってScriptsテーブルでユーザがアクセスできるスクリプトにマークを付けるという管理方法も可能です。

これらのスクリプトには、権限を持ったユーザだけが専用レイアウトを介してアクセスできるようにします。

これは「2番目のフィールドの値も表示」でかつ「2番目のフィールドのみを表示」のオプションをしています。

ユーザにはスクリプト名が表示されますが、裏側ではIDを保存しています。そして実行時にはIDを再度スクリプト名に変換しています。

この方法の主なメリットは、スクリプトの名称が変更された場合でのコードを変更することなく動作する点です。(カスタム関数の詳細についてはこちらを参照してください: ValuePosition: the Function FileMaker Forgot).

そしてスクリプトIDの話題が出たところで、Fabrice NordmannのすばらしいFM_Name_IDカスタム関数を紹介します。この関数はほとんど全てのFileMakerオブジェクト名を対応するIDに、あるいはその逆に変換をしてくれます。 詳しくはこちらを参照してください Avoid Hard Coding (こちらの記事でも触れています Avoiding Brittleness)。

xmCharts (コードの決め打ち vs. 動的コード生成)

次は、実行コードをテーブルに保存する方法の極めて基本的なサンプルです。実際、基本的すぎて「コード」という言葉を使うのがはばかられるほどです。ここではxmChartを使用して、ある地域のある期間におけるワインの売り上げを、製造メーカーの国ごとに示しています。(ここでは見ての通り「国」という言葉をゆるい定義で用いています)

クライアントは、グラフエンジンが自動的に設定する色ではなく、国ごとの色を常に同じにしてほしいと希望しました。幸運なことにxmChartはどちらの方法にも対応できます(色を自動的に設定する、あるいはユーザが設定できる)。Case文で国と色の組み合わせを決め打ちでスクリプトに埋め込むこともできましたが、我々は国と色コードを保存するテーブルを作成することにしました。

グラフの生成時に、スクリプトがループして変数$fillを次の式で組み立てます。

変数$fillは最終的に以下のようになります。

そしてこのグラフ表示のためのコードの最後近くで呼び出されています。(私がxmChartsを好きな理由は、設定情報がテキストコードでできているのでカスタマイズ・デバッグ・再利用が簡単にできるからです。)

pie chart calc 2
変数 $fill

xmChartのコードをいつも上のような形式で書ければいいのですが、同僚でxmChartを使いこなしているMartha Zinkと話す機会があり、彼女によると作りたいグラフのタイプごとのコードをTemplateテーブルのレコードとして保存するのが便利だということでした。

そして実行時に、substituteによって適切な値に置き換えられて、グラフ表示用のコードが生成されます。

結果のグラフは以下のようになります。

下図は同じデータを別の形式で表示した場合の例です。

この先の話は、将来のFileMaker Hacksの記事でさらに掘り下げるかも知れないので、今回はここまでにしておきます。デモファイルをダウンロードしてくださいxmCharting-POE-2014。既存のグラフを表示するのにはプラグインは不要ですが、新規にグラフを作成したり、既存のグラフを修正する場合はxmChartプラグインの評価版か登録済バージョンが必要です。評価版を使用した場合、大きく「DEMO」の文字が作成・再作成したグラフ全体に表示されます。

偽のマージフィールド

次にこの手法のより日常的な使用例として、必要最小限の機能を実装したデモファイル(runtime merge code)を紹介します。この手法はとても大きな効果を産む可能性がありますが、デモでは最低限の機能を示します。

まずTemplatesテーブルがあります。

次にAliasesテーブルにはInputフィールドに偽のマージフィールドが含まれ、Outputフィールドに実行するコードが入っています。

ユーザがテンプレートを選択すると、

このカスタム関数がInputをOutputに変換し、

結果として以下のようなテキストを生成します。

デバッグの目的で手紙文の生成中にコントロールキーを押し続けるとマージデータの部分が赤字になります。

この時点でセッションの終了時間が近づいていましたが、残り時間を使って駆け足で、過去にFileMaker Hacksで公開した実行時コードを利用した2つの例を紹介しました。

ユーザフレンドリーなExcelエクスポート

前の例と同じく、この手法はコードをテーブルに保存し、FileMakerからExcelへのデータのエクスポートを支援します。

例えばこのデータから…

…このデータを生成します。(1行目にわかりやすいようにヘッダ行が含まれています。)

以下は元記事へのリンクです。

バーチャル計算式

最後に“バーチャル計算式(virtual calculations)”を紹介しました。

ここでは計算式のコードがスクリプトに保存されていて、ファイルの起動時に読み込まれます。

これらについても2013年に詳しい記事を書きました。

最後に

セッションに参加してくれた皆さんに感謝します。また、今年のPOE/Portlandを実現してくれたMatt Navarre、Gerald Chang、その他の方々に感謝します。すばらしいカンファレンスでした。

Leave a Reply