基礎編12

2021.3.23 

 

掲示板詳細画面の追加/コメント機能の実装

 

コメント一覧、コメントフォームがうまくいかなかった。

アソシエーションが加わったので、modelの指定などに戸惑った。

 

<今回学んだこと>

board_path(comment.board)

board_path(ID値)で"boards/ID"のようなURLの文字列が返り値として取得できる。

ほんとは違うけど、board_path => "boards/"みたいな感じ

comment.board

このようにオブジェクト自体を引数に渡すと、そのID値でURLを生成してくれる。

 

board_path(comment.board_id)

このような形が想像つくけど毎回書くのは長いので! 

 

(views.comments/_form.html.erb)

<%= form_with model: comment, url: [board, comment], local: true do |f| %>

form_withの行き先をcommentモデルに固定する。urlを[board, comment]という形で指定するのはboardモデルとcommentモデルがアソシエーションしているためこのように書く必要がある。

 

・ロジックをmodelにまとめて記載することによって、メンテナンス時にここを変更すればいい。

・「自分の作成したリソースの判定はUserモデルにまとめて記載する」という方針にすると、各リソースに対してのロジックをUserモデルをみるだけで済むようになる。

ex)

(app/models/comment.rb)

commentモデルに記載した場合、selfレシーバを使用して実行対象のリソースのuserを取得して判断できる。

def is_mine?(current_user)
self.user == current_user
end
# if @comment.is_mine?(current_user) という形式でViewで記載

 

(app/models/user.rb)

Userモデルに記載した場合、selfレシーバからcurrent_userを取得できるため、引数にcpmmentを渡してuserを取得して判断

 
def my_comment?(comment)
self == comment.user
end
# if current_user.my_comment?(@comment) という形式でViewで記載

↓クラスメソッドとして定義すると

self.my_comment?(current_user, comment)
current_user == comment.user
end
# クラスメソッドとして定義すると、実行時に判定用のオブジェクトすべてを引数として渡す必要がある。
# if User.my_comment?(current_user, @comment)

クラスメソッドに定義すると実行時に判定用のオブジェクト全てを引数として渡す必要がある。

インスタンスメソッドとして定義すれば、判定メソッド実行時の引数に渡すオブジェクトが少なくて済む。= インスタンスメソッド内でselfレシーバを使うと実行対象のインスタンス変数の情報を参照できるため、メソッドに渡す引数を減らすことができる。

 

<流れ>

❶コメントのモデルを追加する

$ rails g model comment body:text user:references board:references

text カラム null: false(presence: true と同じ感じ) 設定する。

validatesを追加

$ rails db:migrate

 

❷Boardモデルにコメントとの関連を追加

(board.rb)掲示板とコメントが1対多の関係であることを追加

belongs_to :user
has_many :comments, dependent: :destroy

(user.rb)ユーザーとコメントが1対多の関係であることを追加

has_many :boards, dependent: :destroy
has_many :comments, dependent: :destroy

 

❸コメントのコントローラーを追加する

(routes.rb)コメントのrouteと掲示板詳細ページのrouteを追加する

resources :boards, only: %i[index new create show] do
resources :comments, only: %i[create], shallow: true
end

 

$ rails g controller comments

 

(comments_controller.rb)

class CommentsController < ApplicationController
def create
comment = current_user.comments.build(comment_params)
if comment.save
redirect_to board_path(comment.board), success: t('defaults.message.created', item: Comment.model_name.human)
else
redirect_to board_path(comment.board), danger: t('defaults.message.not_created', item: Comment.model_name.human)
end
end

private

def comment_params
params.require(:comment).permit(:body).merge(board_id: params[:board_id])
end
end

リダイレクトさせる場合はローカル変数を使う。

renderの時はインスタンス変数を取得する必要がある。

(理由)

render => ビューを描画するだけで、新たにhttpリクエストは送信しない。
redirect_to => 新たにhttpリクエストを送信して、その結果コントローラからビューが描画される。

 

コントローラー側で値を保持しない処理の場合はローカル変数を使う。

今回は、入力フォームの再表示とは違いコメントの作成、失敗後は紐づいている掲示板の詳細画面に移動して、そこで関連するコメントの一覧を読み込んで(パーシャルになってる奴のこと?)表示する仕様になっている。

 

掲示板詳細ページのコントローラを追加する

(board_controller.rb)

def show
@board = Board.find(params[:id])
@comment = Comment.new
@comments = @board.comments.includes(:user).order(created_at: :desc)
end

 

❺コメントのビュー作成

参照

【Rails6】link_toにFontAwesomeを埋め込む具体的方法【コピペOK】 - Qiita

・link_toにアイコンを埋め込む

<%= link_to URLヘルパー, class: "btn btn-danger", HTTPメソッドの指定 do %>
アイコンのコード #=> ここに挿入する
<% end %>

HTTPメソッドの指定: 削除ボタンなら「method: :delete」になる

ex)今回

<%= link_to '#', id: "button-delete-#{board.id}" do %>
<%= icon 'fas', 'trash' %>
<% end %>

(基本)

<%= linl_to "削除" , URLヘルパー ,class: btn btn-primary, method: :delete %>

 

comment/show.html.erbにあるコメントフォームにdef newのやつが入っててそこにprivatemで許可されたものだけが入っててそれを用いて、2つのモデルに関連するオブジェクトを作成saveして表示させている。