[Rails]コメント投稿、削除機能のajax化11/20
はじめに
コメント投稿、削除機能のajax化を実装していきます。
環境:
Rails 6.1.7.3
ruby 3.0.0
Ajaxとは
Ajax(Asynchronous JavaScript and XML)は、Webページの一部を非同期に更新するための技術です。Ajaxを使用することで、ページの再読み込みを行わずにデータの送受信や部分的な画面の更新が可能となります。
Railsにおけるajax通信
Railsにおいてajax通信を実装する場合、主に二つの方法があります。
- remote: trueを指定する方法
- JSファイルに任意のタイミングでajax処理を発火させるように記述を施す方法
remote: true
remote: true
は、Ruby on Rails のビューヘルパーメソッドであり、AJAX リクエストを行う際に使用されます。このヘルパーメソッドを使用すると、フォームやリンクなどの要素をクリックしたり送信したりした際に、非同期的なリクエストが行われます。
具体的には、remote: true
を使用すると、以下のようなことが実現できます:
- ページのリロードなしでデータの送信や取得ができる。
- レスポンスを JavaScript で処理して、動的なコンテンツの更新や表示ができる。
<%= form_with url: some_path, remote: true do |form| %>
<!-- フォームのフィールドやボタンなどのコード -->
<% end %>
<%= link_to 'Click me', some_path, remote: true %>
今までのページ遷移では、ブラウザはページ全体のHTMLをサーバから取得し、続いてそのHTMLが要求するアセットすべてをサーバから取得して、ページを組み立てます。
Ajaxを使うと、ページの一部だけをサーバから非同期で取得し、コントローラーのアクションに対応するJavaScriptファイルで処理され、レスポンスビューを作成されます。
コメント投稿のajax化
コメントした時ににページをリロードせずJSで新規コメントをフォームの直下に表示させます。
コントローラーを編集しアクショのビューをJSにします。
ビューへ変数の値を渡すためにインスタンス変数にします。
class CommentsController < ApplicationController
def create
@comment = Current.user.comments.build(comment_params)
@article = Article.find(params[:article_id])
@comments = @article.comments.includes(:user).order(created_at: :desc)
respond_to do |format|
if @comment.save
format.js
format.html { redirect_to article_path(comment.article), flash: { success: t('defaults.message.created', item: Comment.model_name.human) } }
else
format.html { redirect_to article_path(comment.article), flash: { danger: t('defaults.message.not_created', item: Comment.model_name.human) } }
format.js
end
end
end
end
respond_to
とは
respond_to
は、Railsコントローラー内で使用されるメソッドです。このメソッドは、リクエストの種類や形式に応じて異なるレスポンスを返すために使用されます。
通常、format
ブロックと組み合わせて使用されます。
local: false
オプション
rails6ではform_with
で同期通信がデフォルト(local: true)になったため、local: false
を指定する必要があります。
<%= form_with model: [article, comment], local: false, id: 'new_comment' do |form| %>
フォームが非同期送信になりました。
コンソールでrequest.format
を実行してフォーマットがjavascript
であることを確認します。
(byebug) request.format
#<Mime::Type:0x00007f887bac99e0
@synonyms=["application/javascript",
"application/x-javascript"],
@symbol=:js,
@string="text/javascript",
@hash=-116090781805038615>
TRANSACTION (0.7ms) begin transaction
↳ app/controllers/comments_controller.rb:9:in `block in create'
Article Load (1.0ms) SELECT "articles".* FROM "articles" WHERE "articles"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
↳ app/controllers/comments_controller.rb:9:in `block in create'
Comment Create (3.1ms) INSERT INTO "comments" ("body", "article_id", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["body", "hello?"], ["article_id", 1], ["user_id", 1], ["created_at", "2023-06-30 10:03:04.906103"], ["updated_at", "2023-06-30 10:03:04.906103"]]
↳ app/controllers/comments_controller.rb:9:in `block in create'
TRANSACTION (1.3ms) commit transaction
↳ app/controllers/comments_controller.rb:9:in `block in create'
*** No sourcefile available for <internal:kernel>
(byebug) next
# jsを実行される
Rendering comments/create.js.erb
Rendered comments/_comment.html.erb (Duration: 39.6ms | Allocations: 403517)
Rendered comments/create.js.erb (Duration: 57.9ms | Allocations: 602137)
jsにてビューを作成する
comments#create
の処理後、create.js.erb
にてコメントの部分テンプレートの再描画を行うように実装していきます。
JSにてhtml要素を取得するためにIDを付けました。
(function() {
<%# 既に表示されているエラーメッセージがあった場合は削除する %>
document.querySelector('.error_messages')?.remove();
<% if @comment.errors.any? %>
<%# エラーがある、処理失敗時にはエラーメッセージのパーシャルを表示させる %>
let commentErrors = '<%= j(render('shared/error_messages', object: @comment)) %>';
let newCommentForm = document.getElementById('new_comment');
newCommentForm.insertAdjacentHTML('afterbegin', commentErrors);
<% else %>
let commentTemplate = '<%= j(render('comments/comment', article: @article, comment: @comment)) %>';
let commentContainer = document.getElementById('js-comment-container');
<%# エラーがない、処理成功時には作成されたコメント内容をHTML要素として追加する %>
commentContainer.insertAdjacentHTML('afterbegin', commentTemplate);
<%# コメント入力フォームのテキストは表示する必要がないので、空文字に置き換えて内容をクリアする %>
let newCommentBody = document.getElementById('js-new-comment-body');
newCommentBody.value = '';
<% end %>
})();
コメント削除のajax化
コメントを削除した時にJSを発火させコメントビューをページから消します、remote: true
を使用しサーバーの処理から離れます。
routes.rb
を編集する
resources :articles do
resources :comments, only: %i[create destroy]
end
destroy
アクションを追加する
コントローラーにてdestroy
アクションを追加します。
class CommentsController < ApplicationController
def destroy
@comment = Current.user.comments.find(params[:id])
@article = Article.find(params[:article_id])
respond_to do |format|
@comment.destroy
format.js
format.html { redirect_to article_path(@article), flash: { success: t('defaults.message.destroyed', item: Comment.model_name.human) } }
end
end
end
削除用パスを追加する
article_comment DELETE /articles/:article_id/comments/:id(.:format) comments#destroy
<li class="list-inline-item">
<%= link_to article_comment_path(@article, comment),
class: 'js-delete-comment-button',
method: :delete,
id: "button-delete-#{comment.id}",
data: { confirm: t('defaults.message.delete_confirm') },
remote: true do %>
<%= icon 'fas', 'trash' %>
<% end %>
</li>
destroy.js.erb
を作成する
var commentElement = document.getElementById('comment-<%= @comment.id %>');
if (commentElement) {
commentElement.parentNode.removeChild(commentElement);
}
処理の流れを確認します。
Started DELETE "/articles/1/comments/137" for ::1 at 2023-06-30 19:04:49 +0900
Processing by CommentsController#destroy as JS
Parameters: {"article_id"=>"1", "id"=>"137"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
↳ app/controllers/application_controller.rb:9:in `set_current_user'
Comment Load (0.1ms) SELECT "comments".* FROM "comments" WHERE "comments"."user_id" = ? AND "comments"."id" = ? LIMIT ? [["user_id", 1], ["id", 137], ["LIMIT", 1]]
↳ app/controllers/comments_controller.rb:19:in `destroy'
Article Load (0.1ms) SELECT "articles".* FROM "articles" WHERE "articles"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
↳ app/controllers/comments_controller.rb:20:in `destroy'
TRANSACTION (0.0ms) begin transaction
↳ app/controllers/comments_controller.rb:22:in `block in destroy'
Comment Destroy (0.7ms) DELETE FROM "comments" WHERE "comments"."id" = ? [["id", 137]]
↳ app/controllers/comments_controller.rb:22:in `block in destroy'
TRANSACTION (0.5ms) commit transaction
↳ app/controllers/comments_controller.rb:22:in `block in destroy'
# jsを実行される
Rendering comments/destroy.js.erb
Rendered comments/destroy.js.erb (Duration: 0.7ms | Allocations: 140)
Completed 200 OK in 21ms (Views: 4.0ms | ActiveRecord: 1.5ms | Allocations: 6599)
終わりに
AJAX難しいかったです。
流れを掴んだらコメント編集機能も挑戦したいと思います。
Discussion