ActiveResource の使い方(中編) : メソッドの詳細
少し間が空いてしまいましたが、本日は ActiveResource の使い方の中編です。前編では概念的な部分や利用例などをご紹介しましたので、本日は各メソッドの具体的な使用方法をご紹介します。リファレンスの翻訳のようなものを作っても仕方ないので、目的別に分類して書いてみました。このほうが、これから使い始める方には役に立つかと思います。
とは言ってみたものの、一回で ActiveResource の主要なメソッドをすべて扱うのは、ちと無謀だったかもしれません・・・ orz 。説明が足りず、だいぶわかりづらくなってしまいました。不明な点は、コメントしていただければできるだけサポートしたいと思います。無念。
モデルのコンフィギュレーション
それでは、まずは ActiveResource の各モデルのコンフィギュレーションを行うメソッドを見ていきましょう。 ActiveResource はリソースごとに ActiveResource::Base の派生クラスを定義するデザインになっており、コンフィギュレーションも基本的にクラスごとになります。通常は以下のようにクラス定義の内部で個々のクラスメソッドを呼び出すことで行います。
class FooResource < ActiveResource::Base self.site = 'http://www.example.com/' end
以下、個々のメソッドをご紹介していきます。
URL の指定
前編でご紹介したとおり、 ActiveResource のデフォルトではモデル名などからリソースのパスを推測します。これは便利なのですが、ごく単純な場合を除くと、そのまま使えることはあまりありません。そこで、 URL を明示的にカスタマイズする以下のメソッド(プロパティー)が用意されています(すべて ActiveResource::Base のクラスメソッドです)。
- site
- リソースのスキーマ (http or https) 、ドメイン、ポート番号などを指定します。パス情報は含めないほうが良いでしょう。文字列か URI オブジェクトで指定してください。
- prefix
- 上位のパスを指定します。デフォルトは '/' です。 URL の生成処理がけっこう手抜きなので、先頭と末尾に '/' が必須です(^^;。また、 ':name' 形式でのパラメータ展開も可能です。詳細は find の説明をご覧ください。
- element_name
- リソースの名前を指定します。具体的には collection_name のデフォルト値として使われるほか、やりとりする XML のルート要素名にもなります。デフォルトはモデル名を小文字のアンダースコア区切りにしたものです。
- collection_name
- リソースの名前を指定します。デフォルトは element_name の複数形です。
実際に find(:all) などでアクセスされる URL は以下のようになります(以降、この形式の URL を「コレクション URL」と呼びます)。 suffix は後述のデータフォーマットで定義されている拡張子です。
#{site}#{prefix}#{collection}.#{suffix}
ちょっとわかりづらいですが、 Rails の RESTful ルーティングならば index, create アクションの呼び出しに相当する形になります。
これに対して、 ID 指定の find や save, destroy などではこうなります(以降「エレメント URL」と呼びます)。
#{site}#{prefix}#{collection}/#{id}.#{suffix}
Rails なら show, update, delete アクションに相当することがおわかりいただけるかと思います。
以上の例として、コレクション URL が "http://www.example.com:3000/api/posts.xml" となる ActiveResource モデル PostResource の定義を掲載しておきます。
class PostResource < ActiveResource::Base self.site = 'http://www.example.com:3000' self.prefix = '/api/' self.element_name = 'posts' end
BASIC 認証
こちらも前編でもご紹介したとおり、 ActiveResource は BASIC 認証をサポートしています。そのユーザー名やパスワードを指定するには、以下のように user, password を使用します。
class PostResource self.user = 'ユーザー名' self.password = 'パスワード' #... end
また、さらに別の方法として、 site に含めて指定することも可能です。
class PostResource self.site = '<html>http://user:password@www.example.com</html>' #... end
もっとも、とくにメリットもないので、通常は user, password で指定すれば良いと思いますが。
データフォーマットの指定
ActiveResource で扱うデータ形式はデフォルトで XML ですが、その他に JSON も利用できます。データ形式を JSON に切り替えるには、 format を利用します。
class PostResource < ActiveResource::Base self.format = :json #... end
また、自分で定義した Format モジュールを利用するときは、そのモジュール自体を format に設定するのが良いと思います。
module MyFormat # ... end class PostResource < ActiveResource::Base self.format = MyFormat end
フォーマットの定義方法は後編でご紹介する予定です。
その他
上記以外にも、モデルの設定変更を行うプロパティーとしては以下のものがあります。
- primary_key
- プライマリキーとして使うフィールド名を文字列(シンボル不可)で指定します。これによりプライマリキーとしたフィールドには id アクセサでアクセスできるようになり、 save や destroy でアクセスされる URL の id 部分に使われます。デフォルトは "id" です。
- timeout
- HTTP リクエストのタイムアウトを秒数で指定します。この値は Net::HTTP#read_timeout にそのまま設定されます。
リソースの取得 (find)
サーバーからリソースを取得するには、 ActiveRecord と同様に find メソッドを利用します。さすがに SQL が使えるわけではないので、呼び出し方はちょっと違ったものになっています。ここは ActiveResource のキモになる部分なので、少し詳しくご紹介しようと思います。
前提知識 : レスポンスの種類
前述のとおり、 ActiveResource がアクセスする URL にはコレクション URL とエレメント URL があり、それぞれレスポンスの形式が違います。それらの概要を知っていると以降が理解しやすいので、まずはそこから見ていきましょう。ここでは XML フォーマットで説明しますが、 JSON でも基本は同じです。
まずコレクション URL が返すレスポンスは、だいたい以下のようなものになります。
<?xml version="1.0" encoding="UTF-8"?> <posts type="array"> <post> <!-- 各フィールドのデータ --> </post> <post> <!-- 各フィールドのデータ --> </post> <!-- ... --> </posts>
このように複数のリソースをまとめた感じになっており、 ActiveRecord オブジェクトの配列を to_xml した結果と同じものです。つまり、 Scaffold が生成した index アクションのレスポンスと同じなわけですね。以降はこの形式を「コレクション形式」と呼ぶことにします。
これに対して、エレメント形式はコレクション形式のひとつのリソースを抜き出したものになっています。
<?xml version="1.0" encoding="UTF-8"?> <post> <!-- 各フィールドのデータ --> </post>
これは単体の ActiveRecord オブジェクトを to_xml したものと同じです。 Scaffold が生成した show, post, put アクションのレスポンスも同形式ですし、 post, put が受け付けるリクエストボディーもこの形式になります。以降はこれを「エレメント形式」と呼びます。
このように、 Rails が返す標準的なデータ形式に合わせて、 ActiveResource も 2 つのデータ形式を持っていることを覚えておいてください。
find の基本的な使い方
さて、データ形式がわかったところで、実際にリソースを取得する方法を見ていきましょう。前述のとおり、リソースの取得にはクラスメソッドの find を使います。基本的な書式は以下になります。
find(シンボル or ID, :params => { クエリーパラメータ })
第一引数は以下のシンボル、もしくはプライマリキーの値で、それによって返り値が単体のオブジェクトだったり、配列だったりします。シンボルの場合、指定できる値は以下の三種類です(本当はもうひとつありますが、後述)。
シンボル | メソッドの返り値 |
---|---|
:all | 全オブジェクトの配列 |
:first | コレクションの先頭のオブジェクト |
:last | コレクションの最後のオブジェクト |
これらのシンボルを指定した場合、リクエストはコレクション URL に発行されるので、 Rails で言えば index アクションを呼び出すことになります。当然レスポンスはコレクション形式になり、 :all ならばそれを復元した配列がそのまま返ります。 :first, :last の場合は、その配列の先頭要素もしくは最後の要素を返します。つまり、以下の 2 行はほぼ同じ処理になります。
post = PostResource.find(:all)[0] post = PostResource.find(:first)
第一引数がシンボルではなかった場合、それはプライマリキーの指定として扱われます。この場合、リクエストはエレメント URL に発行されますので、 Rails ならば show アクションが呼び出されます。レスポンスはエレメント形式で、それを復元したオブジェクトが find の返り値になります。
いずれの場合でも、 params が指定されれば、それをクエリーパラメータ("?key1=value1&key2=value2…" の形式)に展開して、リクエスト URL の末尾に付加します。ただし、次で説明する prefix パラメータは除外されます。
prefix パラメータの指定
前述のとおり、 prefix には ":name" という形式のプレースホルダを含めることができます。例えば、以下のように。
class Comment self.site = 'http://www.example.com' self.prefix = '/posts/:post_id/' end
この場合、 find に渡した params[:post_id] の値で prefix の ":post_id" が置換されます。勘の良い方はお分かりかと思いますが、この機能はネストした(もしくは :has_many 等を使った) RESTful ルーティングに対応するためのものです。例えばサーバー側のルーティングが以下のように設定されていた場合・・・
map.resources :posts do |posts| posts.resources :comments end
以下のコードで ID=5 の投稿についた全コメントを取得できます。
comments = Comment.find(:all, :params => { :post_id => 5 })
ActiveResource は has_many などのアソシエーションをサポートしないので、このような方法で個別にアクセスすることになります。このあたりは、もう少し頑張ってほしいところですね。
また、 find のときに指定された prefix パラメータは各オブジェクトに保存され、 save, destroy などで自動的に適用されます。したがって、以下のコードだけで・・・
Comment.find(:first, :params => { :post_id => 5 }).destroy
ID=5 の投稿の最初のコメントを削除できます。
パスの直指定
ここまではコレクション URL かインスタンス URL のいずれかにリクエストを送るものでしたが、 :from オプションを使うと、それ以外の任意のパスにアクセスできます(ただし、 site は変更できません)。その場合の書式は以下になります。
find(シンボル, :from => パス, :params => { クエリーパラメータ }
第一引数のシンボルには、「基本的な使い方」で出てきた三種類の他に、 :one も指定できます。この場合はレスポンスをエレメント形式として扱い、それを復元したオブジェクトを返します。パスの直指定では ID 指定がないので、その代わりに :one を使うわけです。
パスはもちろんアクセスするパスで、 "/path/to/resource" のように絶対パスを文字列で指定してください。クエリーパラメータは通常の find と同様にパスの最後に付加されます。
このように、パスを直指定することで任意のリソースにアクセスできますが、あまり美しくありませんよね。可能な場合は後述するカスタムメソッドを使う方が望ましいでしょう。
再読み込み
find で取得したオブジェクトの内容を最新の状態に更新したい場合は、 reload メソッドを呼びます。
comment = Comment.find(:first, :params => { :post_id => 5 }) # この間に他のクライアントがデータを変更 comment.reload # 最新のデータが反映される
こうすると、エレメント URL にリクエストを発行し、そのレスポンス(当然エレメント形式)の内容でオブジェクトを更新します。 prefix パラメータは find で指定されたものが適用されるので、 reload で再度指定する必要はありません。また、パスの直指定や後述のカスタムメソッドで取得したオブジェクトを reload した場合でも、リクエストは標準のエレメント URL に対して行われるので注意してください。
フィールドへのアクセス
リソースを取得したら、当然各フィールドにアクセスしますよね。前編でも少し触れましたが、基本的には ActiveRecord と同じくアクセサ経由でアクセス可能です。
post = PostResource.find(:first) puts post.title post.title += ' - WebOS Goodies'
日本語等のメソッド名として許されないフィールド名には、 attributes 経由でアクセスします。
puts post.attributes['日本語フィールド']
ActiveRecord とは違い、 [ ] 演算子は定義されていません。また、フィールド名にシンボルは使えず、必ず文字列で指定しなければいけません。このあたりはまだ ActiveRecord ほど気が利いていない感じですね。将来の改善に期待です。
リソースの新規作成と mass assignment
サーバーから読み込むのではなく、新規に ActiveResource オブジェクトを作成するには、 new, create のいずれかのクラスメソッドを使います。
- new
- 新しい ActiveResource オブジェクトを作成し、引数として与えられたハッシュでオブジェクトの内容を初期化します。もしハッシュの中に prefix パラメータが含まれていた場合、それはフィールドには代入せずに prefix パラメータとして別に保存します。
- create
- new とほぼ同じですが、オブジェクトを作成した後、自動的に save メソッドを呼び出します。
また、既存のオブジェクトに mass assignment を行うには、以下の 2 つのメソッドを使います。これらはクラスメソッドではなく、通常のメソッドです。
- load
- 引数としてハッシュを与えると、その各要素をフィールドに設定します。 new と同様、 prefix パラメータは別に保存します。また、与えたハッシュに含まれないフィールドについては、以前の値が保持されます。
- attributes
- フィールドのアクセスに使う attributes そのものにハッシュを代入すると、その内容ですべてのフィールドを置き換えます。 load との違いは、 prefix パラメータの処理を行わないことと、以前のフィールドは(ハッシュに含まれないものも含めて)すべて失われる点です。
いずれの場合も、ハッシュのキーは文字列でなければならず、シンボルは使えません。
リソースの保存
save メソッドを呼び出すと、サーバーに POST もしくは PUT リクエストを送信して、リソースを保存します。このとき、プライマリキーにあたるフィールドが存在するかどうかで、 HTTP メソッドや送信先 URL が以下のように変わります。
プライマリキー | HTTP メソッド | リクエスト URL |
---|---|---|
なし | POST | コレクション URL |
あり | PUT | エレメント URL |
つまり、プライマリキーがなければ新規作成、あれば更新という扱いになるわけです。いずれの場合も、リクエスト・ボディーにはエレメント形式でシリアライズされたデータが格納されます。
URL が prefix パラメータを含むときは、オブジェクトに保存されているものでそれを置き換えます。例えば、先ほど出てきた Comment リソースを保存するには、以下のようにします。
comment = Comment.new(:post_id => 5, 'title' => 'タイトル', 'body' => 'コメント') comment.save
このように、 save メソッドでは prefix パラメータを指定する必要はなく(というかできない)、 new や find で指定されたものがそのまま再利用されます。
また、サーバー側のバリデーションでエラーが発生したときは、その内容が errors に格納され、 save は false を返します。このあたりの詳細は前編をご覧ください。
リソースの削除
destroy メソッドを呼び出すと、エレメント URL に対して delete リクエストが発行できます。たいていはこれでリソースを削除されるでしょう。例えば Comment リソースの削除は以下でできます。
comment = Comment.find(3, params => { :post_id => 5 }) comment.destroy
また、クラスメソッドの delete を使う方法もあります。
Comment.delete(3, :post_id => 5)
find が省けるので、あらかじめプライマリキーがわかっているなら、こちらのほうが効率が良くなります。うまく使い分けてください。
カスタムメソッド
Rails で RESTful なルーティングを定義する際には、 :collection や :member オプションを使用して標準(index, show, create, update, delete)以外のアクションへのルートを設定できます。
map.resources(:post, :collection => { :recent => :get }, :member => { :tags => :all })
ActiveResource でこれらのアクションにアクセスするには、「カスタムメソッド」という機能を使います。カスタムメソッドには find を使う方法と get, post, put, delete を使う方法がありますので、それぞれ個別に見ていきましょう。
find を使う
「URL の直指定」で find メソッドの from パラメータをご紹介しましたが、そこに(文字列ではなく)シンボルを渡すと、カスタムメソッドへのアクセスとして扱われます。指定したシンボルを method とすると、以下の URL に GET リクエストを発行します。
#{site}#{prefix}#{collection}/#{method}.#{suffix}
URL 直指定のときと同様、 find の第一引数にはシンボル (:all, :first, :last, :one) のみが指定可能です。レスポンスの扱いも同じで、返り値はそのクラスのオブジェクト (:first, :last, :one) 、もしくは配列 (:all) になります。
例として、先ほどのルーティングにある recent アクションが最近更新された投稿の配列を返すとすると、それにアクセスするコードは以下のようになるでしょう。
recent_posts = PostResource.find(:all, :from => :recent)
この方法でアクセスするアクションは、標準の index や show アクションと同じ形式のレスポンスを返さなければなりません。
get, post, put, delete を使う
find によるカスタムメソッドでは、 HTTP メソッドが GET のみで、レスポンスの形式も限定されます。これらの制限が問題になる場合は、クラスメソッド、インスタンスメソッドの双方に定義されている get, post, put, delete メソッドを使います。それぞれの書式は以下のとおりです。
get(カスタムメソッド名, クエリーパラメータ) post(カスタムメソッド名, クエリーパラメータ, リクエストボディー) put(カスタムメソッド名, クエリーパラメータ, リクエストボディー) delete(カスタムメソッド名, クエリーパラメータ)
予想はついていると思いますが、 get, post, put, delete はそれぞれ対応する HTTP メソッドのリクエストを発行します。アクセスするアクションは第一引数にシンボルで指定します。これを method とすると、クラスメソッドの場合は
#{site}#{prefix}#{collection}/#{method}.#{suffix}
へのアクセスとなり、インスタンスメソッドの場合は
#{site}#{prefix}#{collection}/#{id}/#{method}.#{suffix}
となります。ただし、まだ save されていない(プライマリキーを持たない)オブジェクトに対して post を読んだ場合は、例外的に以下の URL になります。
#{site}#{prefix}#{collection}/new/#{id}/#{method}.#{suffix}
第二引数は find の params と同じハッシュで、 prefix パラメータにあるものは prefix に展開され、それ以外はクエリーパラメータとして URL の最後に付加されます。
さらに post, put には省略可能な第三引数があり、リクエストボディーの文字列を指定します。省略した場合、クラスメソッドでは空文字列、インスタンスメソッドではオブジェクトの内容をエレメント形式の XML (or JSON) に変換したものになります。
返り値はレスポンス・ボディーを Hash.from_xml や ActiveSupport::JSON.decode で復元したハッシュ、もしくは配列になります。 find と違い、 ActiveResource オブジェクトに自動的に変換されたりはしません。
例えば、先ほど定義した tags アクションが以下のような JSON(?) をやりとりする場合、
['tag1', 'tag2', ...]
それにアクセスするコードは以下のようになるでしょう。
post = PostResource.find(1) tags = post.get(:tags) # => ['tag1', 'tag2'...] tags << 'newtag' tags.put(:tags, {}, ActiveSupport::JSON.encode(tags))
post, put 時に自力でエンコードするのが少々面倒ですね。場合によっては専用の ActiveResource クラスを定義してしまうのも手だと思います。
その他
これまでにご紹介したメソッドでたいていの処理はまかなえると思いますが、 ActiveResource::Base には他にもいくつかメソッドがあります。それらのうち主なものを簡単にリストアップしておきます。
クラスメソッド
- prefix(prefixパラメータ)
- prefixパラメータによる置き換え後の prefix を返します。
- prefix_source
- パラメータ置き換え前の prefix (prefix に代入したそのままの文字列)を返します。
- collection_path(prefixパラメータ, クエリーパラメータ)
- コレクション URL のパス部分を返します。スキーマやドメイン名は含まれません。クエリーパラメータを省略すると、prefixパラメータから抽出します。
- element_path(id, prefixパラメータ, クエリーパラメータ)
- エレメント URL のパス部分を返します。あとは collection_path と同じ。
- connection(refresh = false)
- サーバーとの接続に使う ActiveResource::Connection クラスのオブジェクトを返します。第一引数に true を渡すと、オブジェクトを再作成します。
- exists?(id, クエリーパラメータ)
- HTTP メソッドの HEAD を使って、リソースが存在するかどうかを確認します。しかし、 Rails 2.2.2 ではバグがあって動かないようです。
インスタンスメソッド
- new?
- オブジェクトが新規作成されたものなら true を返します。実際には、プライマリキーのフィールドが存在するかどうかで判断しています。 Rails 2.2.2 では定義されていませんが、将来的には new_record? がエイリアスとして定義されるようです。
- collection_path(クエリーパラメータ)
- 同名のクラスメソッドと同じです。クエリーパラメータはクラスメソッドの第二引数に渡されます。
- element_path(クエリーパラメータ)
- 同名のクラスメソッドと同じです。クエリーパラメータは(以下略)
- exists?
- 同名の暮らすメソッドと同じです。
以上、本日は ActiveResource の各メソッドを詳細にご紹介しました。 Rails アプリケーションとの通信に使うのであれば、ここに書いた知識でほとんど賄えるはずです。ぜひ活用してください。
次回の後編では、一般的な REST API 用に ActiveResource をハックする際の勘所をご紹介しようと思います。お楽しみに!
詳しくはこちらの記事をどうぞ!
この記事にコメントする