はじめに
Railsでコメント機能を実装します。非同期通信なのでJavasprictを使用していきます(jQuery)
自分用なのでかなり端折ってますが、コメント投稿後にリロードなしで表示させたいっ!て方はぜひ参考にしてください。
開発環境、前提条件
- AWS Cloud9
- Ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux]
- Rails 6.1.7.3
- Deviseのインストールが完了していて、ユーザーの新規登録やログインができる状態
- Comments/Controller.rb 作成済み
- Comment.rb 作成済み
やっていきましょう!
まずはコントローラの記述をします。
controllers/comments/controller.rb
class CommentsController < ApplicationController
def create
@post = Post.find(params[:post_id])
@comment = current_user.comments.new(comments_params)
@comment.post_id = @post.id
if @comment.save
@post.create_notification_comment!(current_user, @comment.id)
@comments = @post.comments.order(created_at: :desc)
else
@error_message = "コメントの投稿に失敗しました"
end
end
def destroy
@comment = Comment.find(params[:id])
@comment.destroy
end
private
def comments_params
params.require(:comment).permit(:content, :post_id)
end
end
続いてモデル
models/comment.rb
class Comment < ApplicationRecord
belongs_to :user #ユーザー
belongs_to :post #投稿
has_many :notifications, dependent: :destroy #通知
validates :content, length: { in: 1..140 }
#コメントできるのは1~140文字以内
end
ビュー
Javasprictを使用し投稿したコメントのみビューに追加するのでパーシャルは細かく分けます。
ビューの中はそれぞれのアプリケーションによって変更してください。
まずはコメントフォーム
local: falseとすることで非同期にします。
views/posts/_comment_form.html.erb
<%= form_with(model: [@post, Comment.new], local: false) do |f| %>
<div class="user-comment">
<% if current_or_guest_user.email == 'guest@example.com' %>
<div class="form-group d-flex">
<% if current_user.profile_picture.attached? %>
<%= image_tag current_user.profile_picture, size: "50x50", class: 'rounded-circle' %>
<% end %>
<%= f.text_field :content, placeholder: "ゲストユーザーはコメントできません", class: "form-control-comment" %>
</div>
<% else %>
<div class="user-comment">
<div class="form-group d-flex">
<% if current_user.profile_picture.attached? %>
<%= image_tag current_user.profile_picture, size: "50x50", class: 'rounded-circle' %>
<% else %>
<%= image_tag 'default_profile_picture.png', size: "50x50", class: 'img-fluid rounded-circle mr-2' %>
<% end %>
<%= f.text_field :content, placeholder: "コメント入力欄", class: "form-control-comment ml-2 mt-1" %>
<%= f.submit "コメントする", class: "btn btn-custom" %>
</div>
</div>
<% end %>
</div>
<% end %>
続いてコメントを表示させるビュー
views/posts/_comment.html.erb
<div class="comment-container ml-3" id="comment_<%= comment.id%>" >
<div class="d-flex align-items-start">
<%= image_tag url_for(comment.user.profile_picture), class: 'img-fluid-comment rounded-circle mr2' if comment.user.profile_picture.attached? %>
<div>
<p class="comment-header ml-3"><%= link_to comment.user.username, user_path(comment.user) %></p>
<p class="comment-content ml-3"><%= comment.content %></p>
</div>
<% if current_user == comment.user %>
<div class="ml-auto mt-auto">
<%= link_to post_comment_path(@post, comment), method: :delete, remote: true,
data: { confirm: "本当に削除しますか?" }, class: "comment-delete-link btn btn-link" do %>
<i class="fas fa-trash" style="color: #FF6347;"></i>
<% end %>
</div>
<% end %>
</div>
</div>
上2つで作成したパーシャルをここに持ってきましょう。こうすることで新しく作成されたコメントのみを表示されることができるのでビュー全体を読み込まなくなります。
views/posts/_comments.html.erb
<%= render 'comment_form' %>
<div id="comments">
<% @comments.each do |comment| %>
<%= render 'comment', comment: comment %>
<% end %>
</div>
<script>
document.addEventListener('DOMContentLoaded', (event) => {
let guestActions = document.querySelectorAll('.guest-action');
guestActions.forEach((element) => {
element.addEventListener('click', (event) => {
event.preventDefault();
alert('ゲストユーザーはこの操作はできません');
});
});
});
</script>
Javasprict
("#comments").length > 0) がなぜかコメントアウトされてるように見えますが、実際にはちゃんと記述されています。
prependは新しいコメントを上に表示させるメソッドです。
下に表示させたい場合は、appendにしてください
views/comments.create.js.erb
<% if @error_message.present? %>
alert("<%= @error_message %>")
<% else %>
if ($("#comments").length > 0) {
$("#comments").prepend("<%= j(render('posts/comment', comment: @comment)) %>")
}
$('#comment_content').val("");
<% end %>
削除機能もつけると思うのでこれも非同期にしちゃいましょう。
views/comments.destroy.js.erb
$("#comment_<%= @comment.id %>").remove()
その他Javasprict
config/environment/webpack/environment.js
const { environment } = require('@rails/webpacker')
const jquery = require('./plugins/jquery')
const webpack = require('webpack')
environment.plugins.prepend('Provide',
new webpack.ProvidePlugin({
$: 'jquery/src/jquery',
jQuery: 'jquery/src/jquery',
})
)
environment.plugins.prepend('jquery', jquery)
module.exports = environment
app/javasprict/packs/application.js
// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.
import $ from 'jquery';
window.$ = $;
window.jQuery = $;
import Rails from "@rails/ujs"
import Turbolinks from "turbolinks"
import * as ActiveStorage from "@rails/activestorage"
import "channels"
import "jquery";
import "popper.js";
import "bootstrap";
import "../stylesheets/application"
import '@fortawesome/fontawesome-free/js/all'
require("packs/post_picture")
// require("@rails/ujs").start()
// require("turbolinks").start()
// require("@rails/activestorage").start()
// require("channels")
// require("jquery")
import './likes'
Rails.start()
Turbolinks.start()
ActiveStorage.start()
ルーティング
config/routes.rb
resources :posts do
resources :comments, only:[:create, :destroy]
end
以上で実装の完了です!
参考になりましたら、いいね、コメントお待ちしてます!🎉