【Rails】コントローラの異なるページ上で投稿/削除するしくみをつくる

# 目標物の確認
今回の目標物は以下のようです。
![Qiita用.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2688241/b50ddb43-49ac-d72b-7ce6-63204a15a128.jpeg)
ユーザーAのポストに対し、ユーザーBがコメントを投稿します。
コメントの投稿フォームはposts/show内にあります。
コメントはposts/show内で表示され、自分のコメントに対しては削除することが可能です。

# 分からなかったこと
ポスト表示ページのパスが`posts/:id`であり、ページが持つ固有の`params`要素は`id`のみであるならば、、、

+ コメントを作成する際はCommentモデルの`post_id`カラムに`params[:id]`を代入すれば良いのは分かる。
+ コメントを削除する時、該当するコメントの`id`を`find_by`を使って探すことができない。
（`params`が持つ`id`はPostモデルの`id`であり、Commentモデルの`id`ではないから。）

# 解決策

+ モデル同士を紐づけて
+ ルーティングをネストして
+ コントローラを設計し
+ ビューファイルの`form_with`と`link_to`にひと手間加える


## モデルの紐づけ
PostモデルとCommentモデルに主従関係を持たせます。

`Post`: 大元の投稿
```ruby:post.rb
has_many :comments
```

`Comment`: postへのコメント
```ruby:comment.rb
belongs_to :post
```

## ルーティングのネスト
今回のケースのように、あるコントローラ内で別のコントローラを動かすときは、**ルーティングをネスト**させます。
```ruby:route.rb
resources :posts do
  # 今回使用するコメント機能は投稿と削除だけ
  resources :comments, only: [:create, :destroy]
end
```
これにより、どのようなパスが構成されるのか確認しましょう。
```
$ rails routes
```
の結果を見ると
```
posts          GET     /posts(.:format)                         posts#index
post_comments  POST    /posts/:post_id/comments(.:format)       comments#create
post_comment   DELETE  /posts/:post_id/comments/:id(.:format)   comments#destroy
```

ここで`POST`と`DELETE`のHTTPリクエストに対して`:post_id`と`:id`があることに注目します。
`post_id`はCommentモデルがもつカラム
`id`はPostモデルがもつカラム
です。
コントローラを設計する際、`params`に何を入れて情報を送るのかを考える際に必要になりますので意識しましょう。

## コントローラの設計
### コメントの投稿
コメントを作成するアクションはこちらです。
```ruby:comments_controller
def create
  # 紐づけ先の@postを定義
  @post = Post.find(params[:post_id])
  # @commentは@postに紐づけるのでnewではなくbuild
  @comment = @post.comments.build(comment_params)
  @comment.save
  # リダイレクト先は@commentに紐づいたpost/:id
  redirect_to post_path(@comment.post_id)
end

private
  # comment_paramsはストロングパラメータ
  def comment_params
    params.requier(:comment).permit(:content, :post_id)
    # 必要であればmerge内にuser_id: @current_user.idも追加
  end
```

### コメントの削除
コメントを削除するアクションは以下です
```ruby:comments_controller.rb
def destroy
  # @commentは:idパラメータから探す
  @comment = Comment.find(params[:id])
  @comment.destroy
  # リダイレクト先は、ひとつ前のページ又はposts/index
  redirect_to request.referrer || posts_path
end
```

## ビューファイル
### コメントの投稿
```erb:posts/show.html.erb
<%= form_with model: [@post, @comment], local: true do |f| %>0
  <%= f.text_area :content, required: true %>
  <%= f.submit "送信" %>
<% end %>
```
コメントを投稿する際は、`form_with`のmodel要素にコントローラのアクションで定義したインスタンス変数を配列として与えます。
これによってRailsは`post_comments_path`という`create`メソッドのパスにパラメータを送信します。

### コメントの削除
```erb:posts/show.html.erb
# コメントしたユーザーが自分かどうかを確認
<% if @comment.user.id == @current_user.id %>
  # post_comment_pathには2つのidがある
  <%= link_to("削除", post_comment_path(@comment.post_id, @comment.id),
    {method: "delete", data: {confirm: "削除しますか？"}})
  %>
<% end %>
```
コメントを削除するパスは、`rails routes`で確認した`post_comment_path`を使用します。
このパスは上で確認した通り`/posts/:post_id/comments/:id`であり、2つのidをもっています。
`:post_id`には`@comment.post_id`
`:id`には`@comment.id`
を与えます。

また、削除パスのメソッドは"delete"なので、これも明記する必要があります。
ついでにリンクをクリックするといきなり削除されてしまうのを防ぐため、JavaScriptで削除の確認コメントが出るように`data`要素も追加しました。

以上で、コントローラの異なるページ上で投稿/削除するしくみをつくることができました。

# 参考

[Railsチュートリアル第13章](https://railstutorial.jp/chapters/user_microposts?version=5.1#sec-manipulating_microposts)

[【Rails基礎】プログラミング初学者がつまずきやすい「ルーティングのネスト」について簡単に解説](https://techtechmedia.com/routing-nest-rails/)

[【Rails】 form_withの使い方を徹底解説！#ネストをしている時の書き方](https://pikawaka.com/rails/form_with#%E3%83%8D%E3%82%B9%E3%83%88%E3%82%92%E3%81%97%E3%81%A6%E3%81%84%E3%82%8B%E6%99%82%E3%81%AE%E6%9B%B8%E3%81%8D%E6%96%B9)

[Rails link_toにパラメータを渡す](https://qiita.com/k_fence/items/c51bfdce8ce7b32515a3)

[【Rails】link_to でPrefix pathへの複数idの渡し方](https://qiita.com/koki_73/items/539972a859082fdee2af)