Rails のルーティング

ルーティング解析

  • config/routes.rbの上から

ルーティングファイルの分割

  • ルーティングファイルの分割機能は、本当に必要になるまでは使わない
  • ルーティングが数百件にのぼる場合であっても)、ルーティングファイルを1つにまとめておく方が開発者にとって扱いやすい
  • 基本的にnamespaceやscopeでルーティングを体系的に分割する
# config/routes.rb
 
Rails.application.routes.draw do
  get "foo", to: "foo#bar"
 
  draw(:admin) # `config/routes/admin.rb`にある別のルーティングファイルを読み込む
  draw(:api)
end

確認

http://localhost:3000/rails/info/routes
bin/rails routes
bin/rails routes --expanded
bin/rails routes -g admin
bin/rails routes --unused
  • コンソール
irb> Rails.application.routes.url_helpers.users_path
=> "/users"
irb> user = User.first
=> #<User:0x00007fc1eab81628
irb> app.edit_user_path(user)
=> "/users/1/edit"

ルートパスを指定する

root "pages#main"

リソースフルルーティング

resources :photos
HTTP verbパスコントローラ#アクション目的
GET/photosphotos#indexすべての写真の一覧を表示
GET/photos/newphotos#new写真を1つ作成するためのHTMLフォームを返す
POST/photosphotos#create写真を1つ作成する
GET/photos/:idphotos#show特定の写真を表示する
GET/photos/:id/editphotos#edit写真編集用のHTMLフォームを1つ返す
PATCH/PUT/photos/:idphotos#update特定の写真を更新する
DELETE/photos/:idphotos#destroy特定の写真を削除する

複数

resources :photos, :books, :videos

単数

  • indexアクションは不要なため作られない
  • resolveは、Geocoderのインスタンスをレコード識別を介して単数形ルーティングに変換するために必要
resource :geocoder
resolve("Geocoder") { [:geocoder] }

パスとコントローラーとルーティングヘルパーの構成

namespaceを分ける

  • namespaceスコープを使うと、:moduleや:pathプレフィックスに加えて:asも自動的に追加される
namespace :admin do
  resources :articles
end

パスにはモジュール名を付けたくないが、コントローラーはディレクトリを分ける

scope module: "admin" do
  resources :articles
end
 
OR
 
resources :articles, module: "admin"
 
OR
 
resources :user_permissions, controller: "admin/user_permissions"

パスにはモジュール名を付けたいが、コントローラーはディレクトリを分けたくない場合

  • namespaceスコープを使うと、:moduleや:pathプレフィックスに加えて:asも自動的に追加される
scope "/admin" do
  resources :articles
end
 
OR
 
scope "admin", as: "admin" do
  resources :photos
end

パスを浅くする

  • リソースのネスティングの深さは、1階層にとどめておくべき
  • member -> 個別のリソース
  • collection -> 複数のリソース
shallow do
  resources :articles do
    resources :comments
    resources :quotes
  end
end
 
OR
 
resources :articles, shallow: true do
  resources :comments
  resources :quotes
end
 
OR
 
resources :articles do
  resources :comments, shallow: true
  resources :quotes, shallow: true
end

共通化

  • 共通化しない方が見やすい場合もある

共通化前

resources :messages do
  resources :comments
end
 
resources :articles do
  resources :comments
  resources :images, only: :index
end

共通化後

concern :commentable do
  resources :comments
end
 
concern :image_attachable do
  resources :images, only: :index
end
 
resources :messages, concerns: :commentable
resources :articles, concerns: [:commentable, :image_attachable]

scopeやnamespace内で使う場合

  • concerns(複数形)を使う
namespace :messages do
  concerns :commentable
end
 
namespace :articles do
  concerns :commentable
  concerns :image_attachable
end

リンク生成

詳細ページ

  • インスタンスのみを使う
  • 内部的には@magazine.model_name.route_key, @magazine.to_param
<%= link_to 'Magazine details', @magazine %>
<%= link_to 'Ad details', [@magazine, @ad] %>

アクションを指定する場合

<%= link_to 'Edit Ad', [:edit, @magazine, @ad] %>

