Action Pack: Resources

まず最初は、Action Pack: Resourcesというもの。中身を読んでみたけど、そもそも拡張前の仕様がわからないからさっぱり意味がわからない。
というわけで、まずはサンプルとしてあげられている「routes.rb」について調べることにする。ちなみにroutes.rbは、configディレクトリの中に置いてあります。またまたGoogle検索してみると…

優しいRailsの育て方 routes

こんな素晴らしいページが。Migrationの時と同じBlogだ、感謝。
んで、これを読んでみると、routesはURLとコントローラ/アクションのマッピングを行うためのものらしい。基本的には、マッチするURLパターンと、マッチした場合に追加するリクエストパラメータ(キーと値)を指定できる。で、ここで
map.connect '', :controller => "berryz", :action=> "index"
みたいに指定すると、berryzという名前のコントローラのindexメソッドが呼び出される。どうやら、Railsはリクエストパラメータのcontroller値とaction値をもとに呼び出し先を決めているようだ。デフォルトでは「http://ホスト名:ポート番号/コントローラー名/アクション名/ID」というようなURLの、それぞれの値が使われるわけだが、これも特別扱いされているわけではなく、URL内の値がリクエストパラメータに自動的にマッピングするというroutesの機能を使って実現されているにすぎないようだ。
この、URL内の値をリクエストパラメータに自動的にマッピングする機能を使ったデフォルト動作の指定は、
map.connect ':controller/:action/:id'
この1行で行われている。マッチするURLのパターン内に、「:」を先頭につけた値が指定されていると、そのパス値はすべてのパターンにマッチする。マッチしたパス値は、定義された(「:」が先頭についている)値をキーとするリクエストパラメータの値として設定される。
上記の定義では、「http://ホスト名:ポート番号/ココ/...」が「:controller」にマッチし、その結果、リクエストパラメータとして「:controller => ココの値」が設定される。以下、「:action」や「:id」についても同じ処理が実行される。
これでようやく、URLで指定された値がなぜ勝手にIDとして設定されるのかがわかった。

試しに、自分のroutes.rbを見てみよう。bookmarkアプリを一通り作っただけの状態だと、routes.rbは以下のような状態になっている。

・${app_root}\config\routes.rb
ActionController::Routing::Routes.draw do |map|
map.resources :items

# The priority is based upon order of creation: first created -> highest priority.

# Sample of regular route:
# map.connect 'products/:id', :controller => 'catalog', :action => 'view'
# Keep in mind you can assign values other than :controller and :action

# Sample of named route:
# map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase'
# This route can be invoked with purchase_url(:id => product.id)

# Sample resource route (maps HTTP verbs to controller actions automatically):
# map.resources :products

# Sample resource route with options:
# map.resources :products, :member => { :short => :get, :toggle => :post }, :collection => { :sold => :get }

# Sample resource route with sub-resources:
# map.resources :products, :has_many => [ :comments, :sales ], :has_one => :seller

# Sample resource route within a namespace:
# map.namespace :admin do |admin|
# # Directs /admin/products/* to Admin::ProductsController (app/controllers/admin/products_controller.rb)
# admin.resources :products
# end

# You can have the root of your site routed with map.root -- just remember to delete public/index.html.
# map.root :controller => "welcome"

# See how all your routes lay out with "rake routes"

# Install the default routes as the lowest priority.
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
end
さきほど説明した、デフォルトルートは一番下に定義されている。
# Install the default routes as the lowest priority.
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
先ほどの話どおりの、コントローラー名/アクション名/IDを設定するもの以外に、もう1つ末尾に「:format」が追加されているものがある。URLの最後に、「.xxx」とついていれば、それが「:format」というパラメータで渡されるようだ。この値の使い道は、2.0で生成されたscaffoldコントローラを見てみれば大体わかる。
# GET /items
# GET /items.xml
def index
@items = Item.find(:all)

respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @items }
end
end
この、respond_toの中で「:format」が参照され、値が存在した場合にはこの値に対応するMimeに関連した処理が実行される。例えば、「XXX.xml」というリクエストを送信すると、xmlに対応したMimeに関連する処理が実行された結果、XMLフォーマットのレスポンスが返されることになる。
以降、routes.rbの中のコメントアウトされた定義で、わかるものだけ確認してみる。

通常の定義
# Sample of regular route:
# map.connect 'products/:id', :controller => 'catalog', :action => 'view'
# Keep in mind you can assign values other than :controller and :action
この定義の例では、「http://ホスト名:ポート番号/products/xxx」というURLのリクエストは、catalogコントローラのviewメソッドにマッピングされる。先ほどの説明のとおり。

名前付き定義
# Sample of named route:
# map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase'
# This route can be invoked with purchase_url(:id => product.id)
これはルートに名前をつけるサンプルで、「map.connect」のかわりに「map.xxxx」と指定する。他は通常の定義と同じ。このように定義することで、Rubyコード中でこの名前を使って呼び出せるようになる。

さて、ここまでが基本知識。さきほどのコメントの中で、map.resources...と書かれたものの意味がよくわからない。またまたGoogleで調べてみると、とても素晴らしいBlogを見つけた。

Active Resouceの基本

これは、RESTfulなAPIとRailsのコントローラ/アクションを結びつけるための仕組みのようだ。例えば、routes.rb内の一番上に定義してある、
map.resources :items
この定義なら、
  • GET /items -> ItemsController#show
  • POST /items -> ItemsController#create
  • PUT /items -> ItemsController#update
  • DELETE items/ -> ItemsController#destroy
というようにマッピングされるらしい。なるほど。:formatとあわせれば、Ajaxで使いやすそうなサービスが簡単に実現できそう。routes.rbの例だと、
 # Sample resource route (maps HTTP verbs to controller actions automatically):
# map.resources :products
これに対応する。オプションを追加することもできるらしい。
 # Sample resource route with options:
# map.resources :products, :member => { :short => :get, :toggle => :post }, :collection => { :sold => :get }
これは、通常のmap.connectionと同様に静的にオプションを追加できる機能だろうか。サンプルはよくわからないけどとりあえず先に進む。
 # Sample resource route with sub-resources:
# map.resources :products, :has_many => [ :comments, :sales ], :has_one => :seller
サブリソースを定義できるらしい。こいつもイマイチよくわからない。
 # Sample resource route within a namespace:
# map.namespace :admin do |admin|
# # Directs /admin/products/* to Admin::ProductsController (app/controllers/admin/products_controller.rb)
# admin.resources :products
# end
これは、名前空間のサポートの例のようだ。コメントに書いてあるとおり、一段階パスをはさみ、そのパスをモジュールに対応づける機能らしい。一部わからないところはあるけど、ざっくりと概念はわかったのでとりあえず先に進むことにする。そもそも本題は、Rails2.0の新機能なんだし。で、これをふまえて新機能を読んでみると…

最初の話は名前空間のサポートの話で、ちょうど今出てきた機能。rake routesでルートの一覧が表示できるようになったということも書かれている。最後は、
  # /avatars/45 => AvatarsController#show
map.resources :avatars

# /people/5/avatar => AvatarsController#show
map.resources :people, :has_one => :avatar
こんな形で複数のリソースを同じコントローラにマッピングできるという話。これを見ると、先ほど出てきたサブリソースの定義というのは、エイリアスのような機能を指しているか、またはそういう形でも使えるようだ。
Active Resouce自体は、外部のRESTfulなサービスへの接続にも使える(というよりそれがメイン?)ライブラリなんだけど、InfoQの新機能紹介ではそこまで出てきていないので、今のところは保留にしておくことにする。別途RESTfulなサービスへの接続を試す機会があれば、深く掘り下げてみたい。

関連リンク:
Rails2.0リリース
Rails2.0のscaffoldは前とだいぶ違うらしい
Migration
Rails2.0でブックマークアプリ
Rails2.0の変更点
Action Pack: Multiview
Action Pack: Record identification

コメント