LoginSignup
4
8

More than 3 years have passed since last update.

Railsアプリに非同期通信のコメント機能を実装

Posted at

はじめに

Railsアプリに非同期通信のコメント機能を実装しました。
非同期通信は一言で言えば、画面遷移をせずにページを更新する技術です。
備忘録として手順をまとめたいと思います。

実装イメージ

ezgif.com-gif-maker (8).gif

開発環境

  • macOS Catalina
  • Ruby 2.6.5
  • Ruby on Rails 5.2

目次

1.モデルとアソシエーション
2.ルーティング
3.コントローラー
4.view

1. モデルとアソシエーション

モデル構成とアソシエーションはよくある構成です。
前提としてuserとpostモデルは作成済みとします。
スクリーンショット 2021-04-16 22.54.44.png

commentモデル作成

$ rails g model comment user:references post:references content:string
$ rails g migrate

アソシエーション

user.rb
  has_many :posts, dependent: :destroy
  has_many :comments, dependent: :destroy
post.rb
  belongs_to :user
  has_many :comments, dependent: :destroy
comment.rb
  belongs_to :user
  belongs_to :post

  # バリデーション(カラの入力を無効に)
 validates :content, presence: true

2. ルーティング

コメントは投稿に紐付いているのでルーティングはネストさせて記述します。
ネストさせることでアソシエーション先のレコード(今回で言えば、投稿に紐づくコメント)のidをparamsに追加してコントローラーにわたすことができるようになります。

routes.rb
resources :posts, only: [:index, :new, :create, :show, :destroy] do
  resources :comments, only: [:create, :destroy]
end

3. コントローラー

まずはcommentのコントローラーを作成します。

$ rails g controller comments

次にcreateとdestroyアクションを定義します。

comments_controller.rb

comments_controller.rb
class CommentsController < ApplicationController

  def create
    @comment = Comment.create(comment_params)
    respond_to do |format|
      if @comment.save
        format.html { redirect_back(fallback_location: root_path) } # 前のページに遷移
        format.js  # create.js.erbが呼び出される
      else
        format.html { redirect_back(fallback_location: root_path) } # 前のページに遷移
      end
    end
  end

  def destroy
    @post = Post.find(params[:post_id])
    @comment = current_user.comments.find_by(post_id: @post.id)
    @comment.destroy
    redirect_back(fallback_location: root_path)
  end

  private
  def comment_params
    params.require(:comment).permit(:content).merge(user_id: current_user.id, post_id: params[:post_id])
  end

end

Comment.create(comment_params)
・comment_paramsではmergeメソッドでuser_idとpost_idをcommentテーブルのレコードに格納します。

respond_to do |format|
・処理の結果をHTML形式で返すかJS形式で返すかを分岐させます。
・今回はコメントが保存されたらJS形式で返すように設定します。format.jsと記述することでcreate.js.erbというファイルを返します。

redirect_back(fallback_location: root_path)
・もしJS形式で返せなかった場合は同期通信でコメントを作成します。その際redirect_backでコメント作成ページにとどまることができます。fallback_location: root_pathはエラーが起きた際にroot_pathに遷移するという記述です。

posts_controller.rb

viewで反映させるためにposts_controller.rbを編集します。

posts_controller.rb
 def show
  @post = Post.find(params[:id])
  @comment = Comment.new
  @comments = @post.comments.all
 end

4. View

非同期通信をさせるためにcreate.js.erb_comment.html.erbという部分テンプレートを作成します。
一例ですがディレクトリ構成は以下のようになります。

views
|-posts
|    |-show.html.erb
|-comments
     |-_comment.html.erb
     |-create.js.erb

投稿詳細ページは以下のようになります。

posts/show.html.erb
 <% if @comments %>
    <div class="commentOutline">
      <%= render partial: "modules/comment", locals: { comments: @comments }%>
    </div>
  <% else %>
    <p>コメントはまだありません</p>
  <% end %>

  <div class="bottomInput">
    <%= form_with(model: [@post, @comment], id: "new-comment") do |f| %>
      <%= f.text_field :content, class: "inputComment" %>
      <%= f.submit "コメントする", class: "submitComment" %>
    <% end %>
  </div>

renderメソッドで_comment.html.erbを呼び出します。更にlocalsオプションでposts_controller.rbで定義した@commnetsの変数をcommentsとして部分テンプレート内で使用できるようにします。
form_withはデフォルトでremote: tureになっているのでこれでJS形式でレスポンスすることができます。

部分テンプレートは以下の通りです。

comments/_comment.html.erb
<% comments.each do |comment| %>
  <li>
    <div class="topPosition">
      <%= link_to user_path(comment.user.id), class: "commentUserLink" do %>
        <% if comment.user.icon? %>
          <%= image_tag comment.user.icon.url, class: "commentUserIcon"%>
        <% else %>
          <i class="fas fa-user-circle"></i>
        <% end %>
        <p class="commentUserName"><%= comment.user.nickname %></p>
      <% end%>
      <% if comment.user_id == current_user.id %>
        <%= link_to post_comment_path(comment.post_id, comment.id), method: :delete, class: "deleteCommentLink" do %>
          <i class="fas fa-trash"></i>
        <% end %>
      <% end %>
    </div>
    <div class="bottomPosition">
      <p class="commentContent"><%= comment.content %></p>
      <p class="commentDatetime"><%= comment.created_at.strftime('%Y/%m/%d') %></p>
    </div>
  </li>
<% end %>

最後にcreate.js.erbを編集します。

comments/create.js.erb
$(".commentsArea").html("<%= j(render 'comments/comment', { comments: @comment.post.comments }) %>")
$(".inputComment").val('');

1行目でコメントが作成されたらcommentsAreaに部分テンプレートの内容を更新するという記述をしています。
2行目はコメントを入力するinputエリアの値をリセットする記述です。なお記述を簡略にするためにjQueryで記述しています。

以上で非同期でのコメント機能が実装できていると思います!

4
8
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
4
8