デフォルトの7つ以外のパスやアクションにする

  • 文字列はパスとして推測されるが、シンボルはコントローラのアクションとして認識される
  • 基本的にはリソースフルルーティングを使うべき
  • リソースフルルーティングが適していない場合に、アプリケーションのあらゆる部分を無理にリソースフルなフレームワークに押し込める必要はない
  • リソースフルでないルーティングが適しているユースケースの1つに、既存のレガシーURLを新しいRailsアクションに対応付ける場合

パスとアクション名を変えたい

  • メンバーアクションを変える
resources :photos do
  member do
    get "preview"
  end
end
 
OR
 
resources :photos do
  get "preview", on: :member
end
  • コレクションアクションを変える
resources :photos do
  collection do
    get "search"
  end
end
 
OR
 
resources :photos do
  get "search", on: :collection
end
  • 特定のアクションを変える
  • pathnamesで変えるとルーティングヘルパーやアクション名は変わらない
resources :photos do
  get "search", on: :new
end

同じアクションにする

  • 一覧と詳細を同じアクションにする
get "photos(/:id)", to: "photos#display"
  • GETとPOSTは同じアクションにする
  • GETアクションはCSRFトークンをチェックしないため、1つのアクションにGETリクエストとPOSTリクエストの両方をルーティングすると、セキュリティ上の問題が発生する
match "photos", to: "photos#show", via: [:get, :post]
  • すべてのHTTPメソッドを同じアクションにする
  • GETアクションはCSRFトークンをチェックしないため、1つのアクションにGETリクエストとPOSTリクエストの両方をルーティングすると、セキュリティ上の問題が発生する
match "photos", to: "photos#show", via: :all

パラメーター

  • パスパラメーターとクエリパラメーターはどちらもコントローラーではparamsで取得できる
get "photos/:id/:user_id", to: "photos#show"

デフォルトパラメーター

  • params[:format]は常に”jpg”になる
  • クエリパラメーターでformatを指定しても、セキュリティ上の理由でparams[:format]は”jpg”になる
  • formatはすべてのパスに含まれる暗黙的なオプションのパラメータでもある
get "photos/:id", to: "photos#show", defaults: { format: "jpg" }

制約

  • :constraintsでは正規表現を使えるが、アンカー(

パラメーター

  • 桁数の範囲を制限(bigintは19桁が最長、unsignedは最長で20桁)
get "photos/:id", to: "photos#show", constraints: { id: /\d[1,20]/ }

サブドメイン

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

format

  • jsonのみ
constraints format: :json do
  resources :photos
end
 
OR
 
resources :photos, constraints: { format: :json }
 
OR
 
get "foo", constraints: lambda { |req| req.format == :json }
  • formatを無視
constraints format: false do
  resources :photos
  resources :articles
  resources :comments
end
  • formatを必須
constraints format: true do
  resources :photos
end

カスタム

class RestrictedListConstraint
  def initialize
    @ips = RestrictedList.retrieve_ips
  end
 
  def matches?(request)
    @ips.include?(request.remote_ip)
  end
end
 
Rails.application.routes.draw do
  constraints(RestrictedListConstraint.new) do
    get "*path", to: "restricted_list#index"
    get "*other-path", to: "other_restricted_list#index"
  end
end

リダイレクト

  • デフォルトは301(Moved Permanently)
get "/stories/:name", to: redirect("/articles/%{name}")
get "/stories", to: redirect { |path_params, req| "/articles/#{req.subdomain}" }
get "/stories/:name", to: redirect("/articles/%{name}", status: :found|:moved_permanently|:see_other|:temporary_redirect|:permanent_redirect)

direct

外部URL

direct :homepage do
  "http://www.rubyonrails.org"
end

カスタムURLヘルパー

  • ブロックの戻り値は、必ずurl_forメソッドで有効な1個の引数にしなければならない
  • 有効な「文字列URL」「ハッシュ」「配列」「Active Modelインスタンス」「Active Modelクラス」のいずれか1つ
direct :commentable do |model|
  [ model, anchor: model.dom_id ]
end
 
direct :main do
  { controller: "pages", action: "index", subdomain: "www" }
end