【Rails】Routingのネスト,member, collectionの違い

###はじめに
Railsでアプリケーション作成しております。
作成中にルーティングについて少々学習したので、自分用のメモとして残しておきます。

###ルーティングについて
ルーティングは、受け取ったHTTPリクエストに応じて、特定のコントローラー内のアクションを動作するように割り当てを行なっている。
Railsのルーティングは`config/routes.rb`で設定する。

```ruby:config/routes.rb
Rails.application.routes.draw do
  # ここにルーティングを設定する
  root 'home#index'
end

```

設定したルーティングは、以下のコマンドを実行することで確認できます。

```:ターミナル
$ bin/rails routes

Prefix   Verb   URI Pattern   Controller#Action
  root   GET    /             home#index
```
###実装したかったこと(前提)
ユーザー(user)が、ある施設(house)に口コミ(review)を投稿する機能を実装します。
施設は複数あり、1つの施設に複数の口コミが投稿されると想定する。
各々の投稿のURLを規則的に振り当てるとすると、`https://○○○/house/id/review/id`というふうにしたい。
今回の各モデルのアソシエーションは以下のようにしている。
Userの認証モデルについてはdeviseにて実装済みである。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1382306/dfb85df9-d9e6-68a9-fe35-ac5a6097e60a.png)


###結論
ルーティングをネストするといい！

```ruby:config/routes.rb
Rails.application.routes.draw do
  root 'home#index'
  devise_for :users
  # ネストしたルーティング
  resources :houses do
    resources :reviews
  end
end
```

ルーティングを確認する際は、`-g`や`-c`オプションを用いると見やすくなります。

```:ターミナル
# -g :名前付きルーティングやHTTP動詞、URLパスのいずれかに部分マッチするルーティングのみ表示する
$ bin/rails routes -g reviews

           Prefix Verb   URI Pattern                                  Controller#Action
    house_reviews GET    /houses/:house_id/reviews(.:format)          reviews#index
                  POST   /houses/:house_id/reviews(.:format)          reviews#create
 new_house_review GET    /houses/:house_id/reviews/new(.:format)      reviews#new
edit_house_review GET    /houses/:house_id/reviews/:id/edit(.:format) reviews#edit
     house_review GET    /houses/:house_id/reviews/:id(.:format)      reviews#show
                  PATCH  /houses/:house_id/reviews/:id(.:format)      reviews#update
                  PUT    /houses/:house_id/reviews/:id(.:format)      reviews#update
                  DELETE /houses/:house_id/reviews/:id(.:format)      reviews#destroy

# -c :特定のコントローラーのルーティングのみ表示する
$ bin/rails routes -c reviews

           Prefix Verb   URI Pattern                                  Controller#Action
    house_reviews GET    /houses/:house_id/reviews(.:format)          reviews#index
                  POST   /houses/:house_id/reviews(.:format)          reviews#create
 new_house_review GET    /houses/:house_id/reviews/new(.:format)      reviews#new
edit_house_review GET    /houses/:house_id/reviews/:id/edit(.:format) reviews#edit
     house_review GET    /houses/:house_id/reviews/:id(.:format)      reviews#show
                  PATCH  /houses/:house_id/reviews/:id(.:format)      reviews#update
                  PUT    /houses/:house_id/reviews/:id(.:format)      reviews#update
                  DELETE /houses/:house_id/reviews/:id(.:format)      reviews#destroy
```

###memberやcollectionとの違いは？
ルーティングにREST以外のアクションを追加したい場合に、`member`や`collection`を使用します。
memberやcollectionを使う場合とどのような違いが出るのか、確認してみます。

####①memberを使う場合

```ruby:config/routes.rb
Rails.application.routes.draw do
  root 'home#index'
  devise_for :users

  resources :houses do
    member do
      resources :reviews
    end
  end
end
```

