[IM]リピーター判別の汎用化

INTER-Mediatorに関して、1つ重要な仕様変更を考えています。エンクロージャー、リピーターの識別に関することです。現状は次のようなルールになっています。

(1)ターゲット指定(data-im属性)のあるノードを含む、TR、OPTION、LIタグ要素は自動的にリピーターとなる
(2)自動的にリピーターとなったノードの親ノードが自動的にエンクロージャーとなる
(3)DIV、SPANに限り、data-im-control属性を指定して、リピーターとエンクロージャーを明示的に指定できる

この3つ目のルールを廃止して、次のようなルールにしようかと思っています。ようするに汎用化です。(1)(2)のルールはそのまま継続させます。結果的に、(2)は(3′)に対しても適用されることになります。

(3′)data-im-control属性の値が「repeater」の場合、そのノード及び兄弟ノードのリピーターを集めてレコードの数だけ繰り返す。
(2′)repater群の1つ上位のノードは自動的にエンクロジャーであると識別する。

OL/ULだけでなく、DLなどのタグや、その他、SECTIONタグなど、エンクロージャー/リピーターとして使用したいタグがある点は、以前から指摘されています。SAPN/DIVが対応していれば、「なんとかなる」とは言え、時に不便なこともあります。結果的に、なんでもリピーターにしていいのではないかというのが考えた結論です。そうすれば、DIV内部にINPUTタグのリピートみたいなこともできます。

FileMakerのスクリプトで実現するインジェクションもしくはアスペクト指向

アスペクト指向プログラミング(AOP)はオブジェクト指向の世界での手法の1つですが、FileMakerでのスクリプトでそれを実現する方法が見つかったので、記事で残しておきます。AOPについては後ろの方でまとめて、まず、何を実現したかを具体的に説明しましょう。

マスターテーブルからの選択結果を記録する

データベースに次のようなリレーションシップを定義しています。フィールドmemoとtermはテキストで、後のフィールドは数値型です。_idの付くフィールドは連番数値を自動入力して、主キーフィールドととします。

shot3094

あるレイアウト(User Inteface)は、テーブルオカレンスのUI_Mainのレコードを表示させるように定義しました。そして、リレーションシップの先の2つのテーブルオカレンスを、3つのポータルに表示しています。このデカルト積を使うリレーションシップにより、Selectionsテーブルのすべてのレコードがポータルに表示されます。FileMaker 13からサポートされたポップオーバーボタンで選択入力をするときに、このようなリレーションシップを利用するのが1つの典型的なパターンではないでしょうか? ポータルには3つのボタンがありますが、それぞれ概ね同じ目的です。例えば、最初のポータルでは、ポータル内でのボタンを押したレコードに対応するselection_idフィールドの値が、select1フィールドに入力されます。その動作を3通り考えることで、AOPに迫るというのが概要です。当然、実際のソリューションでは主キー値となる連番はユーザーに見せないように作りますが、ここでは動作が一目瞭然となるように意識的に通常のソリューションでは見せない内容をレイアウトに表示して動作をチェックします。Memoフィールドの左側のClearボタンにより、Memoフィールドの文字列を消去します。

shot3123

ここでSelectAボタンがどうなっているのかを説明しましょう。SelectAボタンは一番簡単なというか、理解しやすい方法です。以下のように、ボタンをクリックすると、「Select Button 1」というスクリプトが実行されます。2つ目のポータルのボタンは「Select Button 2」、3つ目のポータルでは「Select Button 3」というスクリプトが動くように指定をします。ボタン名の番号と、スクリプト名の番号は対応していません。ちょっとわかりにくいことをやってしまってごめんなさい。

shot3124

3つのスクリプトは以下のようになります。つまり、ポータル内で選択したレコードのselection_idの値を、UI_Mainテーブルオカレンスのselect*フィールドに代入をしています。それぞれ、自分自身のポータルと、対応するUI_Mailのフィールドがスクリプトの設定に見えています。

shot3129

shot3130

shot3131

スクリプトの共通化

このような「同じようなスクリプト」は、共通化したいですね。昔のFileMakerなら、共通化は難しい上に、個別に記述できるから分かりやすいといった議論もあったわけですが、今時の複雑なソリューションでは、スクリプトの数が爆発する傾向にあり、1つのスクリプトにまとめる意義は高まっていると言えるでしょう。そういうわけで、「SelectB」ボタンでは、SelectAボタンの手法を1つのスクリプトにまとめてみました。3つのポータルにある各SelectBボタンは、「Select Button B」という共通のスクリプトを呼び出しています。

shot3125

この時、最初のポータルのSelectBボタンは、スクリプト引数として、次のような設定を行います。つまり、値を入力するフィールド名と、値を取り出すフィールド名を、改行で区切って指定をしておきます。それぞれのポータルのSelectBボタンは同一のスクリプトを呼び出すものの、スクリプト引数を異なるものにするということです。

shot3127

Select Button Bスクリプトは、次のようなものです。引数から値を取り出したものは、フィールド名の文字列です。その文字列のフィールド名で指定したフィールドに値を設定するには「フィールドを名前で設定」スクリプトを利用します。そして、文字列で与えたフィールドから値を取り出すのは、GetField関数を使います。引数を変数に入れると同時に、スクリプトステップは異なるものとなり、関数の利用も行わないといけません。

shot3132

共通化したことによるデメリット

ここまでのような単純なスクリプトで済ませられるのであれば、それはそれでいいのですが、実際の開発では複雑な要求が発生します。ここまでは、選択ボタンは同じ動作をするという前提でしたが、現実にはそういうことはあまりありません。ボタンごとに固有の要求が出てくることがあります。具体例としては次のようなことです。

  • あるポータルでのボタン選択をするときにだけ、別の選択肢は空欄にしたり、場合によっては初期値的な設定を強制的に行う必要がある
  • ある選択の場合に、ある条件が成り立つ場合にだけ、選択していいかどうかをダイアログで問い合わせる
  • ある選択肢の選択後に、テーブルの更新など、その選択肢に固有の処理を追加しないと要求が実現できない

こうなってくると、「そら見たことか、スクリプトはボタンごとに設定するのが王道だ」と思わず口走ってしまうかもしれませんが、未だに1ファイルに1データベースみたいな作り方をする発想が抜けきれないとしか言いようがありません。ここの例では、共通なステップは1つだけですが、複雑な処理は共通化しておかないとデバッグやメンテナンスコストが増大するのは言うまでもなく、システム構築の常識です。

もちろん、FileMakerでは、プログラミング言語的な柔軟性を犠牲にする分、ツールを使った開発がやりやすいとか、学習コストが低いという長所を得ています。しかし、なんとかならないかとも考えるわけです。

