読者です 読者をやめる 読者になる 読者になる

まっしろけっけ

めもてきなやーつ

初心者じゃなくても役に立つかもしれないRailsのroutingの記述方をまとめてみた

まとめようと思った経緯


と思って書き始めたのが1月の中頃
何を言っているのかわからねーと思うが
気づいたら違う記事をいくつか書いて公開していた。
(いつのまにかRuby2.2.1もリリースされていた・・・)

環境

Ruby - 2.2.1
Rails - 4.2.0

urlを直接指定する

get '/games',      to: 'games#index'
get '/games/:id',  to: 'games#show'
get '/games/test', to: 'games#test', as: :test

下記のようにも書ける

get '/games'      => 'games#index'
get '/games/:id'  => 'games#show'
get '/games/test' => 'games#test', as: :test

resource

scaffoldを使用した際にroutes.rbに追記される
複数のroutingを作成してくれやつ

resource :users

これだけで下記のようなroutingが生成される(簡単!)

Prefix Verb URI Pattern Controller#Action
users GET /users(.:format) users#index
POST /users(.:format) users#create
new_user GET /users/new(.:format) users#new
edit_user GET /users/:id/edit(.:format) users#edit
user GET /users/:id(.:format) users#show
PATCH /users/:id(.:format) users#update
PUT /users/:id(.:format) users#update
DELETE /users/:id(.:format) users#destroy
一部のroutingがいらない場合

resourceで作成されるroutingでindexとshowは必要だけどcreateいらないとかのパターンは
下記の用にonly,exceptで絞ることが可能

resource :sports, only: [:index] # indexのみ有効
resource :posts, except: [:index, :show] # index,show以外有効
向き先のcontrollerを変更

下記のようにcontrollerを指定することも可能
この場合「GET /hoge」へのリクエストが「users#index」に

resources :hoge, controller: :users
Prefix Verb URI Pattern Controller#Action
hoge POST /hoge(.:format) users#create
new_hoge GET /hoge/new(.:format) users#new
edit_hoge GET /hoge/edit(.:format) users#edit
GET /hoge(.:format) users#show
PATCH /hoge(.:format) users#update
PUT /hoge(.:format) users#update
DELETE /hoge(.:format) users#destroy
ネストしてみると・・・

resourceをネストしてみると下記のようなroutingが生成される
※結果が長くなってしまうのでonlyで絞っています。

resource :authors, only: [:index, :show, :create] do
  resource :books, only: [:index, :show, :create]
end
Prefix Verb URI Pattern Controller#Action
authors_books POST /authors/books(.:format) books#create
GET /authors/books(.:format) books#show
authors POST /authors(.:format) authors#create
GET /authors(.:format) authors#show
collection
resources :dogs, only: [:index, :show, :create] do
  get 'test1', on: :collection
  get 'test2', on: :collection
end

下記のようにも書ける

resources :dogs, only: [:index, :show, :create] do
  collection do
    get 'test1'
    get 'test2'
  end
end
Prefix Verb URI Pattern Controller#Action
test1_dogs GET /dogs/test1(.:format) dogs#test1
test2_dogs GET /dogs/test2(.:format) dogs#test2
dogs GET /dogs(.:format) dogs#index
POST /dogs(.:format) dogs#create
dog GET /dogs/:id(.:format) dogs#show
member
resources :cats do
  get 'test1', on: :member
  get 'test2', on: :member
end

下記のようにも書ける

resources :cats do
  member do
    get 'test1'
    get 'test2'
  end
end
Prefix Verb URI Pattern Controller#Action
test1_cat GET /cats/:id/test1(.:format) cats#test1
test2_cat GET /cats/:id/test2(.:format) cats#test2
cats GET /cats(.:format) cats#index
POST /cats(.:format) cats#create
new_cat GET /cats/new(.:format) cats#new
edit_cat GET /cats/:id/edit(.:format) cats#edit
cat GET /cats/:id(.:format) cats#show
PATCH /cats/:id(.:format) cats#update
PUT /cats/:id(.:format) cats#update
DELETE /cats/:id(.:format) cats#destroy
new

上記のcollection, memberの他にnewも指定可能

resources :companies, only: [:index, :show, :create] do
  get 'test1', on: :new
  get 'test2', on: :new
end

下記のようにも書ける

resources :companies, only: [:index, :show, :create] do
  new do
    get 'test1'
    get 'test2'
  end
end
Prefix Verb URI Pattern Controller#Action
test1_new_company GET /companies/new/test1(.:format) companies#test1
test2_new_company GET /companies/new/test2(.:format) companies#test2
companies GET /companies(.:format) companies#index
POST /companies(.:format) companies#create
company GET /companies/:id(.:format) companies#show
param
resources :hoge, only: [:index, :show], param: :user_id
Prefix Verb URI Pattern Controller#Action
hoge_index GET /hoge(.:format) hoge#index
hoge GET /hoge/:user_id(.:format) hoge#show

namespace

URI Patternとcontrollerにnamespace Admin::を付ける

namespace :admin do
  resources :users, only: [:index, :show]
end
Prefix Verb URI Pattern Controller#Action
admin_users GET /admin/users(.:format) admin/users#index
admin_user GET /admin/users/:id(.:format) admin/users#show

scope

controllerにのみnamespace Adminを付ける

