おもしろwebサービス開発日記

Ruby や Rails を中心に、web技術について書いています

Rails 3 のルーティング定義について

Rails 3のルーティングで気になったところについて。いつものメモです。あくまで気になったところなので全部網羅しているわけではありません。あしからず。

基本形

map.connect から match メソッドに変更。オプションも下記のように変更。

# Rails 2
map.connect 'products/:id', :controller => 'products', :action => 'view'
# Rails 3
match 'products/:id', :to => 'catalog#view'
# :to は省略可能
match "/account" => "account#index"
# :controller/:action 形式であればさらに省略可能
match "account/overview"

Named Routes

asオプションで指定するように変更になった。

# Rails 2
map.logout '/logout', :controller => 'sessions', :action => 'destroy'
# Rails 3
match 'logout', :to => 'sessions#destroy', :as => "logout"

メソッドの指定

get(post,put,delete)メソッドで指定できるように。またはviaオプションで。

# Rails 2
map.connect "account/overview", :controller => "account", :action => "overview", :conditions => {:method => :get }
# Rails 3
get "account/overview"
# または:viaオプションで指定
match "account/overview", :to => "account#overview", :via => :get

デフォルトパラメータの指定

defaults オプションでデフォルトで渡すパラメータを指定できる

match 'photos/:id' => 'photos#show', :defaults  => { :format => 'jpg'  }

RESTfulなルーティング

resourcesには複数の引数が渡せる(これはRails 2 からできたみたい)

resources :photos, :books, :videos

これまでresourcesメソッドのオプションとして扱われていたものがブロック内のメソッドとして使えるように。

resources :products do
  collection do
    get  :sold
    post :on_offer
  end
end

インラインでも指定出来る

resources :products do
  get :sold, :on => :member
end

resourcesで出来るアクションのルーティングをブロックの中で上書きすることが出来るみたい。

resources :sessions do
  get :create
end

asオプションを使うとnamed routesの名前を変更できる

# devices_url で products#index にアクセスできる
resources :products, :as => 'devices'

pathも変えたければ下記のようにする

# /devices にアクセスすると products#index へ
resources :devices, :controller => "products"

onlyとexceptで不必要なルーティングを作成しないように出来る(これは Rails 2.2.1 以降 と 3 で共通)

resources :posts, :except => [:index]

Resourceのネストは Rails 2 と 3 で同じ。

resources :projects do
  resources :tasks, :people
end

singularも Rails 2と同じ

resources :teeth, :singular => "tooth"

Namespaceを定義

Admin::PostsController 用のRESTfulなルーティングは下記のように定義できる

namespace :admin do
  resources :posts
end

パスは /admin/posts みたいになる。/admin を削除したければ下記のようにする。

scope :module => "admin" do
  resources :posts, :comments
end

一行で書きたい場合は下記のようにする。

resources :posts, :module => "admin"

逆に/admin/photosでPostsControllerにアクセスしたいときは下記のようにする

scope "/admin" do
  resources :posts, :comments
end

一行で書きたいときには下記のようにする

resources :posts, :path => "/admin"

newやeditのアクションのパスを変更するにはpath_namesオプションを使う。

resources :projects, :path_names => { :edit => 'cambiar' }

scope を使えばブロック中全部のルーティングに適用できる

scope :path_names => { :new => "make" } do
  # rest of your routes
end

リダイレクト

redirectメソッドを使うことで、ルーティング上でリダイレクトを設定できる。ただし他のメソッドと違い、ブロック中では使えないらしい。

# /foo/1 にアクセスしたら /bar/1sにリダイレクトする
match "/foo/:id" => redirect("/bar/%{id}s")
# /account/proc/john にアクセスしたら /johnsにリダイレクトする
match 'account/proc/:name' => redirect {|params| "/#{params[:name].pluralize}" }
# request オブジェクトもブロックパラメータとして渡せる
match "/stories" => redirect {|p, req| "/posts/#{req.subdomain}"  }

ホストのドメインを登録していなければ、Railsはリクエストベースでドメインを判断する。

Rack

ルーティング先にRackアプリケーションを指定できる。

match "/application.js" => Sprockets

"posts#index"のように指定したときは、最終的にRackアプリを返す PostsController.action(:index) が呼ばれるようになっているので全部のルーティングで最終的には Rack アプリが指定されていることになる。

Constraints

requirements オプションから constraints オプションに変わった。

match "/posts/show/:id" => "posts#index", :constraints => {:id => /\d/}

上記のような場合はconstraintsを省略できる

match "/posts/show/:id" => "posts#index", :id => /\d/

resources なルーティングには id の constraints がかけられる。

resources :photos, :constraints => {:id  => /[A-Z][A-Z][0-9]+/} 

もちろんブロックをとるメソッドも用意されている

constraints(:id => /[A-Z][A-Z][0-9]+/) do
  resources :photos
  resources :accounts
end

Requestオブジェクトのメソッドのうち、Stringを返す全てのメソッドに constraints を設定できる。例えば、Request#subdomainが定義されているので、下記のようにするとサブドメインを利用したルーティング定義が出来る*1

match "photo", :constraints => {:subdomain  => "admin"}

RegExpもキーにとれる

match "photo", :constraints => {:subdomain  => /user/}

もちろんブロックをとるメソッドも用意されている

namespace "admin" do
  constraints :subdomain => "admin" do
    resources :photos
  end
end

constraints 用のオブジェクトを作ってルーティングに使用することが出来る。オブジェクトはmathces?が定義されていればあとは特に条件はないみたい。

class BlacklistConstraint
  def initialize
    @ips = Blacklist.retrieve_ips
  end

  def matches?(request)
    @ips.include?(request.remote_ip)
  end
end

TwitterClone::Application.routes.draw do
  match "*path" => "blacklist#index",
    :constraints => BlacklistConstraint.new
end

オプション

括弧でくくったパスはオプションとして扱われる

match 'posts(/new)', :to => 'posts#create'

残り全部とマッチさせる

残り全部とマッチさせる書き方は Rails 2 と同じくアスタリスクを使う。

match 'photo/*other' => 'photos#unknown'

/photo 以下のパスは params[:other] に入る。

感想

Rails 3のルーティング定義は、2の頃ではできなかったいろんな書き方が定義されてこれまで複雑な記述をしていた定義がエレガントに書けるようになり嬉しい反面、出来ることが多すぎて覚えるのが大変そうですね><

*1:これでサブドメインRails標準で扱える!!と思ったのですが、url_forなどのリンクの生成側ではサブドメインに対応していないので結局subdomain-fu等のプラグインは必要みたいです