【Rails】よく使うルーティング記法


## はじめに
こんにちは。[kosukein38](https://twitter.com/kosukein38)です。
「こんな感じのpathにしたいんだけど、このルーティングっってどう書くんだっけ？」ってなって試行錯誤することが多かったので、ノート的によく使うルーティング記法をまとめました。


## 対象者
- Railsのルーティング設定に慣れていない方

## ルーティングとは
[Railsガイド](https://railsguides.jp/routing.html#rails%E3%83%AB%E3%83%BC%E3%82%BF%E3%83%BC%E3%81%AE%E7%9B%AE%E7%9A%84)によると、以下の定義。
> Railsのルーターは受け取ったURLを認識し、適切なコントローラ内アクションやRackアプリケーションに振り分けます。ルーターはパスやURLも生成できるので、ビューでこれらのパスやURLを直接ハードコードする必要はありません。

ざっくりとは、クライアントから受け取ったHTTPリクエストを適切なコントローラーに振り分けたり、パスやURLを簡単に生成できるようにするためのルールです。HTTPリクエストを正しいコントローラーで処理し、適切なビューを表示させたり、APIでデータを返却したりしています。


## よく使うルーティング
### resourcesを使ったルーティング設定
`:id`を含むリソース作られる。
（例）TaskモデルのCRUD

| Helper         | HTTP Verb  | Path                      | Controller#Action | 
| -------------- | ---------- | ------------------------- | ----------------- | 
| tasks_path     | GET        | /tasks(.:format)          | tasks#index       | 
|                | POST       | /tasks(.:format)          | tasks#create      | 
| new_task_path  | GET        | /tasks/new(.:format)      | tasks#new         | 
| edit_task_path | GET        | /tasks/:id/edit(.:format) | tasks#edit        | 
| task_path      | GET        | /tasks/:id(.:format)      | tasks#show        | 
|                | PATCH(PUT) | /tasks/:id(.:format)      | tasks#update      | 
|                | DELETE     | /tasks/:id(.:format)      | tasks#destroy     | 

**上を設定する場合のルーティングの記法**

```ruby:config/routes.rb

Rails.application.routes.draw do
  resources :tasks
end

```

### resourceを使ったルーティング設定
`:id`を含むルーティングが生成されません。

（例）SettingモデルのCRUD

| Helper             | HTTP Verb  | Path                     | Controller#Action | 
| ------------------ | ---------- | ------------------------ | ----------------- | 
| new_settings_path  | GET        | /settings/new(.:format)  | settings#new      | 
| edit_settings_path | GET        | /settings/edit(.:format) | settings#edit     | 
| settings_path      | GET        | /settings(.:format)      | settings#show     | 
|                    | PATCH(PUT) | /settings(.:format)      | settings#update   | 
|                    | DELETE     | /settings(.:format)      | settings#destroy  | 
|                    | POST       | /settings(.:format)      | settings#create   | 

**上を設定する場合のルーティングの記法**

```ruby:config/routes.rb

Rails.application.routes.draw do
  resource :settings
end

```


### 名前空間を使ったルーティング設定
adminと他のリソースを分けたい時などに使います。


| Helper               | HTTP Verb  | Path                            | Controller#Action   | 
| -------------------- | ---------- | ------------------------------- | ------------------- | 
| admin_tasks_path     | GET        | /admin/tasks(.:format)          | admin/tasks#index   | 
|                      | POST       | /admin/tasks(.:format)          | admin/tasks#create  | 
| new_admin_task_path  | GET        | /admin/tasks/new(.:format)      | admin/tasks#new     | 
| edit_admin_task_path | GET        | /admin/tasks/:id/edit(.:format) | admin/tasks#edit    | 
| admin_task_path      | GET        | /admin/tasks/:id(.:format)      | admin/tasks#show    | 
|                      | PATCH(PUT) | /admin/tasks/:id(.:format)      | admin/tasks#update  | 
|                      | DELETE     | /admin/tasks/:id(.:format)      | admin/tasks#destroy | 

**上を設定する場合のルーティングの記法**

```ruby:config/routes.rb

Rails.application.routes.draw do
  namespace :admin do
    resources :tasks
  end
end

```

また、以下のようにすると、できるpathは上と同様にadmin配下（`admin/~~`）となりますが、コントローラーはadmin配下ではなく、`app/controllers/tasks_contraller.rb`を参照します

**上を設定する場合のルーティングの記法**

```ruby:config/routes.rb

Rails.application.routes.draw do
  namespace :admin do
    resources :tasks
  end
end

```

### ネストしたルーティング設定
ネストは1回までが原則。

(例)1対多でtaskにcommentsが紐づいている場合

| Helper                 | HTTP Verb  | Path                                        | Controller#Action | 
| ---------------------- | ---------- | ------------------------------------------- | ----------------- | 
| task_comments_path     | GET        | /tasks/:task_id/comments(.:format)          | comments#index    | 
|                        | POST       | /tasks/:task_id/comments(.:format)          | comments#create   | 
| new_task_comment_path  | GET        | /tasks/:task_id/comments/new(.:format)      | comments#new      | 
| edit_task_comment_path | GET        | /tasks/:task_id/comments/:id/edit(.:format) | comments#edit     | 
| task_comment_path      | GET        | /tasks/:task_id/comments/:id(.:format)	     | comments#show     | 
|                        | PATCH(PUT) | /tasks/:task_id/comments/:id(.:format)      | comments#update   | 
|                        | DELETE     | /tasks/:task_id/comments/:id(.:format)<br>  | comments#destroy  | 

**上を設定する場合のルーティングの記法**

```ruby:config/routes.rb

Rails.application.routes.draw do
  resources :tasks do
    resources :comments
  end
end

```


### ネストを浅くするshallowオプションを使ったルーティング設定
`edit`, `show`, `update`, `destroy`アクションのpathが浅くなります。

| Helper                 | HTTP Verb  | Path                                   | Controller#Action | 
| ---------------------- | ---------- | -------------------------------------- | ----------------- | 
| task_comments_path     | GET        | /tasks/:task_id/comments(.:format)     | comments#index    | 
|                        | POST       | /tasks/:task_id/comments(.:format)     | comments#create   | 
| new_task_comment_path  | GET        | /tasks/:task_id/comments/new(.:format) | comments#new      | 
| edit_task_comment_path | GET        | /comments/:id/edit(.:format)           | comments#edit     | 
| task_comment_path      | GET        | /comments/:id(.:format)                | comments#show     | 
|                        | PATCH(PUT) | /comments/:id(.:format)                | comments#update   | 
|                        | DELETE     | /comments/:id(.:format)<br>            | comments#destroy  | 

**上を設定する場合のルーティングの記法**

```ruby:config/routes.rb

Rails.application.routes.draw do
  shallow do
    resources :tasks do
      resources :comments
    end
  end
end

```

### `scope`メソッドに`:shallow_path`オプションを使ったルーティング設定
指定されたパラメータをメンバーのパスの冒頭に追加します。

| Helper                | HTTP Verb  | Path                                     | Controller#Action | 
| --------------------- | ---------- | ---------------------------------------- | ----------------- | 
| task_comments_path    | GET        | /tasks/:task_id/comments(.:format)       | comments#index    | 
|                       | POST       | /tasks/:task_id/comments(.:format)       | comments#create   | 
| new_task_comment_path | GET        | /tasks/:task_id/comments/new(.:format)   | comments#new      | 
| edit_comment_path     | GET        | /corporation/comments/:id/edit(.:format) | comments#edit     | 
| comment_path          | GET        | /corporation/comments/:id(.:format)      | comments#show     | 
|                       | PATCH(PUT) | /corporation/comments/:id(.:format)      | comments#update   | 
|                       | DELETE     | /corporation/comments/:id(.:format)      | comments#destroy  | 

**上を設定する場合のルーティングの記法**

```ruby:config/routes.rb

Rails.application.routes.draw do
  scope shallow_path: "corporation" do
    resources :tasks do
      resources :comments, shallow: true
    end
  end
end

```

### `scope`メソッドに`:shallow_prefix`オプションを使ったルーティング設定
指定されたパラメータを（パスではなく）名前付きルーティングヘルパー名の冒頭に追加します。

| Helper                        | HTTP Verb  | Path                                   | Controller#Action | 
| ----------------------------- | ---------- | -------------------------------------- | ----------------- | 
| task_comments_path            | GET        | /tasks/:task_id/comments(.:format)	    | comments#index    | 
|                               | POST       | /tasks/:task_id/comments(.:format)     | comments#create   | 
| new_task_comment_path         | GET        | /tasks/:task_id/comments/new(.:format) | comments#new      | 
| edit_corporation_comment_path | GET        | /comments/:id/edit(.:format)           | comments#edit     | 
| corporation_comment_path      | GET        | /comments/:id(.:format)                | comments#show     | 
|                               | PATCH(PUT) | /comments/:id(.:format)                | comments#update   | 
|                               | DELETE     | /comments/:id(.:format)                | comments#destroy  | 

**上を設定する場合のルーティングの記法**

```ruby:config/routes.rb

Rails.application.routes.draw do
  scope shallow_prefix: "corporation" do
    resources :tasks do
      resources :comments, shallow: true
    end
  end
end

```

### メンバールーティングを使ったルーティング設定
デフォルトの7つのアクションに加えて、別のRESTfulなアクションを追加したい場合に使用します。メンバールーティングは`:id`を伴うルーティングに対して、アクションを追加したい場合に用います。


| Helper                       | HTTP Verb  | Path                                    | Controller#Action        | 
| ---------------------------- | ---------- | --------------------------------------- | ------------------------ | 
| reset_password_password_path | GET        | /passwords/:id/reset_password(.:format) | passwords#reset_password | 
| assign_password_path         | GET        | /passwords/:id/assign(.:format)         | passwords#assign         | 
| unassign_password_path       | GET        | /passwords/:id/unassign(.:format)       | passwords#unassign       | 
| passwords_path               | GET        | /passwords(.:format)                    | passwords#index          | 
|                              | POST       | /passwords(.:format)                    | passwords#create         | 
|                              | GET        | /passwords/new(.:format)                | passwords#new            | 
|                              | GET        | /passwords/:id/edit(.:format)           | passwords#edit           | 
|                              | GET        | /passwords/:id(.:format)                | passwords#show           | 
|                              | PATCH(PUT) | /passwords/:id(.:format)                | passwords#update         | 
|                              | DELETE     | /passwords/:id(.:format)                | passwords#destroy        | 

**上を設定する場合のルーティングの記法**

```ruby:config/routes.rb

Rails.application.routes.draw do
  resources :passwords do
    member do
      post 'csv_import'
    end
  end
end

```



### コレクションルーティングを使ったルーティング設定
メンバールーティング同様、デフォルトの7つのアクションに加えて、別のRESTfulなアクションを追加したい場合に使用しますが、`:id`を伴わないルーティングに対して、アクションを追加したい場合に用います。

| Helper                    | HTTP Verb  | Path                            | Controller#Action    | 
| ------------------------- | ---------- | ------------------------------- | -------------------- | 
| csv_import_passwords_path | POST       | /passwords/csv_import(.:format) | passwords#csv_import | 
| passwords_path            | GET        | /passwords(.:format)            | passwords#index      | 
|                           | POST       | /passwords(.:format)            | passwords#create     | 
|                           | GET        | /passwords/new(.:format)        | passwords#new        | 
|                           | GET        | /passwords/:id/edit(.:format)   | passwords#edit       | 
|                           | GET        | /passwords/:id(.:format)        | passwords#show       | 
|                           | PATCH(PUT) | /passwords/:id(.:format)        | passwords#update     | 
|                           | DELETE     | /passwords/:id(.:format)        | passwords#destroy    | 

**上を設定する場合のルーティングの記法**

```ruby:config/routes.rb

Rails.application.routes.draw do
  resources :passwords do
    member do
      post 'csv_import'
    end
  end
end

```

### 名前付きルーティング設定
`:as`オプションを使うと、任意のルーティングに名前を指定できます。

【asつけない場合】
Helperが生成されません

| Helper | HTTP Verb  | Path                                         | Controller#Action     | 
| ------ | ---------- | -------------------------------------------- | --------------------- | 
|        | GET        | /stations/:station_id/access_guide(.:format) | stations#access_guide | 

**上を設定する場合のルーティングの記法**

```ruby:config/routes.rb

Rails.application.routes.draw do
    get 'stations/:station_id/access_guide', to: 'stations#access_guide'
end

```

【asつけた場合】

Helperが生成されます。
| Helper                    | HTTP Verb  | Path                                         | Controller#Action     | 
| ------------------------- | ---------- | -------------------------------------------- | --------------------- | 
| station_access_guide_path | GET        | /stations/:station_id/access_guide(.:format) | stations#access_guide | 

**上を設定する場合のルーティングの記法**

```ruby:config/routes.rb
Rails.application.routes.draw do
  get 'stations/:station_id/access_guide', to: 'stations#access_guide', as: 'station_access_guide'
end
```
### 名前付きルーティングパラメータのオーバーライド
Railsガイドの説明が分かりやすいのでそのまま引用します。
> `:param`オプションは、デフォルトのリソース識別子`:id`(ルーティングの生成に使われる動的なセグメントの名前) をオーバーライドします。`params[<:パラメータ>]`を使って、コントローラからそのセグメントにアクセスできます。

【`param: 'token'`があるとき】

| Helper            | HTTP Verb  | Path                           | Controller#Action   | 
| ----------------- | ---------- | ------------------------------ | ------------------- | 
| reservations_path | POST       | /reservations(.:format)        | reservations#create | 
| reservation_path  | PATCH(PUT) | /reservations/:token(.:format) | reservations#update | 

**上を設定する場合のルーティングの記法**

```ruby:config/routes.rb
Rails.application.routes.draw do
  resources :reservations, only: [:create, :update], param: 'token'
end

```

【`param: 'token'`がないとき】

| Helper            | HTTP Verb  | Path                        | Controller#Action   | 
| ----------------- | ---------- | --------------------------- | ------------------- | 
| reservations_path | POST       | /reservations(.:format)     | reservations#create | 
| reservation_path  | PATCH(PUT) | /reservations/:id(.:format) | reservations#update | 

**上を設定する場合のルーティングの記法**

```ruby:config/routes.rb
Rails.application.routes.draw do
  resources :reservations, only: [:create, :update]
end

```


## ルーティングは探索順に注意!!

これまでの内容をふまえて、例えば以下のようなルーティングを考えてみます。

```ruby:config/routes.rb
Rails.application.routes.draw do
  resources :reservations, only: [:create, :edit, :update], param: 'token' do
    member do
      get '/:reservation_no', to: 'reservations#guest_page', as: 'guest_page' 
    end
end
```

一見良さそうにみえるのですが、生成されるパスは以下の順番になります。

| Helper                      | HTTP Verb  | Path                                           | Controller#Action       | 
| --------------------------- | ---------- | ---------------------------------------------- | ----------------------- | 
| guest_page_reservation_path | GET        | /reservations/:token/:reservation_no(.:format) | reservations#guest_page | 
| reservations_path           | POST       | /reservations(.:format)                        | reservations#create     | 
| edit_reservation_path       | GET        | /reservations/:token/edit(.:format)            | reservations#edit       | 
| reservation_path            | PATCH(PUT) | /reservations/:token(.:format)                 | reservations#update     | 

実はこの書き方では`edit`アクションのpathにアクセスすると、先に`guest_page`アクションのパスがヒットしてしまい、エラーが発生します。
これは`guest_page`アクションのパスが`:reservation_no`という動的なセグメントを含んでおり、　　Railsがルーティングを探索する際に、このパスが`edit`アクションのパスよりも先にヒットしてしまうためです。
なので、例えば以下のように修正します。

```ruby:config/routes.rb
Rails.application.routes.draw do
  get '/reservations/:token/edit', to: 'reservations#edit', as: 'edit_reservation'
  resources :reservations, only: [:create, :update], param: 'token' do
    member do
      get '/:reservation_no', to: 'reservations#guest_page', as: 'guest_page' 
    end
  end
end
```

そうすると生成されるパスは次のようになり、`edit`アクションのパスが一番上に生成されるようになります。
Railsは上から順番にパスを検索するため、こうすることで正しいパスを認識するようになります。
（もっと上手い書き方があるかもしれません...）

| Helper                      | HTTP Verb  | Path                                           | Controller#Action       | 
| --------------------------- | ---------- | ---------------------------------------------- | ----------------------- | 
| edit_reservation_path       | GET        | /reservations/:token/edit(.:format)            | reservations#edit       | 
| guest_page_reservation_path | GET        | /reservations/:token/:reservation_no(.:format) | reservations#guest_page | 
| reservations_path           | POST       | /reservations(.:format)                        | reservations#create     | 
| reservation_path            | PATCH(PUT) | /reservations/:token(.:format)                 | reservations#update     | 

resourcesで書いたルーティングが一番先に生成されるのかと思いきや、ブロックの中身のルーティングが先にできて、その後ろになるのは個人的に間違えやすいポイントでした。
（中のブロックを先に評価しているからかな?）


## まとめ
- よく使うルーティング記法と、その例の出力結果をまとめました
- 動的なセグメント(`:id`みたいなやつ)を使う時は、他のルーティングに影響しないかをチェックする


## その他、参考記事
[Railsガイド](https://railsguides.jp/routing.html#rails%E3%83%AB%E3%83%BC%E3%82%BF%E3%83%BC%E3%81%AE%E7%9B%AE%E7%9A%84)