以前だと、「共通部分があるものの、違う部分もある」という場合にどういった手法をやっていたかといえば、まずは、スクリプトが分かれてしまうのは仕方ないとして、共通部分を「スクリプトの実行」ステップで呼び出すということです。もちろん、この方法は汎用的かつ共通部分の抽出ができるという面ではメリットですが、「違う部分」が発生するごとにスクリプトが増えることになります。また、ありがちですが、「違う部分がない」にもかかわらずスクリプトが別々とか、「違う部分がある」のにシステムの全然違う箇所からスクリプトを共通に使うなどの、後からの不具合発見を阻害する作りに往往にしてなりがちです。

1つの苦肉の索は、共通のスクリプト内に、IF〜ELSE IF〜END IFを利用して、例えば、押したボタンごとに分岐を作り、ボタンごとに違う処理を組み込む方法です。これだと、1つのスクリプトで共通に使えますが、一方で、ボタンが増えればIFの分岐が増えてしまいます。もちろん、ちゃんと作れば動作しますが、あるボタンの動作を追う時には無視するスクリプトが大量になってしまうなど、可読性が低くなります。また、自分が関係してるところだけをちゃんと書けばいいということは理屈の上では成り立つことですが、現実には変数を共通に使ったことでのバグの発生などが懸念されます。分離されていないことでの気づかない悪影響は、当然ながら気づくののコストがかかります。したがって、IFで分岐を入れるのはいいような悪いようなということになります。

共通スクリプトと個別スクリプトの分離

そこで、オブジェクト指向の世界でのDI(Dependency Injection)の手法により、関心ごとを分離して実装することができないかと考えました。例えば、ボタンごとにニーズが違うのであれば、ボタンごとに関心ごとが異なると位置づければ、これはAOPの実現でもあります。

まず、レイアウト上にある「SelectC」ボタンは、いずれも、「Select Button C」スクリプトを呼び出すようにしています。3つのポータル上のいずれも同様な設定になっています。

shot3126

以下のスクリプト指定は、1つ目のポータルのSelectCボタンのものです。スクリプト引数はSelectBボタンと同様な指定です。スクリプトは同一でも、ポータルごとに、設定するフィールドやポータルが参照するテーブルオカレンスが違うので、それらをスクリプト引数で指定をします。

shot3128

そして、「Select Button C」スクリプトは次のようなものです。コメントの「前処理スクリプトの呼び出し」部分が追加されています。「URLを開く」ステップと併せて示します。

shot3133

shot3108

ここで、このボタンをクリックしたスクリプトの関心ごとを、選択した結果を保存するフィールドで区別するということを考えます。この前提が上記のスクリプトの動作に必要となります。

ここで、例えば、1つ目のSelect3ボタンをクリックしたとします。すると、「Select Button C」スクリプトの最初の2つのステップで、次のように変数が設定されます。

  • $destField ← UI_Main::select1
  • $srcField ← UI_First_Selections::selection_id

そして、「URLを開く」オプションで、次の方なURLが生成されて、そのURLを開こうとします。FileMakerのURLスキーマは、ご存知ない場合には、どこかのサイト等で調べてください。

fmp://$/AOScriptTest?script=SelectButton_UI_Main_select1&param=any_parameters&$srcField=UI_First_Selections::selection_id&$destField=UI_Main::select1

最初の$で自分自身を参照します。URLのscriptパラメータは、その直前のscriptName変数の計算式をみてください。スクリプト名は、「SelectButton_<値を設定するフィールド名>」となります。値を設定するフィールド名は、ボタンのスクリプト呼び出しのスクリプト引数で指定されています。なお、コロンがフィールド名に含まれますが、無難にするためにアンダーラインに置き換えます。つまり、1つ目のポータルのSelectCボタンをクリックすると、自分自身の「SelectButton_UI_Main_select1」スクリプトを呼び出すということになります。ここで、「スクリプトの実行」ステップが使えない理由は、そのステップではスクリプトの選択はできても、変数等でのスクリプト名の指定ができないからです。なので、ここではURLスキームの利用を行います。「SelectButton_UI_Main_select1」スクリプトは例えば、以下のようなものを作成しました。select2とselect3フィールドを空白にします。ここで、レイアウトを指定する必要がない点は注目点の1つです。

shot3114

2つ目のポータルのSelectCボタンを押した時に呼び出されるURLでは、スクリプト名は「SelectButton_UI_Main_select2」となりますが、このためのスクリプトは作っていません。つまり、2つ目のポータルのSelect3ボタンをクリックした時、「URLを開く」では存在しないスクリプトをパラメーターに指定します。しかしながら、エラーとして出ないように「エラー処理」ステップを入れているので、要するに、存在しないスクリプトを実行しようとしても、スルーするだけです。

「SelectButton_UI_Main_select3」スクリプトは次のようなものです。SelectButton_UI_Main_select1スクリプトとSelectButton_UI_Main_select3スクリプトの最初の1行は同じで、パラメーターに指定した内容をmemoフィールドに入力することです。式が途中で切れていますが、結果を見れば明白だと思うので、式はここには記述していません。

shot3134

選択した時の動作を確認する

実際に動かしてみます。まず、Clearボタンを押してmemoフィールドを空白にして、3つ目のポータルのSelectCボタンをクリックしてください。すると、選択結果がselect3フィールドに入り、memoには何かの表示が見えています。つまり、Select Button Cスクリプトがボタンクリックにより呼び出され、その中で、SelectButton_UI_Main_select3スクリプトが呼び出されたということです。

shot3136

Clearボタンを押してmemoフィールドを空白にして、2つ目のポータルのSelectCボタンをクリックしてください。すると、選択結果がselect2フィールドに入りますが、memoは空白のままです。つまり、Select Button Cスクリプトがボタンクリックにより呼び出されただけの結果になります。

shot3137

Clearボタンを押してmemoフィールドを空白にして、1つ目のポータルのSelectCボタンをクリックしてください。すると、選択結果がselect1フィールドに入り、select2とselect3は空白となり、memoには何かの表示が見えています。つまり、Select Button Cスクリプトがボタンクリックにより呼び出され、その中で、SelectButton_UI_Main_select1スクリプトが呼び出されたということです。

shot3138

Select Button Cスクリプトでは、IF〜Else IFを使うことなく、クリックしたボタンに応じて処理を分岐させています。ここで、もし、新しくselect4フィールドを用意して、同様にポータルから選択できるようにしたとします。もちろん、ボタン等にSelect Button Cスクリプトを設定して、引数にフィールド名を指定します。そして、select4フィールドに特有の処理を、「SelectButton_UI_Main_select4」を記述して追加します。ここに、選択肢を設定する前に行いたい処理を記述します。こうして、フィールドが増えてスクリプトの利用元が増えたとしても、Select Button Cスクリプト自体に一切の変更を加えることなく、select4フィールド特有の処理を追加できることになります。実装のパタンとしては、GoFのパターンのStateに近いことをやっていると考えていいかと思います。

実開発への展開に向けて