scope module: 'admin' do
  resources :authors
end

下記のようにも書ける

resources :authors, module: 'admin'
Prefix Verb URI Pattern Controller#Action
authors GET /authors(.:format) admin/authors#index
author GET /authors/:id(.:format) admin/authors#show

concern

同じような記述をgroup化しておけるイメージ(?)
DRYに書けてすっきりする気がする

concern :categorize do
  resources :large_categories, only: [:index, :show] do
    get 'test'
  end
end

resources :dogs, only: [:index], concerns: :categorize
resources :dogs, only: [:index], concerns: :categorize
Prefix Verb URI Pattern Controller#Action
dog_large_category_test GET /dogs/:dog_id/large_categories/:large_category_id/test(.:format) large_categories#test
dog_large_categories GET /dogs/:dog_id/large_categories(.:format) large_categories#index
dog_large_category GET /dogs/:dog_id/large_categories/:id(.:format) large_categories#show
dogs GET /dogs(.:format) dogs#index
cat_large_category_test GET /cats/:cat_id/large_categories/:large_category_id/test(.:format) large_categories#test
cat_large_categories GET /cats/:cat_id/large_categories(.:format) large_categories#index
cat_large_category GET /cats/:cat_id/large_categories/:id(.:format) large_categories#show
cats GET /cats(.:format) cats#index

constraints

routingに色々な条件を付与できる
デフォルトでは{id: /[^\/]+/}このような指定になっているようです。

:idを正規表現でチェック

下記のように記述すると:idが数字の場合のみ対応するactionが呼ばれる

resources :small_categories, constraints: {id: /\d+/}

下記のような記述方法もある

constraints(id: /\d+/) do
  resources :small_categories
end
Prefix Verb URI Pattern Controller#Action
small_categories GET /small_categories(.:format) small_categories#index
POST /small_categories(.:format) small_categories#create
new_small_category GET /small_categories/new(.:format) small_categories#new
edit_small_category GET /small_categories/:id/edit(.:format) small_categories#edit {:id=>/\d+/}
small_category GET /small_categories/:id(.:format) small_categories#show {:id=>/\d+/}
PATCH /small_categories/:id(.:format) small_categories#update {:id=>/\d+/}
PUT /small_categories/:id(.:format) small_categories#update {:id=>/\d+/}
DELETE /small_categories/:id(.:format) small_categories#destroy {:id=>/\d+/}

下記のような場合に便利

resources :small_categories, constraints: {id: /\d+/}, only:[:show]
get '/small_categories/test', to: "small_categories#test"

もしこのようにroutingを書いていた場合に「/small_categories/test」にアクセスすれば「small_categories#test」が呼ばれるが、
constraintsがないと「params[:id]に"test"」が格納されている状態で「small_categories#show」が呼ばれる。

記述する順番を逆にすれば回避出来るが毎回順番を気にしなければいけないのでつらめだと個人的には思う。
また、controller側で:idの値を使用する際もどのようなデータが渡ってくるのかある程度予想出来るので
考えることが減る気がする。

subdomainでチェック

下記のように記述するとアクセスしたsubdomainがadminの場合のみ
該当のmethodが呼ばれるようです。(未確認)

resources :books, only: [:index, :show], constraints: {subdomain: 'admin'}
Prefix Verb URI Pattern Controller#Action
books GET /books(.:format) books#index {:subdomain=>"admin"}
book GET /books/:id(.:format) books#show {:subdomain=>"admin"}
matches?を使用してチェック

constraintsに渡したclassのmatches? methodがtrueを返す場合に
該当のactionが呼ばれる。

例)下記のような処理ではuaがpcの場合に該当のactionが呼ばれる。

class PcConstraint 
  def matches?(request)
    # ua check処理
    # uaがpcの場合にtrueを返す
  end
end
 
Rails.application.routes.draw do
  get '/', to: 'pc#index', constraints: PcConstraint.new
end
lambdaを使用してチェック

下記のようにlambdaでもチェック可能

get '/', to: 'iphone#index', constraints: lambda {|request| request.user_agent =~ /iPhone/}
Prefix Verb URI Pattern Controller#Action
GET / pc#index
GET / iphone#index

defaults

get '/users/:id/default' => 'users#default', defaults: { format: :json}                                                                                                        
get '/posts/:id/default' => 'posts#default', defaults: { default_value: :hoge } 
Prefix Verb URI Pattern Controller#Action
GET /users/:id/default(.:format) users#default {:format=>:json}
GET /posts/:id/default(.:format) posts#default {:default_value=>:hoge}

redirect

get "user"        => redirect("/users")
get "user/:id"    => redirect("/users/%{id}")
get 'user/*other' => redirect { |params| "/users/#{params[:other]}"}
Prefix Verb URI Pattern Controller#Action
users GET /users(.:format) users#index
user GET /users/:id(.:format) users#show
GET /user(.:format) redirect(301, /users)
GET /user/:id(.:format) redirect(301, /users/%{id})
GET /user/*other(.:format) redirect(301)

まとめ

今回使用したサンプルは下記で公開していますので
routing周りをいじって色々試してみてください。

shiro16/blog-samples · GitHub

この記事書いてる間にRailsGuidesのRouting周りの話の日本語版が公開されているので
特に初心者はそっちを見た方がいいとかいう話があるかもしれない・・・