```:ターミナル
$ bin/rails routes -g reviews
     Prefix Verb   URI Pattern                            Controller#Action
    reviews GET    /houses/:id/reviews(.:format)          reviews#index
            POST   /houses/:id/reviews(.:format)          reviews#create
 new_review GET    /houses/:id/reviews/new(.:format)      reviews#new
edit_review GET    /houses/:id/reviews/:id/edit(.:format) reviews#edit
     review GET    /houses/:id/reviews/:id(.:format)      reviews#show
            PATCH  /houses/:id/reviews/:id(.:format)      reviews#update
            PUT    /houses/:id/reviews/:id(.:format)      reviews#update
            DELETE /houses/:id/reviews/:id(.:format)      reviews#destroy
```
一見これでも良さそうだが、houses後の`:id`とreview後の`:id`が同じになってしまう。
####②collectionを使う場合

```ruby:config/routes.rb
Rails.application.routes.draw do
  root 'home#index'
  devise_for :users

  resources :houses do
    collection do
      resources :reviews
    end
  end
end
```

```:ターミナル
$ bin/rails routes -g reviews
     Prefix Verb   URI Pattern                        Controller#Action
    reviews GET    /houses/reviews(.:format)          reviews#index
            POST   /houses/reviews(.:format)          reviews#create
 new_review GET    /houses/reviews/new(.:format)      reviews#new
edit_review GET    /houses/reviews/:id/edit(.:format) reviews#edit
     review GET    /houses/reviews/:id(.:format)      reviews#show
            PATCH  /houses/reviews/:id(.:format)      reviews#update
            PUT    /houses/reviews/:id(.:format)      reviews#update
            DELETE /houses/reviews/:id(.:format)      reviews#destroy
```
この場合、houses後に`:id`が付かないURLになってしまう。
以上のことから、今回のケースではネストした方が良いということがわかりました。

###コントローラーやビューでの振る舞い
ルーティングをネストすれば、houseとreviewのIDを別々に定義できることがわかりました。
それでは、それぞれのidを用いて、DBにデータを保存するにはどうすれば良いのか？
今回はform_withを用いた口コミ作成、編集フォームを例に考えます。
コントローラーでのインスタンスの定義や、ビューでの記述については以下のようにしました。

####コントローラーの記述
```ruby:app/controllers/reviews_controller.rb
class ReviewsController < ApplicationController

  def new
    @house = House.find(params[:house_id])
    @review = current_user.reviews.build
  end

  def create
    @review = current_user.reviews.build(review_create_params)
    @review.house_id = params[:house_id]
    if @review.save
      flash[:success] = "口コミを投稿しました！"
      redirect_to house_path(@review.house_id)
    else
      render 'new'
    end
  end

  def edit
    @house = House.find(params[:house_id])
    @review = Review.find(params[:id])
  end

  def update
    @review = Review.find(params[:id])
    if @review.update(review_update_params)
      flash[:success] = "口コミを修正しました！"
      redirect_to root_url
    else
      render 'edit'
    end
  end

  private

    def review_create_params
      params.require(:review).permit(:content, :user_id)
    end

    def review_update_params
      params.require(:review).permit(:content)
    end

end
```

口コミは、どの施設に対する口コミなのかということを定義する必要があります。
そのため、`@house`を上記のような方法で取得しています。

####ビューファイルの記述
```slim:app/views/reviews/new.html.slim
= form_with model: [@house, @review], local: true do |f|
  = f.text_field :content, placeholder: "口コミ本文を書いてください（10,000字以内）"
  = f.submit "口コミを投稿する"
```

```slim:app/views/reviews/edit.html.slim
= form_with model: [@house, @review], local: true do |f|
  = f.text_field :content
  = f.submit '口コミを修正する'
```

ビュー側では、`form_with`の引数に`[@house, @review]`のように配列を渡すように記述する必要があります。


以上になります。誤りなどありましたら、ご指摘お願いします。

###参考
[https://railsguides.jp/routing.html](https://railsguides.jp/routing.html)
[https://pikawaka.com/rails/form_with](https://pikawaka.com/rails/form_with)