Skip to content

浜松rails3道場 routing編

yowasou edited this page Aug 13, 2023 · 39 revisions

浜松Rails3道場とは、習うより慣れろ方式で、Rails3のツボを浜松のRubyist達へ伝える試みである。

Routing編

参考

前準備

練習用のRailsプロジェクトを作る。参考環境: Ruby 1.9.2, Rails 3.0.7

% rails new routing-dojo
% cd routing-dojo
% bundle install

Routingとは

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)

Routingを使う

確認用のコントローラーを生成

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

Routingを確認する

  • サーバーを起動しブラウザで確認
  • 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"

Routingのテスト

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

Named routes

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してみよう。

教訓その1

名前なきRoute使うべからず! 名前付きの方がトラブルが少なく、テストもしやすい。

Resource Routing: the Rails標準

Resource Routingを使うことによって、レストフルなURLを通じてリソースフルなコントローラーにアクセスするためのRouteを素早く宣言できる。index, show, new, edit, create, update, deleteといったリソースを操作するためのお決まりのRouteを幾つも書かなくても、たった1行で済ませることができる。

Resource Routingの規約

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のPathとURL

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へのリンクをビューに追加してみよう。

教訓その2

可能なかぎりResource Routingを使うべし! 名前付けで悩まなくてすむ。規約により共通の理解が得やすい。Scaffoldがそのまま使える。

ネームスペース付きのResource Routing

複数のコントローラーを単一のネームスペースに配置したい場合がある。例えば、管理者用に幾つかのリソースフルなコントローラーを 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を追加して動作確認やテストをしてみよう。

パスやURLをモデルオブジェクトから作る

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なアクションを追加する

RESTfulなRoutingを使っているからといって標準の規約に縛られることはない。もし望むのであれば、リソースのコレクションや単一のリソース対応するRouteを追加することができる。

Member 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の追加

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
Clone this wiki locally