まず、FileMaker的な考慮点を考えましょう。ポイントとなるのは、fmp://$/… スキームにより、変数で与えたスクリプト名で実行できる点です。この時、本当に通信するのではなく、同一のスクリプト実行環境で実行を行っているようです。その証拠として、「URLを開く」ステップの先で、「全スクリプトの終了」ステップを実行すると、呼び出した元のスクリプトも途中で中止してしまいます。また、このことは、「URLを開く」ステップの先で呼び出したスクリプトで、状況を判定して、これ以上の処理はキャンセルするという動作を組み込むことができることも言えるのです。また、グローバル変数を「URLを開く」ステップの先で設定して、それを呼び出し元でも参照できます。つまり、同一のグローバル変数が使えるので、「URLを開く」ステップは同一のユーザーセッション上で稼働していると言えるのです。この辺りの検証スクリプトの残骸が、コメントで残っているので、参考にしてください。

ここのスクリプトでは、前処理だけを実装しましたが、後処理、あるいはある特定の「中処理」を必要に応じて組み込むことも可能です。これは、呼び出すスクリプトの性質上、自由に決めることができるでしょう。一方、FileMakerの場合、その「仕様記述」に困難さんがある点も見逃せません。コメントに書くというのは曖昧かつ、記述者により違いや間違いの混入など、問題点は多いでしょう。開発のマネージメント上、状況に応じたベターな方法を見つける必要があります。

アスペクト指向スクリプトプログラミング

アスペクト指向とは、オブジェクト指向でのクラスの考え方では解決しにくい問題を扱います。クラスは単一の仕組みを持つのが理想ではありますが、いろいろな機能を組み込んだ結果、幾つかのクラスに共通の処理を実装してしまったというようなことがあるかもしれません。機能の実装においては、ある基準でまとめた結果、別の基準で見た時にはまとまってないということがあります。よくあるのがログ記録機能です。いくつかのクラスの実装ではログの記録があるものの、そのログの処理はクラスが違っても共通のことです。そうであれば、ログの記録ということ自体をその他のクラスとは別の独立したことと考える方が、明確にその機能だけに集中して検討が可能です。つまり、物事のある側面(Aspect)に注目すると、その側面は必ずしもクラスとしての分解に馴染まないことが出てきます。しかしながら、側面として検討した昨日をうまく実装する方法が必要になります。それがアスペクト指向プログラミングです。

一方で、アスペクト指向はオブジェクト指向を否定するものではなく、オブジェクト指向プログラミングの拡張の1つとして捉えるべきです。ただし、オブジェクト指向ではないプログラミングでも適用できる手法です。その代表的な実装方法がDIであり、DI(Dependency Injection)はプログラムのあるポイントに別のプログラムを挿入することです。ただし、オブジェクト指向の世界においては、インタフェースや抽象化クラスなどのいろいろなテクニックを使って、実装上の振る舞いや制約をきちんとしたルールに落とし込んでいると言えます。ある側面を定義したものをDIによって別のクラスに埋め込みをするということでの、アスペクト指向の実現できているということになります。アスペクト指向とDIは同一のものではありませんが、結果的には表裏一体な関係であると言えるでしょう。FileMakerでのInjection、つまり処理の注入は、若干明示的にはなってしまいますが、「URLを開く」でできることが以上のようにわかりました。しかしながら、あえてタイトルにインジェクトションだけを書いたのかというと、依存性つまりDependencyのコントロールが事実上できないという点があります。1つの分かりやすい側面は、名前さえ一致すればどんなスクリプトも呼び出せてしまうという点は、依存を無視しているともいえるわけです。

ここで示した例は、スクリプトにより選択肢の選択ができるという共通点があります。その意味では、Select Button Cスクリプトが1つのクラスだと思っていいかと思います。一方で、選択する先によって関心事が違うという状況を想定しました。つまり、あるボタンは「別のフィールドも更新する」という側面を持ち、別のボタンは「他には何もしない」という共通点の薄い別の側面があったわけです。しかしながら、「URLを開く」を応用した手法により、ボタンごとに異なる側面の実装を独立したスクリプトできるようになりました。したがって、この手法は、FileMakerのスクリプト実装におけるアスペクト指向プログラミングであると言えます。

[IM]INTER-Mediatorはなぜ“会社”にしないのか?

このところ、INTER-Mediatorはなぜ会社にしないのかという主旨の質問を聞かれることが何度かありました。INTER-Mediatorに対して、「なぜ、会社にしないのですか?」「これだったら投資してもらえるのでは?」といった疑問を持たれるようです。ちょうどいい機会なので、新居が考えることを書いておきたいと思います。

まず、INTER-Mediatorもそこそこ出来上がってきており、これを元に仕事ができるレベルになっているのは確かです。新居自身、既にコンスタントにINTER-Mediatorを使った開発案件を抱えるくらいになりつつあります。としたら、一般的に考えられるのは、会社を作ってよりビジネスを広げることでしょう。また、投資が得られたら、ツールの開発などに取り組むことも現実的な作業として考えられるでしょう。しかし、新居自身は「INTER-Mediatorの会社」を作る意思は今の所ありません。

考えられる理想的な状況は、会社を作ってビジネスが軌道に乗るということではなく、次のようなことです。INTER-Mediatorはあくまで素材として存在し、それをベースにビジネスを行う会社が複数共存(連立か?)できるような状況が思想的だと考えます。1社の製品として、1社がコントロールする場合、まず、一定のシェアを確保しないと、ビジネスが存続しません。それは、言い換えれば他者を排除することにもつながります。もちろん、多大なシェアを目指すのも1つの手法ですが、既に数度の飽和をしているIT市場であり、いくつものビッグプレイヤーに1社で臨むのはかなり無理があると考えます。また、そうした一種の賭けをやると、終わりも視野に入ってきます。ビジネスの表舞台に立てるというのは魅力である一方、ダメになった時にはゼロ以下になってしまうということもあります。

INTER-Mediatorが目指すものは、システム開発プロセスの変革であり、その結果、情報システムを広げることでITのメリットをたくさんの人が、より多くの場面で享受できるようにすることです。もちろん、そうした意思はINTER-Mediator特有のものではなく、多くの人たちが様々な手法でチャレンジし続けています。INTER-Mediatorはその流れのほんの小さなインパクトにしか過ぎないものですが、理想を目指して継続させることこそが、何らかの貢献になるのではないかと信じています。そのためには、ビジネスのフィールドに軸足を突っ込むことは、リスクを増やすだけと考えています。

ただし、その一方で、INTER-Mediatorでビジネスをすることには肯定的に捉えています。自分も、業務システムの発注を受けていますし、エミックさんのようにFMPublisherというビジネス展開の一翼をINTER-Mediatorベースに進めていらっしゃる会社もあります。新居自身は、他の皆さんがビジネス展開する上で、必要であれば支援は行います。また、特定の会社だけでなく、原則として複数の会社に対しても支援しています。INTER-Mediatorのコアは「コミュニティ」だと考えています。そのコミュニティで培われたものを持ってビジネスフィールドに進出する人や会社は複数あって、それぞれが独自にアイデアを持ち、協業し、あるいは競合して、世界が広がればいいのではないかと考えています。コミュニティを中心にして、ビジネスフィールドが広がるというイメージです。そして、コミュニティの主体を、新居個人ではなく、Committeeにしたのも、そういう願いの1つの現れでもあります。

