レベル: 上級 Norbert (Norb) R. Ryan, III (nryan@us.ibm.com), Software Engineer, IBM
2008年 2月 14日 この記事では、余分なものを整理し、Web サービスと Ajax (Asynchronous JavaScript + XML) によってアプリケーション (この場合は RoR (Ruby on Rails) アプリケーション) を改善する方法を学びましょう。具体的には、Web サービスを呼び出すとともに Ajax を利用することで、一般的な Web アプリケーションの動作 (ここでは住所の入力) を洗練したものにする方法について説明します。また、こうした基本的な Web 2.0 のコンポーネントを組み合わせるための手法をいくつか学びましょう。
基本となる考え
USPS (United States Postal Service: アメリカ合衆国郵政公社) はいくつかの Web サービスを提供しています (囲み記事「USPS Web tools」を参照してください)。この Web サービスの 1 つに、ZIP code (郵便番号) を入力すると、それに対応する city (市町村) と state (州) を返してくれるサービスがあります。この記事のサンプル・アプリケーションでは、この CityStateLookupRequest を使うことで、ユーザーによる文字入力を少し減らします。この機能を使うと入力ミスを減らすことができるため、より正確な住所データをデータベースに入れることができます。
前提条件と想定事項
Ruby on Rails (RoR) の作者であり、その思想を広める中心となっている David Heinemeier Hansson は頭脳明晰な人です。彼は Web アプリケーションの開発がはるかに容易になるような素晴らしい概念の数々を RoR の中で実現しており、私の友人の言葉を借りれば、「RoR のおかげで、プログラミングが再び楽しいものになります」。私の意見では、他のフレームワークやプログラミングのパラダイムもやがてこうした概念を取り入れるであろうことは、ほとんど疑いありません。しかしこの記事は RoR アプリケーションを作成するための方法を説明するチュートリアルではないため、その方法については説明しません。(この記事の最後にある「参考文献」セクションには、適切なチュートリアルや参考情報へのリンクを挙げてあります。)
 | |
Ajax Resource Center にアクセスしてください。ここには記事、チュートリアル、ディスカッション・フォーラム、ブログ、ウィキ、イベント、そしてニュースなど、Ajax プログラミング・モデルに関する情報が豊富に用意されており、ワンストップ・ショップになっています。新しい情報もここに記載されます。 |
|
この記事では、住所 (例えば 590 Madison Ave, New York, NY 10022 など) を入力するための HTML フォームを持つ RoR アプリケーションを既に作成してあることを前提に話を進めます。この Rails アプリケーションには、address という名前のモデルと、それに対応するデータベース・テーブルがあります。さらに、次のことを前提とします。
- Web アプリケーション開発の基本的な設計原則を理解していること。
- RoR アプリケーションを作成したことがあること。
- RoR アプリケーションの基本的な部分 (ActiveSupport、ActiveRecord、ActionView、 ActionController、マイグレーションなど) を理解していること。
- RoR アプリケーションで動作するように構成されたデータベース (IBM® DB2® や MySQL など) があること。
- ユーザーのニーズを先取りすることでユーザーのことを理解し、またユーザーの時間を節約することの重要性を知っていること。
表 1. RoR アプリケーションに含まれている前提のオブジェクト
| Ruby on Rails のファイル | ディレクトリー | 説明 |
|---|
| edit.rhtml | ../app/views/addressadmin | 住所を編集するためのビュー |
|---|
| _form.rhtml | ../app/views/addressadmin | edit.rhtml が使用するパーシャル |
|---|
| addressadmin_controller.rb | ../app/controllers | HTML の入力フォームによって呼び出されるコントローラー |
|---|
| address.rb | ../app/models | ActiveRecord オブジェクト |
|---|
| 001_create_addresses.rb | ../db/migrate | Addresses データベース・テーブルを作成するためのスクリプト |
|---|
ソリューションの概要
下記の箇条書きには、このソリューションを完成するために必要なステップを示しています。(心配する必要はありません。この記事のこれから先では、これらのステップを 1 つずつ進めていきます。) パーシャル (partial) は Ruby on Rails の用語であることに注意してください。パーシャルは、Web ブラウザーに表示される内容に関係する、再利用可能なコード部分です。最近の大部分のフレームワークには何らかの種類のテンプレートとパーシャル機能が含まれており、テンプレートのさまざまな部分を動的に組み合わせて Web ページを作成することができます。パーシャルはアプリケーション開発者にとって非常に便利であり、パーシャルを利用すると開発の負担を大幅に削減することができます。RoR の命名規則では、パーシャルの前にはアンダーバーを付けます (例えば _addressForm.rhtml など)。
- city と state の前に ZIP code を表示するように、_form.rhtml パーシャルを変更します。
- city 用の入力フィールドと state 用の入力フィールドを表示するように、パーシャル (_cityState.rhtml) を追加します。
- ZIP code フィールドへの変更を「リッスン」してサーバーに対して Ajax 呼び出しを行うように、
_form.rhtml パーシャルを変更します。
- ZIP code (5 桁の数字) を検証するようにコントローラーを変更します。ZIP code が有効でない場合には、空の Ajax レスポンスをクライアントに返します。
- USPS の Web サービスに送信するための有効な XML リクエストを作成するように、コントローラーを変更します。
- USPS の Web サービスからの XML レスポンスを受信して構文解析するように、コントローラーを変更します。
- Web サービスの値を使って _cityState パーシャルにデータを追加するように、Ajax レスポンスを変更します。
- このソリューションを改善するための何らかの方法を考え、皆さんの助言を著者に E メールします。
図 1. ソリューションの概要
 |
