ブログなどの投稿ページにマストなコメント投稿、コメント削除機能をAjaxを用いて動的に作ってみました。
コメント投稿、削除でいちいちページ遷移するより圧倒的に使いやすいです。
なんとか実装できましたが、めちゃめちゃ時間かかって苦労しました。。。
ネット上に自分の求めていたジャストの記事が無かったので、今回の実装をまとめてみました!
決意のツイート:
Railsに関してでネットでジャストな記事が見つからなかったから自分で書いてみようと思い立って、初めてブログ書いてみてるけどめちゃくちゃ時間かかる。。。でも、めちゃくちゃ勉強になる!説明しようとすると細かいところが全然理解できてないのがよく分かる。明日公開できるように頑張ろう。
— ゆうすけ@プログラミング (@ysk_pro) 2018年2月7日
(ツイッター始めてまだ一週間くらいですが、今までで一番いいねをいただいたのでこの記事頑張ろうと思いました。ありがとうございます。)
実装の内容自体は基本的なことであり現役プログラマーの方からすれば当たり前のことばかりだと思いますが、色々と調べながら実装したことを書いてみます。
細かい部分まで極力書いているので一つでも勉強になることがあれば嬉しいです。
- 1. 完成イメージ
- 2. スキーマ(コメントテーブル作成)
- 3. モデル(アソシエーション設定)
- 4. ルーティング
- 5. コメントコントローラー
- 6. 投稿のコントローラー
- 7. 投稿のビュー
- 8. パーシャル部分のビュー
- 9. jsファイル
- 10. 最後に
- 11. 追記
- 12. 追記2
1. 完成イメージ
デザインはかなりダサくて恥ずかしいですが、完成のイメージはこんな感じです。(もちろんこれからかっこよくするところです)
現在Mrサンプルさんでログイン中の為、下のコメントのみ削除ボタンが出ており、コメント・削除ボタンを押すと、ページ遷移することなくコメント投稿・削除ができ、スムーズにコメント投稿・削除ができます。
投稿ページの実装、JQueryの読み込みはできている前提です。
さていってみましょう!
※バージョンは、Ruby:2.3.0、Rails:5.1.4です。
まずは、コメントモデル・コメントコントローラーを作成します。
私はまとめてscaffoldで作成しました。(不要なものもできてしまうのですが問題ありません)
2. スキーマ(コメントテーブル作成)
config/db/schema.rb
create_table "comments", force: :cascade do |t| t.text "content" t.integer "post_id" t.integer "user_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false end
どの投稿に対するコメントであるかを格納するために「post_id」、誰の投稿であるかを格納するために「user_id」カラムの作成が必要です。
3. モデル(アソシエーション設定)
app/models/comment.rb
belongs_to :user belongs_to :post validates :content, presence: true
ユーザーはたくさんのコメントを持てる、投稿はたくさんのコメントは持てると、一対他(ユーザー・投稿:一、コメント:他)の関係になっているので、コメントのモデルは「berongs_to」でのアソシエーションになります。
コメントの内容が無いとコメントの意味が無いので、コメント内容必須のバリデーションを設定しています。
app/models/post.rb
belongs_to :user has_many :comments, dependent: :destroy
ユーザーはたくさんの投稿を持てる、投稿はたくさんのコメントを持てる、の関係なので投稿のアソシエーションはこのようになります。
「dependent: :destroy」は、この場合「投稿が削除された時に、同時にコメントも消去する」という意味です。
コメントだけ残ってしまっても意味が無いので記載が必要です。
app/models/user.rb
has_many :posts, dependent: :destroy has_many :comments, dependent: :destroy
同様の考え方です。
4. ルーティング
config/routes.rb
resources :posts do resources :comments end
コメントがどの投稿へのものであるかを識別するために、ルーティングのURLに投稿のIDを含める必要があります。
具体的には「/post/12/comment/」といったURLになります。(ネストする、と言います。)
12がpost_idです。
5. コメントコントローラー
app/controllers/comment.rb
def create @post = Post.find(params[:post_id]) #① @comment = @post.comments.build(comment_params) #② @comment.user_id = current_user.id #③ if @comment.save render :index #④ end end def destroy @comment = Comment.find(params[:id]) #⑤ if @comment.destroy render :index #⑥ end end private def comment_params params.require(:comment).permit(:comment_content, :post_id, :user_id) end
createアクション:
#①:コメントをする対象の投稿(post)のインスタンスを作成します。
#②:「.build」を使うことで、@postのidをpost_idに含んだ形でcommentインスタンスを作成します。
「.new」で普通にインスタンスを作成して、次の行でpost_idを入れても同じです。
参考
build - リファレンス - - Railsドキュメント
#③:現在のuserのidを入れます。
#④:保存がされると、render :indexによって「app/views/comments/index.js.erb」を探しにいきます。
「form_with」でフォームを送信した時は、デフォルトでjsファイルを探しにいく設定になっています。
htmlファイルを探しにいってほしい場合には、form_withの後に「local: true」と記載する必要があります。
参考:form_withについて
rails-ujs と form_with の使い方 - ボクココ
参考:renderについて
レイアウトとレンダリング | Rails ガイド
destroyアクション:
#⑤:削除する対象のコメントインスタンスを探します。
#⑥:削除がされると、「index.js.erb」を探しにいきます。
削除のリンクを記載している「link_to」の中に「remote: true」を記載していることでjsファイルを探しにいってくれます。
7. app/views/comments/_index.html.erb に記載しています。
「remote: true」を記載していなかった場合は、htmlファイルを探しにいきます。
6. 投稿のコントローラー
app/controllers/posts_controller.rb
def show @post = Post.find(params[:id]) @comment = Comment.new #① @comments = @post.comments #② end
どちらも、7. 投稿のビュー「app/views/posts/show.html.erb」でパーシャルに渡す変数として使用します。
#①:入力フォームで使用するインスタンスを作成しています。
#②:コメント一覧表示で使用するためのコメントデータを入れています。
7. 投稿のビュー
app/views/posts/show.html.erb
<div> <h4>コメント</h4> <div id="comments_area"><!-- #① --> <!-- 投稿されたコメント一覧をブログの詳細ページに表示するためのrender --> <%= render partial: 'comments/index', locals: { comments: @comments } %> </div> <% if user_signed_in? %> <!-- コメント入力欄をブログの詳細ページに表示するためのrender --> <%= render partial: 'comments/form', locals: { comment: @comment, post: @post } %> <% end %> </div>
#①:「id="comments_area"」がポイントです。
このidをターゲットにして、このdiv内をAjaxで書き換えます。
このdivの内側に、renderを使ってパーシャルを表示します。
@commentをパーシャル内で使うローカル変数commentとして渡しています。
参考:パーシャルを利用するときのrenderの使い方について
render - リファレンス - - Railsドキュメント
参考:ローカル変数について
https://kadoppe.com/archives/2013/09/rails-partial-template-instance-variables.html
どちらもとても勉強になりました。
8. パーシャル部分のビュー
app/views/comments/_index.html.erb
<% comments.each do |comment| %> <% unless comment.id.nil? %> <p><%= link_to "#{comment.user.name}さん", user_path(comment.user.id) %></p> <p>コメント:<%= comment.content %></p> <% if comment.user == current_user %> <p><%= link_to 'コメントを削除する', post_comment_path(comment.post_id, comment.id), method: :delete, remote: true %></p> <% end %> <% end %> <% end %>
パーシャルはファイル名の先頭に「_」を入れます。
投稿のビューから渡したローカル変数(comments)を comment に入れて一つずつ表示しています。
ポイントは、コメントの削除のところで「(comment.post_id, comment.id)」と投稿のidとコメントのidを渡す必要があることと、5. コメントコントローラーのところでも触れましたが「remote: true」をつけることによって、コントローラーでjsファイルを探しにいってもらうことです。
idをcomment.post_idとcomment.idの2つ渡す必要があるのは、削除したいコメントを指定するには「post/12/comment/31」のようにpost_idとcomment_idを指定する必要があるためです。
app/views/comments/_form.html.erb
<%= form_with(model: [post, comment] ) do |form| %> <div> <%= form.text_area :comment_content %> </div> <div class="actions"> <%= form.submit "コメントをする" %> </div> <% end %>
ポイントは、「model: [post, comment]」とすることです。
post, commentはそれぞれ、7. 投稿のビューで渡しているインスタンスのローカル変数です。
投稿に紐づいたコメントを生成するため、ここでpost、commentのインスタンスを渡すことが必要になります。
9. jsファイル
app/views/comments/index.js.erb
$("#comments_area").html("<%= j(render 'index', { comments: @comment.post.comments }) %>") $("textarea").val('')
とてもシンプルです。
このファイルに、7. 投稿のビューの中で id = "comments_area"とした箇所を書き換える処理を記載しています。
「$("#comments_area")」が id = "comments_area"をターゲットとする記載です。
ターゲットとした箇所を、「render 'index'」で指定している8. パーシャル部分のビューの内容で書き換えています。
{ comments: @comment.post.comments }で、@comment.post.comments をローカル変数 comments に入れて渡しています。
@comment.post.comments は、コメント一覧表示するのに必要なコメント全件です。
「$("textarea").val('')」によって、コメント入力後のコメント入力欄を空にしています。
10. 最後に
以上で、Ajaxを用いた動的なコメント投稿・削除機能が実装できたはずです!
※分かりやすくするために、デザイン面のbootstrapに関するコードは消しています。
間違っている箇所、分かりにくい箇所等あれば是非教えてください!
ブログを書いてみた感想:
Rails関するブログを書いてみてよかったこと。①めちゃ勉強になる。間違ったことを書けないからRails公式ガイド見まくる。そうしたら知らなかったことがたくさん!②コードがきれいになる。今までブログ等のコピペをしていたことで、実際必要のない部分、無駄に長い書き方をしていた箇所を修正できた!
— ゆうすけ@プログラミング (@ysk_pro) 2018年2月8日
(Twitterは自分と同じような境遇、目指すべき人たちと気軽に繋がれるのがすごいいいですよね!始めてみて良かったです。)
次は、いいね機能をAjaxを用いて動的することと、コメントに対しての返信機能をつけようと思っています。
ジャストな記事がなかったらまたブログ書こうと思います!!
(リクエスト等あればお答えしたいです。もし、もし感想、ご意見等いただければ次回も頑張ろうと思えます、、、笑)
11. 追記
Ajaxを用いた動的ないいね機能・コメントへの返信機能の実装が完了しました!コメント返信機能・またその動的な実装はジャストな記事がなかったので、次の記事にするかもしれません。
12. 追記2
このコメント機能を実装して、初めてのwebサービスを作ることができたので、ぜひこちらも合わせてご覧ください!
ysk-pro.hatenablog.com
また、僕が通っていたプログラミングスクール / DIVE INTO CODEに通った感想・できるようになったことをまとめましたのでご興味あれば見てみてください。
ysk-pro.hatenablog.com