0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Rails]非同期(Ajax)でのコメント機能実装

Last updated at Posted at 2021-10-07

この記事の目的

非同期処理(ajax)でのコメント投稿機能を学んだのでアウトプットします

目標

Image from Gyazo

要件

コメント投稿は非同期で行う

環境

・Ruby 2.7.1

・Rails 5.2.6

前提

・UserとPostは作成済み

・bootstrapとjquery-railsが入っている

設計

ER図
Image from Gyazo


ルーティング

POST  /posts/:post_id/comments  comments#create
GET  /comments/:id/edit  comments#edit
PATCH  /comments/:id  comments#update
DELETE  /comments/:id  comments#destroy 

post_idが必要なのはコメントがcreateされる場合だけであり、一度コメントが作成されればそれらコメントは一意のidを持つので、edit,update,destroyの場合はコメントの自身のidのみで特定可能である。
よって、このようなルーティングにする。(shallow: trueで実現可能)
このようなshallowルーティングのメリットは、shallowを使わない場合と比較してルーティングがスッキリするという点である。

実装

データベース

$ rails g model Comment body:text user:references post:references実行

マイグレーションファイル編集(任意)

$ rails db:migrate実行


モデル


models/comment.rb

class Comment < ApplicationRecord
  belongs_to :user
  belongs_to :post

  validates :body, presence: true, length: { maximum: 1000 }
end
models/user.rb

has_many :comments, dependent: :destroy
models/post.rb

has_many :comments, dependent: :destroy

has_many,belongs_toはメソッドを作るためのメソッド。

※ Userモデルにhas_many :commentsを記述することで、Userモデルのインスタンスは.commentsメソッドを使い、自身のidに対応するCommentモデルのuser_idを通じて、コメント情報にアクセスできるようになる。Postも同様。

※ 逆に、Commentモデルにbelongs_to :userを記述することで、Commentモデルのインスタンスは.userメソッドを使い、自身のuser_idに対応するUserモデルのidを通じて、ユーザー情報にアクセスできるようになる。belongs_to :postでも同様。


ルーティング


routes.rb

resources :posts, shallow: true do
    resources :comments
end

※ 上述したルーティング設計に合わせるため、shallowルーティングを使用している
(参考:Railsの”shallow(浅い)”ルーティングを理解する


コントローラー

comments_controller.rb
class CommentsController < ApplicationController
  
  def create
    @comment = current_user.comments.build(comment_params)
    @comment.save
  end

  def edit
    @comment = current_user.comments.find(params[:id])
  end

  def update
    @comment = current_user.comments.find(params[:id])
    @comment.update(comment_update_params)
  end

  def destroy
    @comment = current_user.comments.find(params[:id])
    @comment.destroy!
  end

  private

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

  def comment_update_params
    params.require(:comment).permit(:body)
  end
end

current_user.comments.findComment.findにしてしまうと、コメントしたユーザー以外のユーザーがURL直打ちなどによって他人のコメントを編集できてしまうようになるので注意。

※ ユーザから受け取った情報としてparamsにはないけれども、レコード作成時に追加したい値がある場合はmergeメソッドで含めることができる(今回のpost_id)。
mergeメソッドは、ハッシュ同士を統合できるメソッド。

x = {age: "25", email: "tarou@example.com"}
{name: "太郎"}.merge(x)  
#=> {name: "太郎", age: "25", email: "tarou@example.com"}

(参考)
【初学者向け】Rails mergeメソッド
mergeメソッドについて改めて理解を深めた


buildメソッドnewメソッドと同じ。慣習的に、関連するモデルを生成するときはbuildを使う(参考:
railsのnewとbuildの違い

※ createアクションの処理が終わると、views/comments/ディレクトリ配下にcreate.html.erbまたはcreate.js.erbを探しに行く。edit,update,destroyも同様。


view

jsの記述

views/comments/create.js.erb

<% if @comment.errors.present? %>
   alert("<%= @comment.errors.full_messages.join('\n') %>");
<% else %>
   $('.comments-box').prepend("<%= escape_javascript(render('comments/comment', comment: @comment) ) %>");
   $('.input-comment-body').val('');
<% end %>

※ slimならRubyのコードを囲む<%=...%>#{...}となる

escape_javascriptjと省略して書くこともできる

※ 最初の<% if @comment.errors.present? %>の部分は、ユーザーのコメントがバリデーションに引っかかった場合にエラー文を表示する処理。
<% else %>の1行目の部分は、htmlのcomments-boxクラスで囲まれた部分にrender('comments/comment', comment: @comment) をぶちこんでいる。

イメージ

<div class="comments-box">

   <li>コメント1</li>
   <li>コメント2</li>
   <li>コメント3</li>

   <li>新規コメント</li> ⇦ こいつがjsで追加されるイメージ

</div>

検証ツールで実際の挙動を確認

Image from Gyazo

※ コメントを投稿したとき、画面遷移はせずに<div id="comment-29">...</div>だけが追加されていることがわかる


htmlの記述(投稿されたコメントの表示画面)

views/posts/show.html.erb(postの詳細画面)
・
・
・
<hr>
      / コメント一覧
      <%= render 'comments/comments', comments: @comments %>

    <hr class="m-0">
       <div class="post-comment p-3">
         <%= render 'comments/form', post: @post, comment: @comment %>
       </div>
     </hr>
</hr>
views/comments/_comments.html.erb

<div class="comments-box">
   <%= render comments %>
</div>

※ commentsは@comments。つまりrender @commentsで_comment.html.erbに飛ばしているのと同じ(each文省略するやつ)

views/comments/_comment.html.erb
( @comments.each do |comment| が省略されている )
(<div class="comments-box">)
・
・
・

 / コメント一覧のうちのコメントひとつひとつ(|comment|)
  <div class="col-9">
    <span class="font-weight-bold.pr-1">
          <%= comment.user.username %>
    </span>
        <%= comment.body %>
  </div>

(</div>)

htmlの記述(コメントの入力フォーム)

views/comments/_form.html.erb

<%= form_with model: [post, comment], class: 'd-flex mb-0 flex-nowrap justify-content-between', remote: true do |f| %>
  <%= f.text_field :body, class: 'form-control input-comment-body', placeholder: 'コメント' %>
  <%= f.submit '投稿', class: 'btn btn-primary btn-raised' %>
<% end %>

※ form_withはデフォルトでremote: trueなので書く必要はないが、local: trueではないことを強調するためにremote: trueを明示。
ちなみにlink_toの場合はremote: trueを必ず書かなければいけない。

※ ルーティングをネストさせているのでmodel: [post, comment]と2つ書かなければいけない

※ もしform_with model: commentにしたら、/commentsPOSTリクエストが送られるが、ルーティングのPOST /posts/:post_id/comments comments#createにマッチしないため、post_idが不存在ということでエラーが起きる。

※ edit, update, destroyについてもcreate同様にviewを作っていく


ちなみに、検証ツールを見ればTypeがxhrになっており、form_withのremote: trueが非同期処理をしていることがわかる。

XHR(XMLHttpRequest)とは、JavaScriptなどのウェブブラウザ搭載のスクリプト言語でサーバとのHTTP通信を行うための、組み込みオブジェクト(API)である。
すでに読み込んだページからさらにHTTPリクエストを発することができ、ページ遷移することなしにデータを送受信できるAjaxの基幹技術である。(Wikipedia引用

Image from Gyazo

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?