#はじめに
学んだことを備忘録的に記事にしてます。間違い等あればアドバイスいただけると喜びます。
###作るもの
今回は投稿がある前提で投稿に対して自由にコメントできる機能を実装します。(題材は自分のポートフォリオサイトです。尚、すでにUserテーブルとMicropostテーブルは作成済みです。)
###対象読者
コメント機能を追加したい方。
#作成の流れ
コメント機能追加までの流れです。
1.Commentモデルの作成、変更
2.モデルの関連付け、バリデーションの設定
3.Commentsコントローラーの作成、ルーディングの設定
4.対応するビューの作成
5.コントローラのアクションの作成
#1.Commentsモデルの作成
まずデータベースとやりとりを行う、Commentモデルの作成を行います。
カラム | 型 |
---|---|
id | integer |
user_id | integer |
micropost_id | integer |
content | text |
誰がコメントしたかわかるようにuser_idはUserモデルと関連付けを行い、同じくmicropost_idもどのマイクロポストにコメントされたかわかるようにMicropostモデルと関連付けをこないます。
それではモデルを作成します。
rails g model comment user:references micropost:references content:text
class CreateComments < ActiveRecord::Migration[5.1]
def change
create_table :comments do |t|
t.references :user, foreign_key: true
t.references :micropost, foreign_key: true
t.text :content, null: false
t.timestamps
end
end
end
モデルを作成する際にreferencesとすることでindexと外部キー制約(foregin_key: true)が自動で追加されます。indexをuser_idとmicropost_idに追加することによってそれぞれに関連したコメントを探す際にデータを高速に調べられるようになります。また、外部キー制約がつくことによってuser_id(micropost_id)にはUserテーブルに存在するidのみデータベースレベルで保存するようになります。
また、コメントが空だとコメント機能の意味をなさないためnull:falseを追加します。
それではテーブルを作成します。
rails:db:migrate
#2.モデルの関連付け、バリデーションの設定
作成されたモデルはこんな感じです。
class Comment < ApplicationRecord
belongs_to :user
belongs_to :micropost
validates :content, presence: true, length: { maximum: 140 }
end
自動でbelongs_toで一対一の関連付けができています。User,Micropostモデルにはそれぞれ手動で追加する必要があるので追加していきます。また、バリテーションも追加しています。テーブル作成の時点でcontentにはnull:falseで空で保存させないようにしていますが空文字("")は保存できます。なのでpresence:trueを追加することによって空文字も拒否するようになります。また、バリデーションに基づいたエラーメッセージも保存されます。
class User < ApplicationRecord
has_many :microposts, dependent: :destroy
has_many :comments, dependent: :destroy
class Micropost < ApplicationRecord
belongs_to :user
has_many :comments, dependent: :destroy
UserもMicropostも多数のコメントを持てるためhas_manyで1対多の関連付けを行います。dependent: :destroyでUser、Micropostが消えた際に関連するコメントも消えるようにしています。
#3Comenntsコントローラーの作成、ルーディングの設定
続いてコントローラーを作成します。今回はcreateとdestroyアクションのみ使用します。
rails g controller comments
class CommentsController < ApplicationController
def create
end
def destroy
end
end
ルーディングの設定。
resources :microposts, only: [:new, :show, :create, :destroy] do
resources :comments, only: [:create, :destroy]
end
commetsはmicropostsとネストして親子の関係を持たせます。こうすることによってコメントを作成する際に関連しているmicropostのidを取得することが容易になります。ネスト有無の違いはこんな感じです。commentsがmicropstsの下の階層についているのがわかります。
ネスト有
micropost_comments POST /microposts/:micropost_id/comments(.:format) comments#create
micropost_comment DELETE /microposts/:micropost_id/comments/:id(.:format) comments#destroy
ネスト無し
comments POST /comments(.:format) comments#create
comment DELETE /comments/:id(.:format) comments#destroy
#4対応するビューの作成
今回はMicropostの詳細ページからコメントするような形をとります。
コメントを表示するのはMicropost_show(Microposts/:id)になるのでまずはコントローラーにコメント表示とコメントフォーム用にインスタンス変数を作成しておきます。
def show
@comment =Comment.new #新規コメント用
@micropost = Micropost.find(params[:id])
@comments =micorpost.comments #コメント表示用投稿に関連づくコメントの取得
end
コメント表示(投稿者と中身のみ)
<% @comments.each do |comment| %>
<%= comment.user.name %>
<%= comment.content %>
<% end %>
コメントフォーム
<%= form_with model: [@micropost, @comment], local: true do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class = 'form-group'>
<%= f.text_area :content, class: 'form-control', id:'content'\
placeholder: "コメントを記入してください" %>
</div>
<%= f.submit "コメントする", class: "btn btn-primary" %>
<% end %>
コメントはいずれかのマイクロポストと関連づいているため、どのマイクロポストのコメントなのかという情報が必要になります。
そのため、form_withにmicropostのidも渡します。@comment=Comment.newで新規に値を作成しているので作成ボタンを押すと自動的にcreateアクションが動きます。
#5.コントローラのアクションの作成
次はコメントを作成するcreateアクションを作っていきます。
class CommentsController < ApplicationController
before_action :logged_in_user, only: :create
def create
@comment = current_user.comments.build(comment_params)
@comment.micropost_id = params[:micropost_id]
if @comment.save
flash[:success] = 'コメントしました'
redirect_to @comment.micropost
else
@micropost = Micropost.find(params[:micropost_id])
@comments = @micropost.comments
render template: 'microposts/show'
end
private
def comment_params
params.require(:comment).permit(:content)
end
end
createアクション失敗後に再度同じページを表示するため、コメントを取得してます。(このやり方があっているかはわかりません。)コメントフォーム専用のページを設ければこの取得はいらないです。
コメントを作成する際に、ユーザーのidを渡す必要がありますが、current_user.comments.buildとすることによりログイン中ユーザーのidを入れ込みます。このままだとマイクロポストのidがなくコメントを作成できないのでform_withから送られてくるmicropost_idを取得しています。
(ストロングパラメータでmicoropst_idを取得する方法がわかりませんでした。)
これでコメントフォームからコメントをすることができるようになりました。
続いてコメント削除です。
まずはviewsに削除リンクを埋め込みます。
<% @comments.each do |comment| %>
<%= comment.user.name %>
<%= comment.content %>
<%= link_to '削除', micropost_comment_path(@micropost, comment), method: :delete %>
<% end %>
destroyアクションを動かすためmethod: :deleteを指定します。
pathにはコメントがいずれかのマイクロポストに紐づいている関係上(@micropost,comment)を渡す必要があります。
micropost_comment DELETE /microposts/:micropost_id/comments/:id(.:format) comments#destroy
class CommentsController < ApplicationController
before_action :correct_user, only: :destroy
def destroy
@comment = Comment.find(params[:id])
@comment.destroy
flash[:success] = 'コメントを削除しました'
redirect_to @comment.micropost
end
private
def correct_user
@comment = current_user.comments.find_by(id: params[:id])
redirect_to root_url if @comment.nil?
end
end
#終わりに
間違いや、認識違い等があればコメントください。
それではここまで読んんでいただいきありがとうございました。