uri/queryform.rb

昨日、添付ライブラリの URI と一緒に使うために URI.query_form 関数を作ってみましたが、今日になって、やっぱりクエリー・フォームはオブジェクトにした方がふさわしいのではないかと思い当たりました。

https://tociyuki.sakura.ne.jp/archive/uri/queryform.rb バージョン 0.04

次の3つの機能を兼ねた URI::QueryForm クラスを定義しました。

  1. ハッシュと同じメソッドを持つクエリー・フォームのパラメータのコンテナ機能。ただし、invert だけはメソッドからはずしています。パラメータの値は、配列であることが原則ですが、1個だけのときは自動的に要素が1個の配列として扱います。たいていの Web アプリケーションでは、パラメータの順番はアトランダムでいいはずだと考え直して、データ保持には Hash を使っています。
  2. ruby が文字列を要求するときに自動的に文字列へ変換するシリアライズ機能。to_s で変換した文字列を明示的に取り出すこともできます。文字エスケープもシリアライズ時に自動的におこないます。エンコーディング変換はしません。
  3. クラスメソッド parse(s) を使って、クエリー文字列からパラメータに分解するデシリアライズ機能。ただし、分解されたパラメータの値の部分は、常に配列になります。分解するときにアンエスケープもおこないます。エンコーディング変換はしません。

以下、使い方のメモです。
オブジェクトの生成には、ハッシュのように交互リストで作るか、new にハッシュを渡すか2つの方法があります。いったん生成したオブジェクトは、あたかもハッシュのように値を付け加えたり、変更したり、削除することができます。freeze と dup はハッシュまで影響が及びます。

require 'uri'
require 'uri/queryform.rb'
require 'open-uri'

aws_access_key_id = 'あなたのアクセスキー'
isbn = '9784798111117'

# URI::QueryForm.[](*pairlist) による生成
aws_query = URI::QueryForm[
  'Service', 'AWSECommerceService',
  'AWSAccessKeyId', aws_access_key_id,
]

# URI::QueryForm.new(hash = {}) による生成
aws_query = URI::QueryForm.new({
  'Service' => 'AWSECommerceService',
  'AWSAccessKeyId' => aws_access_key_id,
})

# 複製してから(しなくても良い)、値を追加。[]= や update が使えます
aws_book_search_query = aws_query.dup
aws_book_search_query['Operation'] = 'ItemLookup'
aws_book_search_query.update({
  'SearchIndex' => 'Books',
  'ResponseGroup' => 'Request,Small',
  'Version' => '2007-01-15'
})

# uri.query に結び付けてやるだけで、URI のクエリー部の文字列になります
uri = URI.parse('http://webservices.amazon.co.jp/onca/xml')
uri.query = aws_book_search_query.dup
uri.query['IdType'] = 'ISBN'
uri.query['ItemId'] = isbn
puts uri
puts uri.open {|f| f.read }

直接、URI オブジェクトの生成に利用することもできます。なお、この場合、uri ライブラリが自動的に URI::QueryForm で生成したオブジェクトを clone します。

uri = URI::HTTP.build({
  :host => 'webservices.amazon.co.jp',
  :path => '/onca/xml',
  :query => URI::QueryForm[
    'Service', 'AWSECommerceService',
    'AWSAccessKeyId', aws_access_key_id,
    'Operation', 'ItemLookup',
    'SearchIndex', 'Books',
    'ResponseGroup', 'Request,Small',
    'Version', '2007-01-15'
  ],
})
uri.query['IdType'] = 'ISBN'
uri.query['ItemId'] = isbn
puts uri

URI ライブラリは、URI.build に渡したオブジェクトを自動的に clone する機能があるため、これをうまく利用するために、URI::QueryForm は clone と dup による複製も実装しています。複製すると、オブジェクト自身だけでなく、インスタンス変数のハッシュも複製します。
注意点: ただし、ハッシュの値までは複製しないため、値に push する等の副作用を伴う操作をするときは、値の複製を作って操作するのが無難です。

a = URI::QueryForm.new
a['foo'] = ['a']
b = a.dup
b['foo'] << 'b'
p a #=> ['a', 'b']
p b #=> ['a', 'b']
b['foo'] = b['foo'].dup << 'c'
p a #=> ['a', 'b']
p b #=> ['a', 'b', 'c']

URI のクエリー文字列からハッシュに分解する例です。cgi.rb の params と同じで、値は必ず配列になります。

params = URI::QueryForm.parse(uri.query)
params.each {|k, a|
  a.each {|v| puts "#{k}=#{v}" }
}