目次


Web サービスと Ajax を使ってデータ入力を自動化する

Web 2.0 技術を使って時間を節約し、データの正確さを保証する

Comments

基本となる考え

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 アプリケーションを作成するための方法を説明するチュートリアルではないため、その方法については説明しません。(この記事の最後にある「参考文献」セクションには、適切なチュートリアルや参考情報へのリンクを挙げてあります。)

この記事では、住所 (例えば 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/addressadminedit.rhtml が使用するパーシャル
addressadmin_controller.rb../app/controllersHTML の入力フォームによって呼び出されるコントローラー
address.rb../app/modelsActiveRecord オブジェクト
001_create_addresses.rb../db/migrateAddresses データベース・テーブルを作成するためのスクリプト

ソリューションの概要

下記の箇条書きには、このソリューションを完成するために必要なステップを示しています。(心配する必要はありません。この記事のこれから先では、これらのステップを 1 つずつ進めていきます。) パーシャル (partial) は Ruby on Rails の用語であることに注意してください。パーシャルは、Web ブラウザーに表示される内容に関係する、再利用可能なコード部分です。最近の大部分のフレームワークには何らかの種類のテンプレートとパーシャル機能が含まれており、テンプレートのさまざまな部分を動的に組み合わせて Web ページを作成することができます。パーシャルはアプリケーション開発者にとって非常に便利であり、パーシャルを利用すると開発の負担を大幅に削減することができます。RoR の命名規則では、パーシャルの前にはアンダーバーを付けます (例えば _addressForm.rhtml など)。

  1. city と state の前に ZIP code を表示するように、_form.rhtml パーシャルを変更します。
  2. city 用の入力フィールドと state 用の入力フィールドを表示するように、パーシャル (_cityState.rhtml) を追加します。
  3. ZIP code フィールドへの変更を「リッスン」してサーバーに対して Ajax 呼び出しを行うように、_form.rhtml パーシャルを変更します。
  4. ZIP code (5 桁の数字) を検証するようにコントローラーを変更します。ZIP code が有効でない場合には、空の Ajax レスポンスをクライアントに返します。
  5. USPS の Web サービスに送信するための有効な XML リクエストを作成するように、コントローラーを変更します。
  6. USPS の Web サービスからの XML レスポンスを受信して構文解析するように、コントローラーを変更します。
  7. Web サービスの値を使って _cityState パーシャルにデータを追加するように、Ajax レスポンスを変更します。
  8. このソリューションを改善するための何らかの方法を考え、皆さんの助言を著者に E メールします。
図 1. ソリューションの概要
ソリューションの概要
ソリューションの概要

ステップ 1: ビューを変更する

この住所フォームのユーザビリティーには少し疑問があるかもしれません。アメリカ合衆国では通常、住所入力用のフィールドは次の順序で並んでいます。

  1. Street (街路番地)
  2. City (市町村)
  3. State (州)
  4. ZIP (郵便番号)

しかしこの記事でのフォームは、次の順序でフィールドを並べています。

  1. Street
  2. ZIP
  3. City
  4. 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_fieldPrototypeHelper クラスのヘルパー・メソッドで、簡単に言えば、ライン 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

ライン 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 はさらに大幅に改善されています。


ダウンロード可能なリソース


関連トピック

  • ZIP コード郵便番号に関するウィキペディアの項目を調べてください。
  • United States Postal Services Web Tools が提供するサービスの詳細を調べてください。USPS の API は USPS の配達サービスとの組み合わせでのみ使用することができます。
  • Ruby on Rails の公式サイトとその API を調べてください。
  • 私が Rails を使い始めるきっかけとなった 2 冊の本が『RailsによるアジャイルWebアプリケーション開発 第2版』と『プログラミングRuby』です。私が購入した 3 冊目の本であり、また Rails のライブラリーに追加する価値のある本が『Rails レシピ』です。
  • IBM developerWorks の SOA and web services ゾーンでは、Web サービス・アプリケーションの開発方法に関する内容豊富な記事と、初心者から中級者、そして上級者を対象としたチュートリアルを数多く提供しています。
  • IBM SOA Sandbox を試してみてください。そして IBM SOA のエントリー・ポイントで現実的で実践的な経験を積み、SOA のスキルを磨いてください。
  • Safari bookstore には、この記事や他の技術的な話題に関する本が豊富に取り揃えられています。

コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=SOA and web services, Open source
ArticleID=295351
ArticleTitle=Web サービスと Ajax を使ってデータ入力を自動化する
publish-date=02142008