こうした手法は、オープンソースを主体にした団体の1つの生きる道だと考えます。例えば、Apache Foundationも、ある意味同様な立ち位置ではないかと考えますが、法人格云々となるといろいろなやり方があるかもしれません。しかしながら、コアコミュニティモデルとも言うべき、コンセプトとそれを実現する素材としてソフトウエア開発をコミュニティで行い、ビジネスを行う主体とは別に存在させることで、多彩な展開が実現することを願っています。したがって、INTER-Mediator株式会社を作る意思は今の所はありません。

FileMaker Server 14をOS Xで稼働するときのWebのカスタマイズ

以前に、FileMaker Server 13とOS X ServerのWebという文書で、FileMaker ServerのWebを動かすための方法を書きましたが、そこで、.htaccessファイルなどを有効にする方法記述しました。しかしながら、FileMaker Server 14.0.4を新たにインストールした場合、前記の文書の記述だとエラーが出ることがわかりました。この点は、FileMaker Server 14のどのバージョンでも当てはまるようです。

ということで、設定ファイルはHTTPServer/conf/httpd.confは以下のように変更すれば良いでしょう。

<Directory "${HTTP_ROOT}/htdocs">
    AllowOverride All
    Options All -Indexes -ExecCGI -Includes
    Require all granted
</Directory>

FileMaker Server 13のとき、「Order allow,deny」「Allow from all」も、Directoryタグ内に書いていたのですが、これを書くと、こんなエラーが出ました。最初のエラーにより、ステータスは403が返り、もちろん、ブラウザでは何も出てきません。後半の「an unknown filter was not added: includes」のエラーは派生的なもの、あるいは副作用的なもののようです。

[Sun Nov 29 01:00:40.179248 2015] [authz_core:error] [pid 1534] [client 219.121.7.182:38310] AH01630: client denied by server configuration: /Library/FileMaker Server/HTTPServer/htdocs/index.php
[Sun Nov 29 01:00:40.179642 2015] [core:error] [pid 1534] [client 219.121.7.182:38310] AH00082: an unknown filter was not added: includes

それから、ディレクトリへのアクセスで、index.htmlよりも先にindex.phpを開きたい場合のDirectoryIndexディレクティブの設定は、上記のDirectoryタグのすぐ下にあるdir_moduleが組み込まれた場合の設定にあります。しかしながら、ここを変えても意図通りに動かないなと思ったら、/Library/FileMaker Server/Web Publishing/publishing-engine/php/yosemite/httpd.fmi.conf.phpの最後の行にあるDirectoryIndexディレクティブを修正しないといけないことが分りました。

Apacheの設定ファイルは、バージョンが違うとそこそこ違ったりしますし、管理手法も比較的変動が大きく、「以前使えた設定や手法が通用しない」ということはこのところ頻繁に経験します。注意しましょう。

[IM]INTER-Mediatorワークショップで作った“イベント参加申し込みサイト”

K-OFのイベントの1つとして、INTER-Mediatorのワークショップを2015年11月7日に開きました。ワークショップとなると、参加者の皆さんに手をうごしてもらって…というのが一般的ですが、その場で参加される方々もいらっしゃったので、手を動かすのはプレゼンターだけでやりました。

今回のワークショップでの開発目標は、イベントの参加申し込みです。別の勉強会での事後アンケートで、「こういうのを作りたい」と希望があり、それを題材にさせていただきました。グループカウンセリングを実施している赤石さんは、現在、フォームズというサービスで参加受付を行い、メールで送られてくる参加依頼の内容を1項目ずつコピー&ペースとしているそうです。当初は「コピペをしないで一発でExcelに入れる」という要望だったのですが、いっそのこと、受付フォームと一覧表示をINTER-Mediatorで作成するのはどうかということで了承していただき、それをワークショップの題材としました。なお、実際の運用サイトでは、以下のコード内にあるパスワードや一部の名称はもちろん変更しています。

12191812_909228155821822_1194109405261025992_n

設計内容とスキーマ

ざっくり書いたユーズケース図は次の通りです。アクターは、赤石さんと、グループカウンセリングに参加される「お客様」の2つです。お客様はブラウザで申し込みをして、その結果をメールで受け取ることになります。その申し込み結果を「申し込みリスト」に蓄積して、それを赤石さんは随時チェックするという流れになります。この時、お客様は認証等なく申し込みを行えるようにしますが、一方で赤石さんしかリストが見れないようにします。ここで、データベースへの新規レコードは認証なし、その他の処理は認証を行わないとできないようにするという基本設計もできてしまいます。また、システムとアクターの接点は2か所あり、それぞれ1ページだけで実現しそうなので、合計2つのWebページの作成で済みそうです。

ユースケース図0

ER図と同じ用途のクラス図は次のようになります。ちょっとシンプルですが、「申し込み」という1つのテーブルで作ります。本来、グループカウンセリングの実施が1つのエンティティになり、カウンセリングのテーブルを作成して、申し込みとイベントが多対1の関係を作ります。現状では1種類のイベントを、シーケンシャルに、つまり同時に複数のイベントの募集をしているといった事情もないので、1テーブルで運用します。言い換えれば、イベントには「開催日時」というフィールドしかないので、別テーブルにする必要性は薄いということです。「義援金支払い許諾」はチェックボックスで、受講条件の1つを許諾するかどうかを明確にするものです。「ユーザー情報」は、INTER-Mediatorで認証を実施するために必要なテーブル群です。

クラス図0

