2
0

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-05-02

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

商品詳細ページにコメント機能を実装しました。

全部で7回に分けて記事を投稿しています。

内容 url
第1回 モデル、マイグレーション編 https://qiita.com/sho_U/items/03108801146e65d58413
第2回 ルーティング編 https://qiita.com/sho_U/items/5c829b3060be2cce919a
第3回 コントローラー編 https://qiita.com/sho_U/items/8528f336cf0d470cd719
第4.1回 ヴュー編(一覧表示) https://qiita.com/sho_U/items/6190562270c722956547
第4.2回 ヴュー編(インプットフォーム) https://qiita.com/sho_U/items/67c2ede4fc6c605283e2
第5回 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アクションは商品詳細ページを表示するためのアクションです。
そして、商品詳細ページのヴューが下記になります。
あいぼぼ.png
コメント2.png

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

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

items_controller.rb
  def show
   #(@itemの情報に関するところは省略)
    @comment = Comment.new
    @commentALL = Comment.where(item_id:params[:id])
  end

解説します。
ビューから、itemsコントローラーのshowアクションへ遷移する際には、params[:id]には、詳細表示する商品(item)のidが格納されています。

  @commentALL = Comment.where(item_id:params[:id])

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

SELECT `comments`.* FROM `comments` WHERE `comments`.`item_id` = 1

となります。(商品のidが1の時)

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

 @commentALL = @item.comments

にしました。

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

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

となりました。

「commentsコントローラー」の「createアクション」

続いては、コメントを作成するためのアクションを定義していきます。

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
  # 非同期通信を行うため、後に記載を変更します。

解説します。

 @comment = Comment.new(comment_params)

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

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

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

話を戻しまして、

comments_controller.rb
 @seller_of_item = User.find(@comment.item.seller_id)

この記述で、@seller_of_itemに、コメントした商品の出品者を格納しています。これは、非同期通信で後ほど使用するためです。

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

comments_controller.rb
@seller_of_item = @comment.item.seller

記載できます。

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

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

ちなみに、 item_path の部分は、pathを呼び出すメソッド化したものでprifixといいます。
9556b9a663154a715c64ee64993c4ac4.png
1c2406499e69ecd60c01538a5c6d681f.png

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

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

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アクション」

続いては、コメントを仮削除するためのアクションを定義していきます。

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

解説します。

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

まずは、ルーティング編で説明したとおり、仮削除したいコメントを、@commentに格納します。

@comment.update(delete_check:1)

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

 if comment.delete_check == 1  

で条件分岐し、trueの場合(commentのdelete_checkカラムに1が格納されている場合)は

7ad282c99502ebd8a2452146c510f205.png

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

「commentsコントローラー」の「restoreアクション」

続いては、仮削除状態にあるコメントを復元するためのアクションを定義していきます。

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アクション」

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

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])

が記載されています。これを共通化してまとめて、各々のアクションが実行される際事前に実行されるようにします。

comments_controller.rb
   before_action :set_comment, only: [:update,:destroy,:restore]

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

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

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

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

before_action :check_user, only: [:update,:destroy,:restore]

と定義し、(createアクションにつては、出品者以外でもオッケー)

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

上記のように、check_userメソッドを定義します。
これで、ログインユーザーが出品者と異なる場合、その商品のコメントは削除等の操作はできないようにしています。

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

items_controller.rb
  def show
   #(@itemの情報に関するところは省略)
    @comment = Comment.new
    @commentALL = @item.comments
  end
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回)はヴュー(一覧表示)編となります。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0