某スクールにおいて、チーム開発で、フリーマーケットアプリを作成中であり、使用した技術について公開しています。
※初学者のため、ミスや認識違いが多々あると思いますがご了承ください。
商品詳細ページにコメント機能を実装しました。
全部で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アクションは商品詳細ページを表示するためのアクションです。
そして、商品詳細ページのヴューが下記になります。
ご覧の通り、商品詳細ページは**「コメント一覧表示ページ」と「コメント新規作成ページ」**を兼ねているんですね。
よって、
itemsコントローラーのshowアクションは、**「該当する商品の情報(商品名や値段など)」の他に、「商品に紐づく全コメント」と「空のコメント」(form_withに渡すため)**を、遷移するビュー("items/show.html.haml)に、送らないといけません。
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アクションは
def show
#(@itemの情報に関するところは省略)
@comment = Comment.new
@commentALL = @item.comments
end
となりました。
##「commentsコントローラー」の「createアクション」
続いては、コメントを作成するためのアクションを定義していきます。
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)の値と、ログインユーザーのidをmergeメソッドで結合して、呼び出し元に返しています。
念のため、ターミナルで中身を確認すると以下のようになっています。ハッシュの中に許可されたキーの値と、マージされた内容が結合されているのがわかります。
pry(#<CommentsController>)> comment_params
=> <ActionController::Parameters {"comment"=>"値下げはできません", "item_id"=>"1", "user_id"=>6} permitted: true>
話を戻しまして、
@seller_of_item = User.find(@comment.item.seller_id)
この記述で、@seller_of_itemに、コメントした商品の出品者を格納しています。これは、非同期通信で後ほど使用するためです。
これも、アソシエーションを組んでいるので、
@seller_of_item = @comment.item.seller
記載できます。
@comment.save
redirect_to item_path(@comment.item.id)
@commentをsaveして、商品詳細ページへリダイレクトします。
※非同期通信を実装するため後ほどリダイレクトの部分は修正します。
ちなみに、 item_path の部分は、pathを呼び出すメソッド化したものでprifixといいます。
つまり、 item_path(@comment.item.id) は、 "/items/#{@comment.item.id}" と記載が違うだけで同じitemsコントローラーのshowアクションに遷移します。@commentには、今作成したコメントが格納されているため、@comment.item.idの返りちは、コメントした商品のidとなり、結果として(上記のitemコントローラーのshowアクション)元の商品詳細ページに戻るということですね。その際には、今コメントした内容が、追加されています。
話を戻しまして、結果、commentsコントローラーのcreateアクションは
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アクション」
続いては、コメントを仮削除するためのアクションを定義していきます。
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が格納されている場合)は
を表示する。とういだけです。
この辺りの詳細は、ビュー編で解説します。
##「commentsコントローラー」の「restoreアクション」
続いては、仮削除状態にあるコメントを復元するためのアクションを定義していきます。
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アクション」
続いては、仮削除状態にあるコメントをデーターベースから完全削除するためのアクションを定義していきます。
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])
が記載されています。これを共通化してまとめて、各々のアクションが実行される際事前に実行されるようにします。
before_action :set_comment, only: [:update,:destroy,:restore]
コントーラーの先頭に、このように記載すると、update,destroy,restoreアクションが実行される際、事前に set_comment メソッドが呼び出されます。
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メソッドを定義します。
これで、ログインユーザーが出品者と異なる場合、その商品のコメントは削除等の操作はできないようにしています。
以上でコントローラーが定義できました。参考に全文記載しておきます。
def show
#(@itemの情報に関するところは省略)
@comment = Comment.new
@commentALL = @item.comments
end
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回)はヴュー(一覧表示)編となります。