世界の郵便番号に関する事実とトリビア
- イギリスを含む 11 ヵ国が英数字による郵便番号システムを使っています (このデータ型に注意してください)。
- アイルランドと香港では郵便番号を使っていません。
- インドでは郵便番号を Postal Index Numbers または PIN と呼びます。
- パリ、リヨン、マルセーユでは、郵便番号 (フランス語で code postal) の最後の 2 桁は行政区を表します。
- 驚いたことに、小さな島国であるシンガポールでは 6 桁の郵便番号を使いますが、通常は郵便番号で建物やマンションを識別することができます。
- United States Naval Academy (アメリカ合衆国海軍兵学校) の生徒用の寄宿舎 Bancroft Hall は建物自体に ZIP コード (21412) が付けられている珍しい建物の 1 つです。
- 私は 78418 (テキサス州 Corpus Christi) に生まれました (テキサス人になることはできません。テキサス人として生まれなければなりません。そして現在は 10028 (ニューヨーク市) に住んでいます。
- ワールド・トレード・センター専用の ZIP コードは10048 でした。
|
|
ステップ 1: ビューを変更する
この住所フォームのユーザビリティーには少し疑問があるかもしれません。アメリカ合衆国では通常、住所入力用のフィールドは次の順序で並んでいます。
- Street (街路番地)
- City (市町村)
- State (州)
- ZIP (郵便番号)
しかしこの記事でのフォームは、次の順序でフィールドを並べています。
- Street
-
ZIP
- City
- State
このユーザビリティーの問題は、ユーザーをトレーニングしたり、使用方法を説明したテキストを用意したり、あるいは常識を使ったりすることで克服できるものとしましょう。また、city と state のフィールドを暗くしたり、あるいはどちらにも入力できなくしたりする方法も意味があるかもしれません。ユーザビリティーに関するエキスパートと協力し、妥当なソリューションを作成してください。
リスト 1 は _form.rhtml という名前の入力フォームと、入力テキスト・フィールドを示しています。入力テキスト・フィールド zip5 が city と state の上に移動していることに注目してください。最後の行、debug(params) はオプションです。開発フェーズとテスト・フェーズでは、私は通常このデバッグ・データを RoR のビューに含めています。
リスト 1. 入力フィールドの順序を変更したパーシャル (_form.rhtml)
<%= error_messages_for 'address' %>
<p><label for="address_street">Street</label><br/>
<%= text_field 'address', 'street' %></p>
<p><label for="address_zip5">Zip5</label><br/>
<%= text_field 'address', 'zip5', :size => "9", :maxlength => "5" %></p>
<p><label for="address_city">City</label><br/>
<%= text_field 'address', 'city' %></p>
<p><label for="address_state">State</label><br/>
<%= text_field 'address', 'state' %></p>
<%= debug(params) %>
|
ステップ 2: Rails のパーシャルを追加する
このソリューションの 2 番目のステップでは、city と state の両入力フィールドを新しいパーシャルに分離することで、入力フォーム _form.rhtml を分割します。RoR の命名規則ではパーシャルの前にはアンダーバーを付けます。従って新しいパーシャルの名前は _cityState.rhtmlです。この新しいファイルは _form.rhtml と同じディレクトリーに置かれます。リスト2 はこの新しいファイル _cityState.rhtml のコードを示しています。
リスト 2. RoR の新しいパーシャル (_cityState.rhtml)
<p><label for="address_city">City</label><br/>
<%= text_field 'address', 'city' %></p>
<p><label for="address_state">State</label><br/>
<%= text_field 'address', 'state' %></p>
|
city と state を他の住所フィールドと同じファイルの中に置けたら便利だったと思います。私はそうしようとしたのですが、このようにすることしかできませんでした。なぜでしょう。Ajax 呼び出しによるレスポンスを使って複数のフォーム・フィールドを更新することが難しいのです。RoR に関する私の経験が不足しているか、あるいは生成される JavaScript コードがそれを処理できないか、そのいずれかです。おそらく前者でしょう。リスト 3 は、city と state の入力フィールドを削除した後の _form.rhtml パーシャルのコードを示しています。新しいコードに id = "ajaxLookup" が与えられていることに注意してください。これについては次のステップで説明します。
リスト 3. 新しいパーシャル を含める (_form.rhtml)
<%= error_messages_for 'address' %>
<p><label for="address_street">Street</label><br/>
<%= text_field 'address', 'street' %></p>
<p><label for="address_zip5">Zip5</label><br/>
<%= text_field 'address', 'zip5', :size => "9", :maxlength => "5" %></p>
<div id = "ajaxLookup">
<%= render :partial => "cityStateFields" %>
</div>
|
RoR では命名規則は重要な概念であり、名前の前にアンダーバーが付くものはパーシャルを表し、そのパーシャルを参照する際にはアンダーバーが付かないという前提があります。従ってリスト 3 の <%= render :partial => "cityStateFields" %> という行にはアンダーバーが付かないことになり、この行は正しいのです。RoR はこの行を見ることで、同じディレクトリーの中で _cityStateFields.rhtml という名前のファイルを探します。
ステップ 3: ZIP code への変更をリッスンする
Rails には Ajax のサポートが組み込まれています。Rails が本当に真価を発揮するのが Ajax の領域です。ZIP code フィールドへの変更をリッスンするために、リスト 4 に示すコード行を追加します。
リスト 4. ZIP code の Ajax リスナーを追加する (_form.rhtml)
01 <%= javascript_include_tag :defaults %>
02
03 <p><label for="address_street">Street</label><br/>
04 <%= text_field 'address', 'street' %></p>
05
06 <p><label for="address_zip5">Zip5</label><br/>
07 <%= text_field 'address', 'zip5', :size => "9", :maxlength => "5" %></p>
08
09 <div id = "ajaxLookup">
10 <%= render :partial => "cityStateFields" %>
11 </div>
12
13 <%= observe_field :address_zip5,
14 :frequency => 2.00,
15 :update => "ajaxLookup",
16 :url => {:action => :cityStateSearch, :id => @address},
17 :with => "‘zip5=’ + encodeURIComponent(value)"
18 %>
19
20 <%= debug(params) %>
|
注意: リストの左端の 2 桁のライン番号は説明用です。コードの中には現れません。
これで終わりです。約 10 ラインの Ruby コードで、このビューに Ajax 機能が追加されました。この背後では、RoR と Prototype ライブラリーがすべての JavaScript を処理しています。ではこれらの 20 ラインを順次調べてみましょう。
ライン 01 では Prototype と Scriptaculous という JavaScript ライブラリーをインクルードするようにRoR に命令しています。ライン 03 からライン 12 までは前と同じです。ライン 13 は Prototype ライブラリーの observe_field メソッドを使っています。observe_field は PrototypeHelper クラスのヘルパー・メソッドで、簡単に言えば、ライン 13 からライン 17 では、2 秒ごとに zip5 入力フィールドをチェックするように指示しており、zip5 入力フィールドにユーザー入力があると、現在のコントローラー (addressadmin_controller.rb) の cityStateSearch アクションが呼び出されます。このロジックはユーザーのブラウザー内の JavaScript によって実行されます。zip5 入力フィールドが変更されると、ユーザーのブラウザーからサーバーへの Ajax 呼び出しが行われます。ライン 09 とライン 15 が相関していることに注目してください。ライン 15 は cityStateSearch アクションからのレスポンスをどう扱うかを指定しています。このアクションに対してレスポンスがあると、そのレスポンスによって ajaxLookup という名前の <div> タグが更新されます。ライン 09 には ajaxLookup と等価な ID を持つ div タグがあります。そのため、cityStateSearch アクションからのレスポンスはライン 10 に渡されます。ライン 17 は、このアクションにどんな名前と値のペアを送信するのかを説明しています。つまりこの例では、ストリング zip5=90210 が addressadmin_controller.rb の cityStateSearch という名前のアクションに渡されることになります。
次に、コントローラーの機能に関する作業を開始します。この前のステップでは、ユーザーのブラウザーが ZIP code (つまり zip5) の入力フィールドの変化を検出したら、ブラウザーが cityStateSearch というアクションを非同期で呼び出すように指定しました。サーバー・サイドでコーディングする必要のある主な機能部分は次のとおりです。
- ZIP code を検証する部分。
- Web サービスを呼び出す XML を作成する部分。
- Web サービスを呼び出す部分。
- Web サービスからのレスポンスを構文解析する部分。
- ユーザーの Web ブラウザーにレスポンスを返送する部分。
ステップ 4: ZIP コードを検証する
無効な ZIP code を使って USPS の Web サービスを呼び出しても意味はありません。少し工夫するだけで、無効な ZIP code の大半をなくすことができます。アメリカ合衆国では、ZIP code は 5 桁の数字です。リスト5 に zip5 というパラメーターが 5 桁の数字から構成されているかどうかを調べるコードを示します。
リスト 5. ZIP code を検証する (addressadmin_controller.rb)
01 def cityStateSearch
02
03 if params[:zip5].nil?
04 logger.debug("zip5 is null")
05 elsif !(params[:zip5] =~ /\d{5}/)
06 logger.debug("We have a bad ZIP code -- not 5 digits.")
07 logger.debug("zip5 = #{params[:zip5]}")
08 else
09 logger.debug("We have a good 5-digit ZIP code.")
10 logger.debug("zip5 = #{params[:zip5]}")
11
12 if params[:address].nil?
13 @address = Address.new
14 else
15 @address = Address.find(params[:id])
16 if @address.update_attributes(params[:address])
17 flash[:notice] = 'Address was successfully updated.'
18 end
19 end
20 end
21
22 end #cityStateSearch
|
 |
USPS Web Tools
USPS は以下の 5 種類の異なる Web サービスを提供しています。
- Address Information (住所情報)
- Delivery Information (配達情報)
- Rate Calculators (料金計算)
- Shipping Labels (送付ラベル)
- Carrier Pickup (配達員による収集サービス)
この記事では、このうちの最初の Address Information の API に焦点を絞ります。この API の中には、以下に示す何種類かの機能があります。
-
Address Standardization (住所の標準化): 住所の誤りをなくすことで正確でタイムリーな配達を保証します。この Web ツールは、省略や情報の不足を始めとする street address (街路番地) の誤りを訂正します。またこの Web ツールは ZIP+4 code (4 桁の補助コード付き ZIP code) も提供します。
-
ZIP Code Lookup (ZIP code の検索): 指定された任意のアメリカ合衆国内の street、city、state に一致する ZIP code または ZIP+4 code を調べます。
-
City/State Lookup (市町村と州の検索): ZIP code しかわからない場合に正確な city と state の情報を提供します。(注意: 上記の情報は USPS Web Tools のドキュメンテーションから引用したものです)
注意: USPS は、これらの Web ツールをバッチ処理やデータベース・クレンジングの目的で使用することを許可していません。
|
|
ライン 01 は新しい cityStateSearch アクションに対する定義を開始しています。このコントローラーでの他のアクションとは異なり、cityStateSearch アクションは JavaScript、つまりクライアント・サイドの Ajax コードによって非同期で呼び出されます。ライン 03 はパラメーターの値がヌルかどうかをチェックしています (Ruby ではヌルを nil と呼びます)。ライン 05 はパラメーターのストリングの値を /\d{5}/ と比較する正規表現です (私達は皆、これが 5 桁の数字を表す正規表現であることを知っており、またよく利用しています)。この正規表現の前にある感嘆符は elsif 文を否定しています。(そうです。elsif は RoR では正しい構文ですが、他では else if として知られています。)
ライン 12 からライン 19 では @address オブジェクトの作成または更新を行います。ライン 05 からライン 07 はそれとは少し異なり、ロジックがこれらの行に入りこむと cityStateSearch アクションから抜け出てブラウザーに戻ります。しかし、このことはエンド・ユーザーにはわかりません。cityStateSearch アクションは、zip5 が nil であるか、あるいは 5 桁でない場合には、残りのロジックをスキップして即座にブラウザーに戻るのです。ユーザーのカーソルがビュー上の zip5 の入力テキスト・フィールドにある限り、このアクションは 2 秒ごとに繰り返し呼び出されます。この機能は先ほど observe_field メソッドの中の frequency パラメーターで構成したものです。
ライン 21 は次のコード・セクションを追加する場所です。これは開発用のコードなので、デバッグのために多数の文を挿入します。このコードを何度か改良して洗練させたら、私は logger.debug 文を削除するつもりです。また、私は RoR の初心者なので、デバッグ用の文がたくさんあると自分のプログラミング・スタイルに安心することができます (他の人達は私のプログラミング・スタイルを「叩いて形を整える」と的確に表現しています)。
ステップ 5: USPS の Web サービスに送信するための有効な XML リクエストを作成する
このステップでは RoR アプリケーションのサーバー・サイドに入りますが、この時点で 5 桁の ZIP code があります。この ZIP code は適切であるという妥当な前提に立っているので、USPS の Web サービスを呼び出す作業にかかることができます。このサービスを呼び出すには有効なリクエストを作成する必要があります。少し先回りしてみると、リスト 6 は有効な XML リクエストの一例を示しています。
リスト 6. 有効な XML リクエスト
http://testing.shippingapis.com/ShippingAPITest.dll?API=CityStateLookup
&XML=<CityStateLookupRequest%20USERID="XXXXXXXXXXXX"><ZipCode ID=
"0"><Zip5>90210</Zip5></ZipCode></CityStateLookupRequest>
|
USPS Web Tools を使うためには、まず登録する必要があります。登録は簡単で無料です (詳細は「参考文献」セクションを参照)。私が USPS Web Tools に登録したところ、テスト・サーバーの名前とユーザー ID が送られてきました。(リスト 6 では、できるだけ最高機密政府機関員の真似をして、私のユーザー ID を XXXXXXXXXXXX で消しています。) このリクエストをもう少し構文解析してみましょう。この Web サービスのエンドポイントは API=CityStateLookup によって指定されています。なぜ私が HTML フォームの入力フィールドを zip5 と呼んでいるか、これでわかると思います。USPS のリクエストが入力フィールドに想定している名前が zip5 なのです。この CityStateLookup という Web サービスは、1 回のリクエストで ZIP code の値を 5 個まで受け付けます。簡単のために、このコードでは (XML タグ <ZipCode ID= "0"> を持つ) 1 個の ZIP code しか渡しません。すると、XML リクエストに必要な機能は非常に明らかになります。つまりユーザーが入力した 5 桁の値を取得し、それをこの <Zip5> という名前の XML タグに入れるのです。
これはどんな Web サービスなのか
この一見単純に見える質問が、必ずしも容易に答えられるものではないことがわかります。Web サービスは、サーティワンの 31 種類のアイスクリームやスターバックスのコーヒーのメニューのようになっています。コーヒー 1 杯を注文することは簡単そうに思えますが、そう思っているとスターバックスで「Grande Chai Latte with Soy (グランデ・サイズのチャイ・ラテをソイで)」のような注文に遭遇するのです。では、まず該当しないものを挙げましょう。この Web サービスは、XML-RPC スタイルの Web サービスではなく、文書スタイルの Web サービスでもなく、SOAP Web サービスでもなく、REST (Representational State Transfer) による (名詞ベースの) リクエスト Web サービスでもありません。USPS の設計者達は、これをごく単純な XML Web サービスとして実装することにしたのです。USPS の Web サーバーは、GET あるいは POST いずれかの HTTP リクエストを受け付けます。これらのリクエストはステートレスであり、クッキーや URL の書き換えはありません。リクエストとレスポンスは大文字と小文字を区別します。繰り返しますが、USPS Web Tools には簡単に登録でき、またドキュメンテーションは豊富にあります。(私が USPS とは何の関係もないことを申し添えておきます。)
XML を作成するためには、RoR に含まれている Builder::XmlMarkup ライブラリーを使います。コントローラーのクラス・ファイルである addressadmin_controller.rb の先頭に、リスト 7 に示すコードを追加する必要があります。
リスト 7. addressadmin_controller.rb に追加するコード
require 'open-uri'
require 'uri'
require 'rubygems'
require_gem 'builder'
require "rexml/document"
|
リスト 8. リクエストの XML 部分を作成する
01 def cityStateSearch
02
03 if params[:zip5].nil?
04 logger.debug("zip5 is null")
05 elsif !(params[:zip5] =~ /\d{5}/)
06 logger.debug("We have a bad ZIP code -- not 5 digits.")
07 logger.debug("zip5 = #{params[:zip5]}")
08 else
09 logger.debug("We have a good 5-digit ZIP code.")
10 logger.debug("zip5 = #{params[:zip5]}")
11 # Build the XML to call the web service
12 xm = Builder::XmlMarkup.new
13 xmlstuff = xm.CityStateLookupRequest("USERID"=>"XXXXXXXXXXXX") {
14 xm.ZipCode("ID"=>"0") {
15 xm.Zip5(params[:zip5]) }}
16
17 end
18 end #cityStateSearch
|
たった 4 行 (ライン 12 からライン 15 まで) で、適切にフォーマットされたリクエスト用の XML を作成することができます。ストリング変数 xmlstuff は以下の XML を含んでいます。
<CityStateLookupRequest%20USERID="XXXXXXXXXXXX"><ZipCode ID=
"0"><Zip5>90210</Zip5></ZipCode></CityStateLookupRequest>
このリクエストを適切にフォーマットするためには、さらにいくつか重要なステップがあります。リクエストの中の特殊文字を、リスト 9 に示すコードを使ってエスケープする必要があります。
リスト 9. リクエストの特殊文字をエスケープする
uri_enc = URI.escape('http://testing.shippingapis.com/ShippingAPITest.dll
?API=CityStateLookup&XML=' + xmlstuff)
uri = URI.parse(uri_enc)
|
このコードによって、すべての特殊文字が HTTP リクエストとしての適切なエンコーディングに変換されます。サーバー名や API の名前などに対して変数またはプロパティー・ファイルを設定することでこのコードを改良するのは、皆さんにお任せすることにします。目標は、ともかく Web サービスに対する呼び出しを行えるようにすることであり、改良して洗練させるのは後ですればよいのです。これで、適切にフォーマットされた HTTP/XML リクエストが用意でき、USPS の Web サービスを呼び出す準備ができました。
ステップ 6: Web サービスを呼び出し、レスポンスを受信する
このステップでは、USPS の Web サービスを呼び出し、レスポンスを受信します。このコードは XML レスポンスを構文解析する必要があります。リスト 10 は USPS の CityStateLookup のレスポンスの例を示しています。
リスト 10. USPS の CityStateLookup のレスポンス
<?xml version="1.0"?>
<CityStateLookupResponse><ZipCode ID="0"><Zip5>90210</Zip5>
<City>BEVERLY HILLS</City><State>CA</State></ZipCode>
</CityStateLookupResponse>
|
この例も、USPS Web Tools のドキュメンテーションから直接引用したものです。このステップでの目標は、city と state の情報を構文解析し、その情報を @address オブジェクトの変数の中に入れることです。最終的にこれらの変数は Ajax レスポンスの一部として返されます。
次のことを忘れないでください: Builder ライブラリーを使うと XML を作成することができ、REXML モジュールを使うと XML データを構文解析することができます。
リスト 11. Web サービスを呼び出し、レスポンスを構文解析する (addressadmin_controller.rb)
# The call to the Web service -- response is in var 'doc'
doc = REXML::Document.new open(uri)
logger.debug("doc = " + doc.to_s)
doc.elements.each("CityStateLookupResponse/ZipCode") { |element|
logger.debug(element)
logger.debug("element[0] = " + element[0].to_s)
logger.debug("element[0].text = " + element[0].text)
logger.debug("element[1] = " + element[1].to_s)
logger.debug("element[1].text = " + element[1].text)
logger.debug("element[2] = " + element[2].to_s)
logger.debug("element[2].text = " + element[2].text)
# Set the model field values to the response from the Web service
@address.city = element[1].text
@address.state = element[2].text
}
|
リスト 11 のコードは XML レスポンスに対して繰り返し処理を行い、city (element[1]) と state (element[2]) を見つけます。element[0] は zip5 の値です。先ほど触れたように、この Web サービスは 1 回のリクエストで最大 5 個の ZIP code の検索処理を行います。今後このコードを改善する際には、これらの値をループ処理することを検討する必要があります。しかしこの簡単な例では、1 回のリクエストに対して常に 1 個の ZIP code の値しか渡しません。またこのコードは、city と state の戻り値がない場合の処理も本来はもっと適切にできるはずです。ここでのポイントは、最低限の機能を動作させ、特定のプロジェクトの制約や要求に応じて改善できるようにすることです。Builder ライブラリーや REXML モジュールの使い方に関して疑問がある場合には、豊富な例を記載した RoR の API のドキュメンテーションを見てください。
他の言語で XML を作成したり構文解析したりする方法に慣れている人は、そうした作業が RoR ではいかに容易であるか、すぐに理解できるでしょう。RoR が静かに主張しているメッセージの 1 つは、『XML の処理は必ずしも大きな悩みの種ではない』ということです。RoR を使えば、XML の作成も、Ajax の処理も、さらには XML の構文解析も非常に簡単なのです。RoR は、本当に必要な作業を真のエンド・ユーザーのために極めて迅速に仕上げることが、私達の目的であるということを頻繁に思い出させてくれ、良い気分になります。
このアクションについて要約すると、このアクションは ZIP code を検証し、Web サービスを呼び出すための XML リクエストを作成し、XML レスポンスを構文解析し、そして city フィールドと state フィールドを @address オブジェクトに入れています。ユーザーの観点から見ると、この処理はすべて非同期に、約 1 秒で行われています。ユーザーのカーソルは相変わらず Web ブラウザーの html フォーム上の ZIP code フィールドにあります。ユーザーが気付いたときには、このアクションによって city と state に正しい値が入れられた @address オブジェクトが返されており、さらにその情報が _cityStateFields.rhtml パーシャルに送られて、ユーザーのブラウザーにその情報が表示されます。これは願わくはユーザーにとって嬉しい驚きであってほしいものです。皆さんはユーザーに対して、このアプリケーションはユーザー・フレンドリーであり、とても便利であるという印象を与えることができました。皆さんはユーザーのニーズを先取りして、ユーザーを楽にするために最善を尽くしたのです。
リスト 12 は、ファイル addressadmin_controller.rb 内の cityStateLookup アクションの完全なコードを示しています。
リスト 12. cityStateLookup アクションの完全なコード (addressadmin_controller.rb)
require 'open-uri'
require 'uri'
require 'rubygems'
require_gem 'builder'
require "rexml/document"
class AddressadminController < ApplicationController
<!-- other methods/actions -->
def cityStateSearch
if params[:zip5].nil?
logger.debug("zip5 is null")
elsif !(params[:zip5] =~ /\d{5}/)
logger.debug("We have a bad ZIP code -- not 5 digits.")
logger.debug("zip5 = #{params[:zip5]}")
else
logger.debug("We have a good 5-digit ZIP code.")
logger.debug("zip5 = #{params[:zip5]}")
if params[:address].nil?
@address = Address.new
else
@address = Address.find(params[:id])
if @address.update_attributes(params[:address])
flash[:notice] = 'Address was successfully updated.'
end
end
# Build the XML to call the Web service
xm = Builder::XmlMarkup.new
xmlstuff = xm.CityStateLookupRequest("USERID"=>"XXXXXXXXXXXX") {
xm.ZipCode("ID"=>"0") {
xm.Zip5(params[:zip5]) }}
webservice = 'http://testing.shippingapis.com/ShippingAPITest.dll?'
uri_enc = URI.escape(webservice + 'API=CityStateLookup&XML=' + xmlstuff)
uri = URI.parse(uri_enc)
# The call to the Web service -- response is in var 'doc'
doc = REXML::Document.new open(uri)
logger.debug("doc = " + doc.to_s)
doc.elements.each("CityStateLookupResponse/ZipCode") { |element|
#logger.debug(element.attributes["name"])
logger.debug(element)
logger.debug("element[0] = " + element[0].to_s)
logger.debug("element[0].text = " + element[0].text)
logger.debug("element[1] = " + element[1].to_s)
logger.debug("element[1].text = " + element[1].text)
logger.debug("element[2] = " + element[2].to_s)
logger.debug("element[2].text = " + element[2].text)
# Set the model field values to the response from the web service
@address.city = element[1].text
@address.state = element[2].text
}
end # valid ZIP code if-statement-checkers
render :partial => "cityStateFields"
end
|

 |
さまざまな考察
このソリューションと Web サービスに関するさまざまなコメントを挙げておきます。
- Web サービスはプログラミング言語にかかわらず動作します。この例では Ruby on Rails を使いましたが、他の言語やフレームワークでも動作します。
- このソリューションには、USPS の Web サービスが利用できない場合の対応策がありません。ローカル・キャッシュを使うことで、サービスが利用できない場合の影響を最小限にとどめられるかもしれません。
- Web サービスからの XML レスポンスを構文解析する部分は、おそらく最もスマートさに欠けたコードです。ソリューションのこの問題を解決するにはテンプレートが役立つかもしれません。
- ZIP code を入力するだけで city と state を調べられるにもかかわらず、なぜユーザーに city と state を入力させる必要があるのでしょう。
- さまざまなワーキング・グループが Web サービスの標準を市場に任せてしまっていますが、そうしたワーキング・グループはどれほど有効なのでしょう。
- UDDI (Universal Description Discovery Integration) 標準の実装について 1 秒でも考えたことがある人はいるのでしょうか。
- Web サービスの標準は複雑すぎるでしょうか。開発者達は膨大な数の頭字語を整理して理解できるでしょうか。
- 市場が 1 人だけ勝者を宣言することは稀です。多くの競合が次のラウンドに進みます。しかし市場は敗者を排除することに関しては非常に効率的なようです。
- RoR のパフォーマンスやセキュリティー、あるいは本番環境での価値に対して、否定論者達はいつまで疑問を投げ続けるのでしょう。
多くの大企業が市場戦略を駆使しているため、Web サービスの本質が見えにくくなっていますが、この記事では単純で理解しやすい Web サービスを使って一般的な問題に対応する方法を説明しました。USPS の人達は確実で非常に使いやすい Web サービスを実装しています。Jakarta Struts は Web アプリケーションのフレームワークへの対応、つまり完全な MVC (model view controller) スタックへの対応という面で、以前のバージョンと比べて大幅に改善されました。その Struts と比べても、RoR はさらに大幅に改善されています。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| Sample application for this article | ajaxsoademo.zip | 176KB | HTTP |
|---|
参考文献 学ぶために
製品や技術を入手するために
議論するために
著者について  | |  | 1983年に Commodore 64 を手にして以来、Norb はコンピューターとソフトウェア・エンジニアリングに魅了されています。彼は 1990年以来、IBM のソリューションを実装しています。 |
記事の評価
|