-
Notifications
You must be signed in to change notification settings - Fork 8
浜松rails3道場 routing編
浜松Rails3道場とは、習うより慣れろ方式で、Rails3のツボを浜松のRubyist達へ伝える試みである。
- Ruby on Rails Guides: Rails Routing from the Outside In
- RailsCasts - #203 Routing in Rails 3
- Ruby on Rails Documentation
練習用のRailsプロジェクトを作る。参考環境: Ruby 1.9.2, Rails 3.0.7
% rails new routing-dojo
% cd routing-dojo
% bundle install
HTTPリクエスト(URL、メソッド)を受け取って、コントローラーのアクションを呼び出す仕組み。また、パスやURLを生成し文字列によるこれらのハードコードを回避することができる。
The Rails router recognizes URLs and dispatches them to a controller’s action. It can also generate paths and URLs, avoiding the need to hardcode strings in your views.(Ruby on Rails Guides)
確認用のコントローラーを生成
rails g controller catalog view
設定ファイルにrouteを追加する。
# config/routes.rb
match 'products/:id' => 'catalog#view'
Viewで使う
<% # app/views/catalog/view.html.erb %>
<%= link_to 'Product: 2', :controller => 'catalog', :action => 'view', :id => 2 %>
Controllerで使う
# app/controllers/catalog_controller.rb
redirect_to :controller => 'catalog', :action => 'view', :id => 3
- サーバーを起動しブラウザで確認
- rake routes タスクで確認
- rails console コマンドで確認
- テストを書いて確認
% rake routes
...
/products/:id(.:format) {:controller=>"catalog", :action=>"view"}
...
% rails console
Loading development environment (Rails 3.0.7)
> r = ActionController::Routing::Routes
> r.recognize_path '/products/1'
=> {:controller=>"catalog", :action=>"view", :id=>"1"}
> r.generate :controller => 'catalog', :action => 'view', :id => '2'
=> "/products/2"
Railsのコントローラーのテストでは、Routingをテストするための3種類のビルトインアサーションが用意されている。
# test/functional/catalog_controller_test.rb
# assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)
assert_generates "/products/1", :controller => 'catalog', :action => 'view', :id => '1'
# assert_recognizes(expected_options, path, extras={}, message=nil)
assert_recognizes({ :controller => "catalog", :action => "view", :id => "2" }, "/products/2")
# assert_routing(path, options, defaults={}, extras={}, message=nil)
assert_routing "/products/3", { :controller => "catalog", :action => "view", :id => "3" }
url_forなどを使いビューのテストでも検証することができる。
# test/unit/helpers/catalog_helper_test.rb
assert_equal '/products/1', url_for(:controller => 'catalog', :action => 'view', :id => '1')
- 'products/:id' の productsと:id の部分を変更してみよう。
- ビューで :controller や :action などのキーをTypoしてみよう。
Test::Unitの代わりにRSpecを使うと専用のRouting specsが生成される。
# spec/routing/catalog_routing.spec
describe "routing to catalog" do
it "routes /products/:id to catalog#show for id" do
{ :get => "/products/1" }.should route_to(
:controller => "catalog",
:action => "view",
:id => "1"
)
end
end
Routeには名前を付けることができる。
# config/routes.rb
match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
# test/functional/catalog_controller_test.rb
assert_equal "/products/1/purchase", purchase_path(:id => '1')
assert_equal "http://test.host/products/2/purchase", purchase_url(:id => 2)
アプリケーションのrootでは専用のショートカットが使える。
root :to => "welcome#index" # root_path => '/'
- 'catalog#view' へのRoutingにも名前を付けてみよう。
- ビューでxxx_path, xxx_url をTypoしてみよう。
名前なきRoute使うべからず! 名前付きの方がトラブルが少なく、テストもしやすい。
Resource Routingを使うことによって、レストフルなURLを通じてリソースフルなコントローラーにアクセスするためのRouteを素早く宣言できる。index, show, new, edit, create, update, deleteといったリソースを操作するためのお決まりのRouteを幾つも書かなくても、たった1行で済ませることができる。
RailsではResource Routingの規約に基づいて、HTTPメソッドとURLをコントローラーのアクションにマッピングする。
まず、設定ファイルに以下のようなRouteを追加する。
# config/route.rb
resources :products
これにより、PruductsControllerの7つのアクションとHTTPリクエストをマッピングする以下のようなRouteが追加される。
HTTPメソッド | リクエストPath | アクション | 一般的な動作 |
---|---|---|---|
GET | /products | index | Productのリストを表示する |
GET | /products/new | new | 新しいProductを追加するためのフォームを表示する |
POST | /products | create | 新しいProductを作成する |
GET | /products/:id | show | 指定されたProductを表示する |
GET | /products/:id/edit | edit | 指定されたProductを編集するためのフォームを表示する |
PUT | /products/:id | update | 指定されたProductを更新する |
DELETE | /products/:id | destroy | 指定されたProductを削除する |
Resource Routingで追加されたRouteは以下のような名前で呼び出すことができる。
products_path # => /products
new_product_path # => /products/new
edit_product_path(id) # => /products/:id/edit (例: edit_product_path(10) => /products/10/edit)
product_path(id) # => /products/:id (例: product_path(10) => /products/10)
- Resource Routing によって呼び出される7つのアクションを持つProductsControllerを追加して動作確認やテストをしてみよう。
- 同じリクエストPathを持ち、HTTPメソッドが違うRouteへのリンクをビューに追加してみよう。
可能なかぎりResource Routingを使うべし! 名前付けで悩まなくてすむ。規約により共通の理解が得やすい。Scaffoldがそのまま使える。
複数のコントローラーを単一のネームスペースに配置したい場合がある。例えば、管理者用に幾つかのリソースフルなコントローラーを Admin::ネームスペースに配置したいとする。つまり、 app/controllers/admin ディレクトリの中にコントローラーを作成した場合は以下のように記述すれば良い。
namespace :admin do
resources :products
end
これにより、以下のようなRouteが追加される。
HTTPメソッド | リクエストPath | アクション | ヘルパー |
---|---|---|---|
GET | /admin/products | index | admin_products_path |
GET | /admin/products/new | new | new_admin_product_path |
POST | /admin/products | create | admin_products_path |
GET | /admin/products/1 | show | admin_product_path(id) |
GET | /admin/products/1/edit | edit | edit_admin_product_path(id) |
PUT | /admin/products/1 | update | admin_product_path(id) |
DELETE | /admin/products/1 | destroy | admin_product_path(id) |
- Admin::ProductsControllerを追加して動作確認やテストをしてみよう。
アプリケーション内に親子関係を持つ以下のようなモデルがあるとする。
class Products < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :product
end
Routeをネストするによってこのような関連を設定できる。この場合、以下のようなRoutingを宣言する。
resources :products do
resources :comments
end
これにより、CommentsControllerとHTTPリクエストをマッピングする以下のようなRouteが追加される。
HTTPメソッド | リクエストPath | アクション | ヘルパー |
---|---|---|---|
GET | /products/1/comments | index | product_comments_path(product_id) |
GET | /products/1/comments/new | new | new_product_comment_path(product_id) |
POST | /products/1/comments | create | product_comments_path(product_id) |
GET | /products/1/comments/1 | show | product_comment_path(product_id, comment_id) |
GET | /products/1/comments/1/edit | edit | product_comment_path(product_id, comment_id) |
PUT | /products/1/comments/1 | update | product_comment_path(product_id, comment_id) |
DELETE | /products/1/comments/1 | destroy | product_comment_path(product_id, comment_id) |
- CommentsControllerを追加して動作確認やテストをしてみよう。
Resource Routingを使うとヘルパーのオプションパラメータとしてidを渡す代わりに、モデルのオブジェクト自体を渡すことができます。
<%= link_to "Comment details", procuct_comment_path(@product, @comment) %>
url_forに同じオブジェクトを渡せば、Railsは自動的にRouteを判別してヘルパーを見つけてくれる。
<%= link_to "Comment details", url_for(@product, @comment) %>
さらに、link_to ヘルパーではオブジェクトを渡すだけで url_for を呼ぶことができる。
<%= link_to "Comment details", [@product, @comment] %>
もしProductにリンクしたいだけであれば配列も不要である。
<%= link_to "Product details", @product %>
RESTfulなRoutingを使っているからといって標準の規約に縛られることはない。もし望むのであれば、リソースのコレクションや単一のリソース対応するRouteを追加することができる。
Member Routeは単一のリソースにアクセスするRouteである。追加したければ、 resources ブロックに member ブロックを追加する。
resources :products do
member do
get 'short'
post 'toggle'
end
end
これらのRouteは例えば、 GET /products/1/short
はProductsController#show にマッピングされる。 それと同時に short_product_url(id) や toggle_product_path(id) ヘルパーも生成される。
memberブロックを使わずに個々のRouteでHTTPメソッドを指定して認識させることもできる。
resources :products do
get 'short', :on => :member
post 'toggle', :on => :member
end
Collection RouteはリソースのコレクションにアクセスするRouteである。追加したければ、 resources ブロックに collection ブロックを追加する。
resources :products do
collection do
get 'sold'
end
end
このRouteは、 GET /products/sold
がProductsController#sold にマッピングされる。 それと同時に sold_products_url や sold_products_path ヘルパーも生成される。
collectionブロックを使わずに :on オプションでHTTPメソッドを指定することもできる。
resources :products do
get 'sold', :on => :collection
end
### 試してみよう!
* Admin::ProductsController#short を呼び出す Member Route を設定してみよう。
* CommentsController#search を呼び出す Collection Routeを 設定してみよう。
## Customizing Resourceful Routes
### Specifying a Controller to Use
### Specifying Constraints
### Overriding the Named Helpers
### Overriding the new and edit Segments
### Prefixing the Named Route Helpers
### Restricting the Routes Created
### Translated Paths
### Overriding the Singular Form
### Using :as in Nested Resources