#はじめに
掲示板サイトを作成中のプログラミング初学者です。
新規投稿機能が実装できたので、
お次にコメント機能を追加しました。
自分のための備忘録ですが、同じような機能を実装しようとしている方の参考になれば幸いです!
#前提
ユーザー認証にdeviseを使用しています。
#コメントモデルの生成
コメントは、投稿が必ず所有する情報ではないため、投稿と別のテーブルで管理しなくてはなりません。
そのため、commentsテーブルを作る必要があります。
さらに、コメントはどのツイートに対してのコメントなのか、どのユーザーが投稿したコメントなのか判る必要があります。
そのため、userモデルとpostモデルの2つにアソシエーションを組む必要があります。
$ bundle exec rails g model Comment
これにより、
・app/modelsフォルダ下にコメントモデルファイルが生成(app/models/comment.rb)
・db/migrateフォルダ下にマイグレーションファイルが生成
下記のようになるように、マイグレーションファイルを編集する。
class CreateComments < ActiveRecord::Migration[6.0]
def change
create_table :comments do |t|
t.integer :user_id
t.integer :tweet_id
t.text :text
t.timestamps
end
end
end
その上で下記を実行。
$ bundle exec rails db:migrate (データベースに変更を指示・反映する)
Sequel Proを開くか、もしくはMySQLを立ち上げて、テーブルが追加されているかどうか確認する。
$ brew services start mysql@5.7
$ mysql --user=root --password を実行。
データベースの中に入ったら下記を実行。
show tables from データベース名;
中にcommentsテーブルがあればOK!
モデルにアソシエーションの記載
Commentモデルにはbelongs_toが自動で設定されていますが、
UserモデルとPostモデルには手動で追加する必要がある。
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy # commentsテーブルとのアソシエーション
end
ユーザーは複数のコメントを投稿できるので、has_many:複数形になります。
class Post < ApplicationRecord
validates :text, presence: true
belongs_to :user
has_many :comments, dependent: :destroy # commentsテーブルとのアソシエーション
end
投稿は複数のコメントを所有できるので、has_many:複数形になります。
dependent: :destroyオプションを追加記述することで、
親モデルのPostモデルが削除された際に、子モデルのCommentモデル(インスタンス)も削除されます。
class Comment < ApplicationRecord
belongs_to :post # postsテーブルとのアソシエーション
belongs_to :user # usersテーブルとのアソシエーション
end
コメントは一人のユーザーと一つの投稿に紐づいているので、belongs_to:モデル単数形になります。
ルーティングの設定
コメントを投稿する際には、どの投稿に対するコメントなのかをパスから判断できるようにします。
ルーティングのネストという方法を使っていきます。
その一番の理由は、アソシエーション先のレコードのidをparamsに追加してコントローラーに送るためです。
Rails.application.routes.draw do
resources :親となるコントローラー do
resources :子となるコントローラー
end
end
今回は「コメント情報を作る機能」を実装するのでcreateアクションのルーティングとします。
Rails.application.routes.draw do
devise_for :users
root to: 'posts#index'
resources :posts do
resources :comments, only: :create
end
resources :users, only: :show
end
ルーティングを確認してみましょう。
$ bundle exec rails routes
Prefix Verb URI Pattern Controller#Action
# 中略
post_comments POST /posts/:post_id/comments(.:format) comments#create
# 中略
Commentsコントローラの作成
$ bundle exec rails g controller comments
class CommentsController < ApplicationController
def create
Comment.create(comment_params)
end
private
def comment_params
params.require(:comment).permit(:text).merge(user_id: current_user.id, post_id: params[:post_id])
end
end
user_idカラムには、ログインしているユーザーのidとなるcurrent_user.idを保存し、
post_idカラムは、paramsで渡されるようにするので、params[:post_id]として保存しています。
続けて、コメントしたら投稿詳細画面に戻る処理をcomments_controller.rbに記述しましょう。redirect_toメソッドを使って記述します。
class CommentsController < ApplicationController
def create
comment = Comment.create(comment_params)
redirect_to "/posts/#{comment.post.id}" # コメントと結びつく投稿の詳細画面に遷移する
end
private
def comment_params
params.require(:comment).permit(:text).merge(user_id: current_user.id, post_id: params[:post_id])
end
end
postsコントローラーのshowアクションを実行するには、投稿のidが必要です。
そのため、ストロングパラメーターを用いた上で変数commentに代入します。
Viewの編集
次は、コメントを投稿するためのフォームを作ります。
投稿の詳細画面からコメントしたいので、
views/posts/show.html.erbにフォームを記述します。
<div class="contents row">
<div class="content_post" >
<p><%= @post.text %></p>
<p><%= image_tag @post.image.variant(resize: '500x500'), class: 'post-image' if @post.image.attached?%></p>
<span class="name">
<a href="/users/<%= @post.user.id %>">
<span>投稿者</span><%= @post.user.nickname %>
</a>
<% if user_signed_in? && current_user.id == @post.user_id %>
</span>
<%= link_to '編集', edit_post_path(@post.id), method: :get %>
<%= link_to '削除', "/posts/#{@post.id}", method: :delete %>
<% end %>
</div>
<div class="container">
<% if user_signed_in? %>
<%= form_with(model: [@post, @comment], local: true) do |form| %>
<%= form.text_area :text, placeholder: "コメントする", rows: "2" %>
<%= form.submit "コメント" %>
<% end %>
<% else %>
<strong><p>※※※ コメントの投稿には新規登録/ログインが必要です ※※※</p></strong>
<% end %>
</div>
</div>
投稿の詳細画面で投稿と結びつくコメントを表示するためには、ビューを呼び出す前のコントローラーが実行されている時点で、まずはコメントのレコードをデータベースから取得する必要があるのでposts_controller.rbのshowアクションを編集しましょう。
class PostsController < ApplicationController
before_action :set_post, only: [:edit, :show]
before_action :move_to_index, except: [:index, :show]
def index
@posts = Post.includes(:user).order("created_at DESC")
end
def new
@post = Post.new
end
def create
Post.create(post_params)
end
def destroy
post = Post.find(params[:id])
post.destroy
end
def edit
end
def update
post = Post.find(params[:id])
post.update(post_params)
end
def show
@comment = Comment.new
@comments = @post.comments.includes(:user)
end
private
def post_params
params.require(:post).permit(:image, :text).merge(user_id: current_user.id)
end
def set_post
@post = Post.find(params[:id])
end
def move_to_index
unless user_signed_in?
redirect_to action: :index
end
end
end
posts/show.html.erbでform_withを使用して、comments#createを実行するリクエストを飛ばしたいので、@comment = Comment.newというインスタンス変数を生成しておかないといけません。
postsテーブルとcommentsテーブルはアソシエーションが組まれているので、@post.commentsとすることで、@postへ投稿されたすべてのコメントを取得できます。
ビューでは誰のコメントか明らかにするため、
アソシエーションを使ってユーザーのレコードを取得する処理を繰り返します。
その際「N+1問題」が発生してしまうので、includesメソッドを使って解決しています。
コメントを表示するためにツイート詳細ページを編集しましょう。
<div class="contents row">
<div class="content_post" >
<p><%= @post.text %></p>
<p><%= image_tag @post.image.variant(resize: '500x500'), class: 'tweet-image' if @post.image.attached?%></p>
<span class="name">
<a href="/users/<%= @post.user.id %>">
<span>投稿者</span><%= @post.user.nickname %>
</a>
<% if user_signed_in? && current_user.id == @post.user_id %>
</span>
<%= link_to '編集', edit_post_path(@post.id), method: :get %>
<%= link_to '削除', "/posts/#{@post.id}", method: :delete %>
<% end %>
</div>
<div class="container">
<% if user_signed_in? %>
<%= form_with(model: [@post, @comment], local: true) do |form| %>
<%= form.text_area :text, placeholder: "コメントする", rows: "2" %>
<%= form.submit "コメント" %>
<% end %>
<% else %>
<strong><p>※※※ コメントの投稿には新規登録/ログインが必要です ※※※</p></strong>
<% end %>
<div class="comments">
<h4><コメント一覧></h4>
<% @comments.each do |comment| %>
<p>
<strong><%= link_to comment.user.nickname, "/users/#{comment.user_id}" %>:</strong>
<%= comment.text %>
</p>
<% end %>
</div>
</div>
</div>
以上で完成です。
▼参考記事▼