はじめに
Railsアプリに非同期通信のコメント機能を実装しました。
非同期通信は一言で言えば、画面遷移をせずにページを更新する技術です。
備忘録として手順をまとめたいと思います。
実装イメージ
開発環境
- macOS Catalina
- Ruby 2.6.5
- Ruby on Rails 5.2
目次
1.モデルとアソシエーション
2.ルーティング
3.コントローラー
4.view
1. モデルとアソシエーション
モデル構成とアソシエーションはよくある構成です。
前提としてuserとpostモデルは作成済みとします。
commentモデル作成
$ rails g model comment user:references post:references content:string
$ rails g migrate
アソシエーション
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy
belongs_to :user
has_many :comments, dependent: :destroy
belongs_to :user
belongs_to :post
# バリデーション(カラの入力を無効に)
validates :content, presence: true
2. ルーティング
コメントは投稿に紐付いているのでルーティングはネストさせて記述します。
ネストさせることでアソシエーション先のレコード(今回で言えば、投稿に紐づくコメント)のidをparamsに追加してコントローラーにわたすことができるようになります。
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
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を編集します。
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
投稿詳細ページは以下のようになります。
<% 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.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を編集します。
$(".commentsArea").html("<%= j(render 'comments/comment', { comments: @comment.post.comments }) %>")
$(".inputComment").val('');
1行目でコメントが作成されたらcommentsAreaに部分テンプレートの内容を更新するという記述をしています。
2行目はコメントを入力するinputエリアの値をリセットする記述です。なお記述を簡略にするためにjQueryで記述しています。
以上で非同期でのコメント機能が実装できていると思います!