この記事の目的
非同期処理(ajax)でのコメント投稿機能を学んだのでアウトプットします
目標
要件
コメント投稿は非同期で行う
環境
・Ruby 2.7.1
・Rails 5.2.6
前提
・UserとPostは作成済み
・bootstrapとjquery-railsが入っている
設計
ルーティング
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
実行
モデル
class Comment < ApplicationRecord
belongs_to :user
belongs_to :post
validates :body, presence: true, length: { maximum: 1000 }
end
has_many :comments, dependent: :destroy
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
でも同様。
ルーティング
resources :posts, shallow: true do
resources :comments
end
※ 上述したルーティング設計に合わせるため、shallowルーティングを使用している
(参考:Railsの”shallow(浅い)”ルーティングを理解する)
コントローラー
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.find
をComment.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の記述
<% 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_javascript
はj
と省略して書くこともできる
※ 最初の<% 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>
検証ツールで実際の挙動を確認
※ コメントを投稿したとき、画面遷移はせずに<div id="comment-29">...</div>
だけが追加されていることがわかる
htmlの記述(投稿されたコメントの表示画面)
・
・
・
<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>
<div class="comments-box">
<%= render comments %>
</div>
※ commentsは@comments。つまりrender @comments
で_comment.html.erbに飛ばしているのと同じ(each文省略するやつ)
( @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の記述(コメントの入力フォーム)
<%= 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
にしたら、/comments
にPOST
リクエストが送られるが、ルーティングの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引用)