これらを実現するMySQL用スキーマとして、以下の記述を作成しました。実はワークショップでは、これに失敗して悩んでしまったのですが、今後のワークショップはこれを雛形にしましょう(苦笑。ユーザーやデータベースを定義してアクセス権を設定し、applyテーブル(クラス図での「申し込み」)を定義します。その後の、authuser、authgroup、authcor、issuedhashのテーブルは認証で必要とするものです。リスト表示の時に使用するuser1ユーザーのみ定義しています。

SET NAMES 'utf8mb4';
#DROP USER 'web'@'localhost';
CREATE USER 'web'@'localhost' IDENTIFIED BY 'password';
DROP DATABASE IF EXISTS akaishi;
CREATE DATABASE akaishi CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE akaishi;
GRANT SELECT, INSERT, DELETE, UPDATE ON TABLE akaishi.* TO 'web'@'localhost';

CREATE TABLE apply (
  apply_id INT AUTO_INCREMENT,
  name VARCHAR(100),
  yomi VARCHAR(100),
  email VARCHAR(100),
  address VARCHAR(255),
  content TEXT,
  agreement INT,
  startdt DATETIME,
  PRIMARY KEY(apply_id)
) CHARACTER SET=utf8mb4, COLLATE=utf8mb4_unicode_ci, ENGINE=InnoDB;

CREATE TABLE authuser (
  id INT AUTO_INCREMENT,
  username VARCHAR(48),
  hashedpasswd VARCHAR(48),
  email VARCHAR(100),
  limitdt DATETIME,
  PRIMARY KEY(id)
) CHARACTER SET=utf8mb4, COLLATE=utf8mb4_unicode_ci, ENGINE=InnoDB;

INSERT authuser SET id=1,username='user1',hashedpasswd='d83eefa0a9bd7190c94e7911688503737a99db0154455354';

CREATE TABLE authgroup (
  id INT AUTO_INCREMENT,
  groupname VARCHAR(48),
  PRIMARY KEY(id)
) CHARACTER SET=utf8mb4, COLLATE=utf8mb4_unicode_ci, ENGINE=InnoDB;

CREATE TABLE authcor (
  id INT AUTO_INCREMENT,
  user_id INT,
  group_id INT,
  dest_group_id INT,
  privname VARCHAR(48),
  PRIMARY KEY(id)
) CHARACTER SET=utf8mb4, COLLATE=utf8mb4_unicode_ci, ENGINE=InnoDB;

CREATE TABLE issuedhash (
  id INT AUTO_INCREMENT,
  user_id INT,
  clienthost VARCHAR(48),
  hash VARCHAR(48),
  expired DateTime,
  PRIMARY KEY(id)
) CHARACTER SET=utf8mb4, COLLATE=utf8mb4_unicode_ci, ENGINE=InnoDB;

申し込みページの作成

まず、お客様がイベントの申し込みを行うページを作ります。コンテキストの定義を含む定義ファイル(apply.php)は以下のようなものです。ポストオンリーモードでのページを作成すればいいので、コンテキストは1つだけです。また、新規レコードは認証なしで、その他のデータベース処理はできないようにしたいので、存在しないユーザー(nobodyknows)に対してのみ読み出し、更新、削除ができるようにして、事実上、これらの3つの処理は行えないようにします。authenticationキーにcreateキーの値がないので、createに関しては認証なく実施できます。オプション指定はなく、データベース指定のところに、dsn、user、passwordのキーで、データベースへの接続についての記述を行っています。

<?php
require_once('../INTER-Mediator/INTER-Mediator.php');

IM_Entry(
  array(
    array(
      'name' => 'apply',
      'table' => 'apply',
      'view' => 'dummy',
      'key' => 'apply_id',
      'post-reconstruct'=>true,
      'authentication' => array(
        'read' => array('user' => 'nobodyknows'),
        'update' => array('user' => 'nobodyknows'),
        'delete' => array('user' => 'nobodyknows'),
      ),
      'send-mail' => array(
        'create' => array(
          'from-constant' => "msyk@msyk.net",
          'cc-constant' => "msyk@msyk.net",
          'subject-constant' => "申し込みを受け付けました",
          'to' => "email",
          'body-template' => "confirm.txt",
          'body-fields' => "name,yomi,email,address,content,startdt",
        ),
      ),
    ),
  ),
  array(),
  array(
    'db-class' => 'PDO',
    'dsn' => 'mysql:host=localhost;dbname=akaishi;charset=utf8mb4',
    'user' => 'web',
    'password' => 'password',
  ),
  false
);

お客様が申し込みを行うページのぺージファイル(apply.html)は次のように定義しました。見出しの文字列は最低限にしていますが、実運用の時には書き直して利用していただくとして、INTER-Mediatorの動作に関わる部分だけを作ってあります。ヘッダ部での定義ファイルの読み込み、BODYタグのonload属性でのフレームワーク呼び出し、そしてボディ部での記述がポイントになります。ボディ部では、TABLEタグによるテーブルが定義されていますが、data-im-control属性により、ポストオンリーモードであることが指定されています。あとは、コンテキストとフィールド名のセットをdata-imに記述します。同意の項目はチェックボックスですが、value=1の指定があり、チェックがあれば1をフィールドに設定します。カウンセリングの日付はstartdtフィールドにしますが、ここの部分はお客様が入力するのではなく、募集するときに日時が決定しているので、その日時を初期値として規定し、INPUTタグ自体はreadonly属性を設定して、表示だけをするようにします。最後のBUTTONもdata-im-control属性を指定して、書き込みボタンにします。なお、定義ファイルのコンテキストにはpost-reconstructキーのみがあり、この場合は書き込みを行うとこのページの再ロードを行うので、そこで入力した結果が消えます。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script type="text/javascript" src="apply.php"></script>
</head>
<body onload="INTERMediator.construct();">
  <table>
    <tbody data-im-control="post">
      <tr>
        <th>名前</th>
        <td><input type="text" data-im="apply@name"/></td>
      </tr>
      <tr>
        <th>名前の読み</th>
        <td><input type="text" data-im="apply@yomi"/></td>
      </tr>
      <tr>
        <th>メールアドレス</th>
        <td><input type="text" data-im="apply@email"/></td>
      </tr>
      <tr>
        <th>住所</th>
        <td><input type="text" data-im="apply@address"/></td>
      </tr>
      <tr>
        <th>相談内容</th>
        <td><textarea data-im="apply@content"></textarea></td>
      </tr>
      <tr>
        <th>同意</th>
        <td><input type="checkbox" value="1" data-im="apply@agreement"/></td>
      </tr>
      <tr>
        <th>日程</th>
        <td><input type="text" data-im="apply@startdt" 
                   value="2015-11-07 13:00:00" readonly/></td>
      </tr>
      <tr>
        <th></th>
        <td><button data-im-control="post">申し込む</button> </td>
      </tr>
    </tbody>
  </table>
</body>
</html>

定義ファイルapply.phpのコンテキストには、レコードを作成した時にメールを送信する設定が組み込まれています。そのためのメールの文面は、confirm.txtファイルに以下のように用意します。メールについては、定義ファイルでの定義通り、差出人、CC、件名は決められたものを設定し、送り先は作成されたレコードのemailフィールドを指定します。本文は、以下のconfirm.txtの中の@@1@@などの部分が新規に作成されたレコードの値に置き換わります。body-fieldsキーの値に列挙されたフィールドとその順番より、@@1@@が名前、@@3@@がメールアドレス、@@5@@が相談内容、@@6@@が開催日時を示します。

@@1@@ 様(メールアドレス:@@3@@)

以下の通り、グループカウンセリングのお申し込みを受け付けました。

開催日時:@@6@@
ご相談内容:
@@5@@

_(などなど必要な情報を追加する)_

作成したページは次のようなものです。

shot3036

申し込みを行うと、入力したメールアドレスに、確認のメールが送られます。

shot3041

一覧ページの作成

一覧ページを構成するために、以下のような定義ファイル(list.php)を作成しました。こちらは、applyテーブルの内容を参照したり、場合によっては変更や削除等があるので、すべてのデータベース操作に対して、user1で認証したユーザーだけが許可されるようにしました。また、開催日の逆順で一覧されることで、最近の開催日に対する参加者がリストの上部にまとまるようにしました。コンテキストには、削除ボタンの追加の指示はありますが、挿入ボタンはありません。挿入ボタンは、必要ならapply.htmlから自分で申し込みを入れてそれを修正すればいいと考えました。

<?php
require_once('../INTER-Mediator/INTER-Mediator.php');

IM_Entry(
  array(
    array(
      'name' => 'apply',
      'table' => 'apply',
      'view' => 'apply',
      'key' => 'apply_id',
      'paging' => true,
      'records' => '20',
      'repeat-control' => 'confirm-delete',
      'sort' => array(
        array('field' => 'startdt', 'direction' => 'desc'),
      ),
      'authentication' => array(
        'all' => array('user' => 'user1'),
      ),
    ),
  ),
  array(),
  array(
    'db-class' => 'PDO',
    'dsn' => 'mysql:host=localhost;dbname=akaishi;charset=utf8mb4',
    'user' => 'web',
    'password' => 'password',
  ),
  false
);

一覧のページファイル(list.html)は以下の通りです。特に変わったところはなく、applyコンテキストの内容をページネーションとともに表示をしています。コンテキスト定義にあるように、20レコードずつ表示をします。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="INTER-Mediator/Samples/sample.css" />
  <title>Title</title>
  <script type="text/javascript" src="list.php"></script>
  <style>
    td{
      border: solid 1px black;
    }
  </style>
</head>
<body onload="INTERMediator.construct();">
  <div id="IM_NAVIGATOR"></div>
  <table>
    <thead>
      <tr><th>開始日時</th><th>名前</th><th>読み</th>
        <th>住所</th><th>相談内容</th><th>同意</th><th></th></tr>
    </thead>
    <tbody>
      <tr>
        <td data-im="apply@startdt"></td>
        <td data-im="apply@name"></td>
        <td data-im="apply@yomi"></td>
        <td data-im="apply@address"></td>
        <td data-im="apply@content"></td>
        <td data-im="apply@agreement"></td>
        <td></td>
      </tr>
    </tbody>
  </table>
</body>
</html>

ページを表示すると、ログインパネルが表示されます。

shot3042

正しいユーザー名とパスワードを入力してログインすれば、リスト形式で、申込者の一覧を見ることができます。

shot3043

ワークショップを終えて

結果的にワークショップでは、入力専用(ポストオンリーモード)のページと、その入力した結果を表示するための2つのページを作成しました。実際のワークショップでは凡ミスに気づかずちょっと時間を無駄しにしてしまって申し訳なかったのですが、通常ではこれくらいであれば1時間程度の作業で行えるということが示せたかと思います。また、典型的なシステム開発の流れも見ていただけるサンプルになったかと思います。

なお、こうして作ったシステムも、作ってから新たなニーズが発生するかと思います。そうした追加の要求や変化する要求についても追跡させてもらって、可能な限り、アフター・ワークショップとしてお伝えできればと考えています。

[IM]JavaScriptのメソッド内関数だけをテストする

INTER-Mediatorだけの話ではなく、JavaScript一般的な話です。メソッドの中に、そのメソッドの中だけで使う関数を書くということが可能なので、INTER-Mediatorではそういう実装をあちらこちらでやっています。処理の多いメソッドでは関数に分離することで見通しが良くなりますが、加えて、メソッド内の関数レベルでのテストをするということも考えました。

以下のプログラムで、変数anObjectはあるオブジェクトを参照しています。オブジェクトはmethod1というメソッドがあり、そのメソッドの中で、内部の関数であるfunc1を呼び出しています。このプログラムにより、コンソールには、”method1″ “func1″と2行の出力が行われます。

var anObject = {
  prop1: null,
  method1: function() {
    console.log("method1");
    func1();
    // method1のその他の処理
    function func1() {
      console.log("func1");
    }
  }
}

anObject.method1();

ここで、func1だけをテスト対象にしたく、かつmethod1の実処理を行いたくないと考えたとします。その場合、次のようにプログラムを変更しました。このプログラムは、こちらのページの記載を参考にしました。

var bObject = {
  prop1: null,
  method1: function(isTesting) {
    console.log("method1: isTesting="+isTesting);
    this.func1 = function() {
      console.log("func1");
    };
    if(isTesting) {
      return;
    }
    this.func1();
    // method1のその他の処理
  }
}

bObject.method1();
(new bObject.method1(true)).func1();

ポイントは、内部関数func1をプロパティにしていることです。そして、テストか通常利用かを示す引数isTestingを追加します。この引数は省略すると、undefinedになりますので、テスト時はtrueを追加するとして、省略時は何も指定しないことにします。テストの時には、プロパティへ内部関数の設定だけを行い、メソッドはそのまま終了します。

最後からの2行目は、普通にmethod2を利用する場合を想定しており、コンソールには、”method1: isTesting=undefined” “func1″と出力されます。この記述は、最初のプログラムの最後の行にある「anObject.method1();」と全く同じで、method1はつまりは通常利用では何も変更せずにそのまま利用できます。

一方、一番最後の行の実行により、コンソールには”method1: isTesting=true” “func1″と表示されます。newがあることでmethod1をコンストラクタとして実行するので、メソッド自体を返し、その結果、func1関数がメソッドのように実行できます。コンソールに”func1″と表示するのは、method1内部のthis.func1()を実行している部分ではありません。isTesting編集がtrueなので、その前にmethod1は終了します。つまり、「(new bObject.method1(true)).func1();」という記述で、func1だけを実行できるので、この関数だけの単体テストができるということです。

ちなみに、この方法は、method1に引数があっても利用できます。

[IM] OAuth2あるいはOpenID対応

Google AppsのOAuth2対応について、Using OAuth 2.0 to Access Google APIsに説明されています。この認証方式をサポートしました。したがって、OAuth2対応およびOpenID対応と言えるかと思います。Google以外のプロバイダについては今後、確認します。まずは、認証できるようになっているので、その使用方法を説明します。

OAuth認証するクライアント

まず最初に、ページをOAuth対応にした場合にどんな感じにページ表示されるのかを示します。認証を必須にすると、INTER-Mediator内蔵のログインパネルが表示されそこに「OAuth認証」というボタンが登場します。ここで、ボタンをクリックします。

shot2712

初めて利用するときには、次のように、認証アカウントから何を取り出すのかを表示するメッセージが出てきます。ここで「許可」をクリックします。なお、複数のアカウントでログインしている場合には、さらにどのアカウントでログインするのかを問い合わせるページもこの前に表示されます。

shot2713

そして、認証されます。おなじみのサンプルのページです。ログインユーザーは、ここでは意図的にプロバイダーによって提供される一意な名前とプロバイダー名を@でつないであります。

shot2714

認証のデータベースを見てみると、そちらに新たにユーザーが追加されています。つまり、外部にあるユーザーの認証ではありますが、いったん、利用しているデータベースのauthuserテーブルに、ユーザーを作り、もっぱらauthuserテーブルのユーザー情報を利用して、認証を行います。一定時間で無効化しますが(今日現在未実装)、それまでは、OAuth認証直後にランダムに生成したパスワードをクライアントに通知して記憶させ、INTER-Mediator自身の認証処理を行うことで、都度都度OAuthサーバーとのやりとりはないようにします。

shot2715

Googleでのアカウント発行

GoogleのOAuth2を利用するためには、アカウント発行が必要です。このアカウントは、アプリケーション、つまり、INTER-Mediatorで作るページに対するアカウントです。ページの利用者はこれらの情報は不要ですし、見えるようにする必要はありません。

まず、Googleデベロッパーのコンソールページに移動します。ここで、「プロジェクトを作成」をクリックします。このページは、もしかすると、2つ先のような画面になっているかもしれません。その場合はページの中にある新規プロジェクトを作成するようなボックスがあるので、それをクリックします。

shot2701

プロジェクトの作成で指定するのはプロジェクト名のみです。もちろん、開発者が把握できる名前を指定して、「作成」ボタンをクリックします。

shot2702

このようなページになります。追加したプロジェクトが1つのボックスになっています。また、左側に見えるように、このページが「ホーム」です。初めての利用であれば、このページに行きますが、以前い何か利用していた場合ではこの限りではありませんので、臨機応変に対処しましょう。ここまでで、ともかく1つのプロジェクトを作成してください。

shot2703

プロジェクトのボックスの右下にある「V」マーク部分をクリックすれば、内容が開きます。ここで、「プロジェクトの詳細」をクリックします。

shot2704

左側のナビゲーションで、APIと認証にある、「認証情報」をクリックします。認証情報というパネルが出るので、「認証情報を追加」をクリックします。

shot2705

ドロップダウンから、「OAuth 2.0クライアントID」を選択します。

shot2706

このような画面になります。「同意画面を設定」をクリックします。ここの同意画面は、最初に認証するときに見える画面です。

shot2707

メールアドレスはGoogleアカウントのメールアドレスが設定されています。サービス名を適当に記述しますが、この名前がタイトルになります。その他は「省略可」であるので、認証の動作には影響しません。入力をして「保存」ボタンをクリックします。

shot2708

アプリケーションの種類は「ウェブアプリケーション」を選択します。そして、「名前」は一覧の中で識別する名前を適当に指定します。そして、承認済みのJavaScript生成元には入力せず、承認済みのリダイレクトURLに、ここではINTER-Mediatorに含まれている/Auth_Support/OAuthCatcher.phpのURLが示されています。実際に利用するときには、このファイルのコピーを作るなどしてもかまいませんが、このまま利用してもかまいません。ログインパネルの「OAuth認証」ボタンをクリックすると、Googleが指定する「トークンリクエスト」のURLに移動し、ユーザーの特定や取得可能な情報のアクセス許可をページ上で求めます。その後に、「承認済みのリダイレクトURL」にリダイレクトするときに、アクセストークンが引数として渡され、INTER-Mediatorはそれを元に、OAuthユーザーをデータベースに作成します。

shot2709

「作成」ボタンを押すと、クライアントIDとクライアントシークレットが表示されます。これらはparams.phpファイルにコピペをします。コピペの方法は後で説明します。(ご安心ください。このIDとシークレットはすでに無効化してあります)

shot2710

OKをクリックすると、発行したアイアウントが一覧されるページになります。ここから、削除や追加、あるいは変更などのアカウントの管理もでき明日し、同意画面の変更などもできます。

shot2711

INTER-Mediatorで作成するファイル

OAuth認証を使いたいページでは、まず、定義ファイルに基本的な設定を行います。例えば、以下のファイルは、Samples/Sample_Auth/MySQL_definitions.phpの記述です。IM_Entryの第2引数に、authenticationキーで連想配列を指定します。とりあえず、クレデンシャルの記録方法(ここではセッションストレージ)とレルム(ここでは「Sample_Auth/MySQL_definitions」)を指定します。レルムは単に同一認証を受け付ける領域名とと考えてください。

IM_Entry(
    array( /* コンテキスト定義 */
    array(
        'authentication' => array(
            'storing' => 'session-storage',
            'realm' => 'Sample_Auth/MySQL_definitions',
        ),
    ),
    array('db-class' => 'PDO'),
    2
);

Googleに関連づける情報は、params.phpファイル側に記述できます。これらは、定義ファイルでは記述できないのが現在の仕様です。それぞれ、以下のように、決められた変数名に値を指定します。最初はプロバイダーで現在は「Google」のみサポートしています。続いて、クライアントIDトクライアントシークレットを、Googleのデベロッパーコンソールのページからコピー&ペーストします。最後のリダイレクトのURLは、「承認済みのリダイレクトURL」と同じURLを指定します。

$oAuthProvider = "Google";
$oAuthClientID = '1084721348801-jv3hvi4shcmr4j7unuhioq8k2mm47n6s.apps.googleusercontent.com';
$oAuthClientSecret = 'hV5TZD8x108K1Zac4RfZopur';
$oAuthRedirect = 'http://localhost:7001/Auth_Support/OAuthCatcher.php';

まだ完成というまでには至っていませんが、ともかく、認証できるところまでを作りました。タイムアウトのタイミングや、カスタマイズポイントなどを作りこみたいと思いますが、ご意見があれば、Facebookのグループでよろしくお願いします。

El Capitanのイチオシ機能はSplit View

年中行事となったOS Xのアップグレードですが、それだけに「大した違いはない」という感想を持ってしまいがちです。しかしながら、2015年10月リリースのEl Capitan(OS X 10.11)のSplit Viewはとても注目できる機能です。一部のiPadでiOS 9により実現していた機能なので、iOSの方が少し先になりましたが、複数のアプリケーションを使い分けるMacでこそ真価が発揮できそうな注目の機能です。

要するにこんなことができます。画面全体に表示されている感じを示すために、画面ショットに外枠も追加してみました。

右は編集中の原稿をJeditで見ているところです。左側はそれをブラウザのChromeで見ているところです。画面にはこの2つのアプリケーションの画面があるだけで、他のアプリケーションはありません。右で入力や変更をして保存、左側をクリックして更新とある程度の操作は必要ですが、ウインドウの大きさは固定であり、スクリールをするだけで、位置を調整したりということしなくても、2つのアプリケーションを並行して快適に利用できます。

shot0001f

こちらは、左がで開発ツールのPHPStromを使い、右側のブラウザ(Chrome)で実行しながらデバッグするという状況です。どちらのアプリケーションもさらにペインに分割されて多数のペインになっていますが、こういう作業でウインドウの前後関係を気にしないで作業できるのは非常に快適です。

shot0002f

使用方法は次の通りです。まず、利用したいウインドウを2つあらかじめ用意しておきます。そして、3本指でトラックパッドを画面方向にスワイプするMission Controlを利用します。すべてのウインドウが画面いっぱいに重ならないように表示されます。そして、1つのウインドウを画面上の「デスクトップ1」などと見えているデスクトップが並ぶ箇所(背景画像がボケて見える箇所)にドラッグします。何もないところにドラッグします。以下の図ではグラフィックス表示していますが、最初は文字のみです。マウスポインタを画面の上の方に移動させれば、グラフィックスも表示されます。

shot2695

デスクトップと同じ大きさのグレーのボックスが表示されます。これが出ることを確認してからトラックパッドの押し込みを放すなどして、ドラッグを終了します。

shot2696

すると次の図のように、もう1つデスクトップが確保されますが、アプリケーション(この場合PHPStorm)が最大化された状態で追加されます。

shot2697

続いて別のウインドウを先ほど新たに作ったデスクトップの上の右半分(あるいは左半分)のあたりにドラッグして追加します。追加される場所が濃いグレーで応答しますので、ドラッグを終了する前に、画面の状況を確認しながら作業をしましょう。

shot2698

こうすれば、2画面に分割したデスクトップが1つ確保されます。Mission Controlの画面でクリックして切り替えてもいいですが、2本指で左右にスワイプして、デスクトップを切り替える方が、快適に切り替わるでしょう。アプリケーションの境界線をドラッグして、左右に移動することもできます。ディスプレイが複数台あれば、それぞれのディスプレイで、内容の異なるSplit Viewを構成することもできます。ちなみに3つのウインドウまではできません。同一アプリケーションの2つのウインドウであれば、Split Viewで表示できます。

Macだと複数のアプリケーションを行き来することはよく行います。マルチウインドウシステムはそうした作業に適合するとはいえ、たくさんのウインドウが折り重なるデスクトップは混乱の元です。切り替わるべきウインドが前面に来るはずが、全然違うウインドウが出てきてがっかりすることも多々あります。作業の上で「それぞれのウインドウを見る」となると、今まではウインドウの位置や大きさをユーザーが調整していたのですが、そういう使い方ではマルチウインドウよりスプリット形式に表示する方が見やすく効率がいいのは、多くのアプリケーションで実証されています。そして、その仕組みをOSがサポートすることで、アプリケーションを超えて1つの画面に集約できる機能がやっと使えるようになったということです。Split ViewはiOSの系譜があるとはいえ、Macにとっては新しいインタフェースを加えるものであり注目できる機能と言えるでしょう。

[IM]追加の検索条件のマッチング

INTER-Mediatorでのページ状態の記憶の仕組みについて、幾つかの変更を行っています。変更前に動作の基本として、ローカルコンテキストは、ローカルストレージ(使えない時はクッキー)に保存をするようにしています。ローカルコンテキストには、ページファイル内にあるdata-im属性が「_@fieldName」のデータの置き場所を確保します。加えて、INTERMediator.addConditionメソッドでクエリーに追加する検索条件も、INTERMediatorオブジェクトのプロパティに見えるものの、実際にはそれをローカルストレージに保存しています。その結果、ページを開いた時にはローカルストレージは復元されるので、追加する検索条件も一度与えると明示的に消さない限りは、そのページに戻ったときに復元され、つまりは検索条件が常に保持されるように見える仕組みが組み込まれています。

ここで、ローカルストレージのキーに、URLを含めていましたが、URLのうちのクエリー部分は排除すべきかと考えました。例えば、ページファイルを開くときに渡すパラメータを与えて「page.html?id=101」などといったURLがある場合、ローカルストレージのキーに「page.html?id=101」までを含めるのか、「page.html」だけを含めるのか、どっちがいいのか(正しいのか?)を考えましたが、後者ではないかと思ったのです。クエリー部分があると、クエリーの内容によって以前の検索条件が消えるというのは、動作として正しくないと考えました。

ところがこうすると、こんな問題が発生します。例えば、page.htmlはクエリー部の情報を検索条件として付与するとします。「page.html?id=101」により、id=101という検索条件が加わり、ローカルコンテキストに保持されて残ります。そして、「page.html?id=55」をアクセスしたとき、どちらもpage.htmlなので、id=101という検索条件が残ったまま、id=55が付与されます。つまり、id=101 AND id=55というように、追加の検索条件が設定されてしまいます。idが主キーなら、検索されるレコードは0になってしまいます。これは意図しない動作です。

この問題には正解はないと思いますが、ロカールストレージのキーにクエリー部を含むURLを入れてしまうと、クエリー部によって、保持されるかどうかの挙動が変わってしまうことになります。一方、クエリー部を含めないでキーにすると、検索条件が追加されてしまって、意図しない動作になります。

そこで考えたのが、追加する検索条件については、マッチングをするということです。ローカルトレージのキーには、クエリー部は含めません。しかし、すでにid=101を検索条件として記憶している時に、addConditionメソッドで条件を追加するときには、条件のオブジェクトのfieldおよびoperatorプロパティが同一のもがあれば、その値のみを置き換えるようにします。id=55を追加すると、id=101とid=55のfieldおよびoperatorは同一なので、id=55の方だけが残るようにします。なお、こうした動作ではなく、従来と同様に単純に追加したい場合は、addConditionの3つ目の引数にtrueを指定することで、マッチングをしないで追加をするようにしました。ここまでをすでに実装しました。

そして、INTERMediator.clearConditionは、ローカルコンテキストの追加検索条件をクリアしていますが、同時に、ローカルストレージもクリアするのが正しいのではないかと考えています。すぐに実装は可能です。

[IM] OAuth2 (Open ID)による認証の実装

現在の認証方式は、[IM]INTER-Mediatorの認証フレームワーク(2)に記載の通りですが、これのベースにOAuthを動作させたいと考えています。ということで、とりあえず、Googleをベースに考えてみました。ブログなどでよく見られる「Google対応」は、2.1、4.1、4.2の部分です。Googleで認証をした結果を取り出すという部分です。しかしながら、そこから後が長いですね。その認証で得られた情報を元に、アプリケーション自体の認証もしなければなりません。OpenIDのtoken_idをそのままクッキーに入れるといった実装も見られますが、実際の認証はすでに動作しているユーザー名とクレデンシャルを使った方法を利用します。Open IDのtoken_idがあれば、その署名の検証を行うことで、内容を信頼するようにしようかと考えています。一番、問題なのは8.4で、クレデンシャルの期限切れがあったときに、クレデンシャルを問い合わせるか、あるいは再生成して再設定するかを行うことになると思います。その時、token_idの中身を見て、ユーザー名を特定することで、確実に動作してくれないかと考えています。ご意見よろしくお願いします。

シーケンス図0