【フリマアプリ】商品詳細ページのコメント機能（第３回）〜コントローラー編〜

　某スクールにおいて、チーム開発で、フリーマーケットアプリを作成中であり、使用した技術について公開しています。
※初学者のため、ミスや認識違いが多々あると思いますがご了承ください。

# 商品詳細ページにコメント機能を実装しました。
全部で7回に分けて記事を投稿しています。

    |内容             |                  url   |
|:-----------------|------------------:|:-----------------------------:|
| 第１回             |       モデル、マイグレーション編 |    https://qiita.com/sho_U/items/03108801146e65d58413   |
| 第２回         |           ルーティング編 |     https://qiita.com/sho_U/items/5c829b3060be2cce919a |
| 第３回|コントローラー編|https://qiita.com/sho_U/items/8528f336cf0d470cd719|
|第４.1回|ヴュー編(一覧表示）|https://qiita.com/sho_U/items/6190562270c722956547|
|第４.2回|ヴュー編(インプットフォーム）|https://qiita.com/sho_U/items/67c2ede4fc6c605283e2|
|第５回|jquery編|https://qiita.com/sho_U/items/d72b60114f76380d05f6|
|第6回|ajax編|https://qiita.com/sho_U/items/caed9b1471e63d43dd3a|

〜コード全文〜https://qiita.com/sho_U/items/310ac5b653bdfcb99a2c


#コントローラーについて

##「itemsコントローラー」の「showアクション」

まず、本実装をするにあたり初めに考えなければならないのは、**「itemsコントローラー」**の**「showアクション」**となります。

itemsコントローラーのshowアクションは商品詳細ページを表示するためのアクションです。
そして、商品詳細ページのヴューが下記になります。
<img width="469" alt="あいぼぼ.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/c49133b7-07dc-8706-e020-e793f2fdf1dc.png">
<img width="477" alt="コメント２.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/1b04c92b-0cd5-012f-2708-217d7abc41f5.png">

ご覧の通り、商品詳細ページは**「コメント一覧表示ページ」**と**「コメント新規作成ページ」**を兼ねているんですね。

よって、
itemsコントローラーのshowアクションは、**「該当する商品の情報（商品名や値段など）」**の他に、**「商品に紐づく全コメント」**と**「空のコメント」（form_withに渡すため）**を、**遷移するビュー**（"items/show.html.haml）に、送らないといけません。

```ruby:items_controller.rb
  def show
   #(@itemの情報に関するところは省略）
    @comment = Comment.new
    @commentALL = Comment.where(item_id:params[:id])
  end
```
解説します。
ビューから、itemsコントローラーのshowアクションへ遷移する際には、params[:id]には、詳細表示する商品(item)のidが格納されています。

```ruby
  @commentALL = Comment.where(item_id:params[:id])
```
.whereメソッドを使いcommentsテーブルから、商品に紐づくコメント（item_idカラムに当該商品のidが格納されてるもの）を全て取得しています。
ちなみに、この時発行されるSQLは

```
SELECT `comments`.* FROM `comments` WHERE `comments`.`item_id` = 1
```
となります。（商品のidが１の時）

このままでも機能はするのですが、第１回の〜モデル、マイグレーション編〜で、せっかく**Commnet(コメント)**と**Item(商品)**はアソシエーションを組んでいるので、
（第１回〜モデル、マイグレーション編〜https://qiita.com/drafts/03108801146e65d58413/edit　）

```ruby
 @commentALL = @item.comments
```
にしました。

結果、itemsコントローラーのshowアクションは

```ruby:items_controller.rb
  def show
   #(@itemの情報に関するところは省略）
    @comment = Comment.new
    @commentALL = @item.comments
  end
```
となりました。

##「commentsコントローラー」の「createアクション」
続いては、コメントを作成するためのアクションを定義していきます。

```ruby:comments_controller.rb
  def create
    @comment = Comment.new(comment_params)
    @seller_of_item = User.find(@comment.item.seller_id)
    @comment.save
    redirect_to item_path(@comment.item.id)
  end

private
  def comment_params
    params.require(:comment).permit(:comment,:item_id).merge(user_id: current_user.id)
  end
  # 非同期通信を行うため、後に記載を変更します。
```

解説します。

```ruby
 @comment = Comment.new(comment_params)
```
**form_with**を入力フォームに使用しているため、**newメソッド**の引数で、**プライベートメソッド**である**comment_params**を呼び出して
返り値から**commnet**を作成し、**@comment**に格納しています。

ちなみに、**comment_params**では、不正を防ぐために、ヴューから送られた情報のうち**許可されたキー（:comment,:item_id）**の値と、**ログインユーザーのid**を**mergeメソッド**で結合して、呼び出し元に返しています。
念のため、ターミナルで中身を確認すると以下のようになっています。ハッシュの中に許可されたキーの値と、マージされた内容が結合されているのがわかります。

```:ターミナル
 pry(#<CommentsController>)> comment_params
=> <ActionController::Parameters {"comment"=>"値下げはできません", "item_id"=>"1", "user_id"=>6} permitted: true>
```
話を戻しまして、

```ruby:comments_controller.rb
 @seller_of_item = User.find(@comment.item.seller_id)
```
この記述で、**@seller_of_item**に、コメントした商品の出品者を格納しています。これは、非同期通信で後ほど使用するためです。

これも、アソシエーションを組んでいるので、

```ruby:comments_controller.rb
@seller_of_item = @comment.item.seller
```
記載できます。

```ruby:comments_controller.rb
@comment.save
redirect_to item_path(@comment.item.id)
```

@commentをsaveして、商品詳細ページへリダイレクトします。
※非同期通信を実装するため後ほどリダイレクトの部分は修正します。

ちなみに、　item_path　の部分は、pathを呼び出すメソッド化したもので**prifix**といいます。
<img width="1272" alt="9556b9a663154a715c64ee64993c4ac4.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/2cda8345-36bf-222d-6fd0-e6b1ec56c0be.png">
<img width="1273" alt="1c2406499e69ecd60c01538a5c6d681f.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/0b336b4f-0d63-bec1-e1fd-718b4fdebde4.png">

つまり、　item_path(@comment.item.id)　は、　"/items/#{@comment.item.id}"　と記載が違うだけで同じitemsコントローラーのshowアクションに遷移します。@commentには、今作成したコメントが格納されているため、@comment.item.idの返りちは、コメントした商品のidとなり、結果として（上記のitemコントローラーのshowアクション）元の商品詳細ページに戻るということですね。その際には、今コメントした内容が、追加されています。

話を戻しまして、結果、commentsコントローラーのcreateアクションは

```ruby:comments_controller.rb
  def create
    @comment = Comment.new(comment_params)
    @seller_of_item = @comment.item.seller
    @comment.save
    redirect_to item_path(@comment.item.id)
  end

private
  def comment_params
    params.require(:comment).permit(:comment,:item_id).merge(user_id: current_user.id)
  end
```  
となりました。

##「commentsコントローラー」の「updateアクション」
続いては、コメントを**仮削除**するためのアクションを定義していきます。

```ruby:comments_controller.rb
  def update
    @comment = Comment.find(params[:id])
    @comment.update(delete_check:1)
    redirect_to item_path(@comment.item.id)
  end
```

解説します。

```ruby
@comment = Comment.find(params[:id])
```
まずは、ルーティング編で説明したとおり、**仮削除したいコメント**を、@commentに格納します。

```ruby
@comment.update(delete_check:1)
```
第一回編で説明のとおり
commentはcreateした時、デフォルト値としてdelete_checkカラム には　"**0**"　が格納されます。
これを、**"1"**　に更新します。これにより、当該commentレコードは**「仮削除状態」**となります。
仮削除状態というと何か小難しい特殊な処理が施された状態と感じるかと思いますが、全然そんなことなく
ビューに遷移したとき、

```ruby
 if comment.delete_check == 1  
```
で条件分岐し、**true**の場合(commentのdelete_checkカラムに**1**が格納されている場合）は

<img width="664" alt="7ad282c99502ebd8a2452146c510f205.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/ff727f3e-1c71-d0bc-412b-fdb8e44d5c28.png">

を表示する。とういだけです。
この辺りの詳細は、ビュー編で解説します。

##「commentsコントローラー」の「restoreアクション」
続いては、**仮削除状態**にあるコメントを**復元**するためのアクションを定義していきます。

```ruby:comments_controller.rb
  def restore
    @comment = Comment.find(params[:id])
    @comment.update(delete_check:0)
    redirect_to item_path(@comment.item.id)
  end
```
upadetアクションと、考え方は一緒です。**仮削除状態を解除**するために、**delete_check**を**"0"**　に戻すだけですね。

##「commentsコントローラー」の「destroyアクション」
続いては、**仮削除状態**にあるコメントを**データーベースから完全削除**するためのアクションを定義していきます。

```ruby:comments_controller.rb
  def destroy
    @comment = Comment.find(params[:id])
    @comment.destroy
    redirect_to item_path(@comment.item.id)
  end
```
destroyメソッドで、@commentを削除するだけですね。


##リファクタリング
これで、必要なコントローラーを定義でしました。これで機能は実装できましたが、update,restore,destroyアクションには、1行目に共通して、　

```
@comment = Comment.find(params[:id])
```
が記載されています。これを共通化してまとめて、各々のアクションが実行される際事前に実行されるようにします。

```ruby:comments_controller.rb
   before_action :set_comment, only: [:update,:destroy,:restore]
```
コントーラーの先頭に、このように記載すると、update,destroy,restoreアクションが実行される際、事前に　**set_comment**　メソッドが呼び出されます。

```ruby:comments_controller.rb
private
  def set_comment
    @comment = Comment.find(params[:id])
  en
end
```
privateメソッドに上記のようにset_commnetメソッドを定義します。これで共通した部分をまとめることができました。各メソッドから不要な記載となった　@comment = Comment.find(params[:id])　を削除します。
(リダイレクトの部分も共通していますが、非同期通信を実装する際に削除するのでこのままにしておきます。)

次に、現状、出品者以外のユーザーが直接URLを打ち込んだ場合、コメントの削除等が実行されてしまう恐れがあります。
そこで、 

```ruby
before_action :check_user, only: [:update,:destroy,:restore]
```
と定義し、(createアクションにつては、出品者以外でもオッケー）

```ruby
private
 def check_user
   unless @comment.item.seller == current_user
     flash[:alert] = "その操作はできません"
     redirect_to root_path
   end
 end
```
上記のように、check_userメソッドを定義します。
これで、ログインユーザーが出品者と異なる場合、その商品のコメントは削除等の操作はできないようにしています。

以上でコントローラーが定義できました。参考に全文記載しておきます。

```ruby:items_controller.rb
  def show
   #(@itemの情報に関するところは省略）
    @comment = Comment.new
    @commentALL = @item.comments
  end
```

```ruby:comments_controller.rb
class CommentsController < ApplicationController
  
  before_action :set_comment, only: [:update,:destroy,:restore]
  before_action :check_user, only: [:update,:destroy,:restore]

  def create
    @comment = Comment.new(comment_params)
    @seller_of_item = User.find(@comment.item.seller_id)
    @comment.save
    redirect_to item_path(@comment.item.id)
  end

  def update
    @comment.update(delete_check:1)
    redirect_to item_path(@comment.item.id)
  end

  def destroy
    @comment.destroy
    redirect_to item_path(@comment.item.id)
  end

  def restore
    @comment.update(delete_check:0)
    redirect_to item_path(@comment.item.id)
  end

private
  def comment_params
    params.require(:comment).permit(:comment,:item_id).merge(user_id: current_user.id)
  end

  def set_comment
    @comment = Comment.find(params[:id])
  end

  def check_user
    unless @comment.item.seller == current_user
      flash[:alert] = "その操作はできません"
      redirect_to root_path
    end
  end
end
```

### 次回（第4.1回）はヴュー(一覧表示）編となります。
https://qiita.com/sho_U/items/6190562270c722956547