Ajaxを用いたコメント削除機能を実装するにあたり詰まった点がいくつかあったので、記録として残しておきます。
実装イメージは以下の通りです。
詰まった点は以下の2点です。
1.ネストされているコメントに対する削除方法(引数をどのように渡せばいいか)
2.js.erbファイルの記述方法
この2点に対する解決方法を記述をします。(解決までに自分が行ったことも書いていくため、長くなります。)
環境は以下のとおりです。
ruby 2.6.5
Ruby on Rails 6.0.3.3
1.ネストされているコメントに対する削除方法(引数をどのように渡せばいいか)
まずは同期通信で削除ができるように実装を進めていきましたが、いきなり躓きました。ネストされている機能(今回の場合はコメントに関する機能)を削除したことがなかったからです。
とりあえず引数どうすればよいのかと考え以下の様にしました。
①コメントのidを渡す
単純にコメントのidを渡してどのような動きになるのか確認をしました。
<% @comments.reverse.each do |comment| %>
<div class="comment">
<%=link_to image_tag("delete.png", class: "delete-btn"), laundry_comment_path(comment.id), method: :delete %>
(略)
</div>
id=11のコメントなどないと怒られました。実際データベースを確認したところid=11のコメントはありませんでした。
不思議に思いつつbinding.pryで確認してみたところ何故か店舗のidとコメントのidが逆に渡されていました。
(本来はlaundry_idが11でid(コメントのid)が123でないといけません。)
何故このようになってしまったのかはわかりませんがとりあえず違うということで別の方法を考えます。
※結果として今でも分からずにいます。わかる方いましたらご教授ください。
②インスタンス変数を渡す
ネットで検索をしていたところ以下のQiita記事を発見しました。
[Rails]ネストしたコメントの削除機能の作成
まさに私が探し求めていたものだ!ということで早速実践。
<%=link_to image_tag("delete.png", class: "delete-btn"), laundry_comment_path(@laundry,comment.id), method: :delete %>
引数を@laundry,comment
の2つにしました。(@laundryはコントローラーで定義しています。)
実行してみましたがエラーがでてうまくいきませんでした。(そもそもブロック変数を用いる式の中でインスタンス変数を用いることってあるのか?という疑問が残りました)
ここでネストをしている時は2つ引数を渡す必要があると気づきパスを確認してみたところ/laundries/:laundry_id/comments/:id
というパスになっていました。
普段も:id
の部分がわかるように引数を渡しており、今回もlaundry_idとコメントのidが渡せればいいためブロック変数のcomment
からlaundry_idを渡すために以下のように引数を指定して解決しました。
解決策
<%=link_to image_tag("delete.png", class: "delete-btn"), laundry_comment_path(comment.laundry_id, comment.id), method: :delete %>
ネストがあったとしても基本は同じで、どこのパスにいくのか、そのためにはどの引数が必要なのかを判断する必要があると学びました。
2.js.erbファイルの記述方法
同期通信が実装できたため、次は非同期の実装に取り掛かります。
基本的にはlink_to
に対してremote: true
のオプションをつけることで非同期通信が実行されてhoge.js.erbファイル(今回はdestroy.js.html)を探してレンダリングするという流れですが、destroy.js.html.js.erbの記述でまた躓きました。
そもそもjs.erbファイルを記述したことがなかったためjs.erbファイルとは?というところから始まったのですが要点をまとめると以下のような感じになるかと思います。
・js(ajax)リクエストが行わたときにレンダリングされるファイル。
・Javascriptを用いて記述ができる。
・ERBの記法(<%= >や<% >)が使用できる
Javascriptが使えるのか〜と思いながら削除機能に関するjs.erbファイルの書き方等を調べていると全部と言ってもいいぐらいネットの記事ではjQueryで書かれていました。
そこまで記述量多くなさそうだし素のJavascriptでやってみようと思い、Javascriptで記述をすることにしました。
const commentArea = document.getElementById('comment-area');
commentArea.innerHTML = "<%= render partial: 'index', locals: { comments: @comments } %>";
とりあえず必要な要素を取得してそこにrenderで部分テンプレートを埋め込めばOKそうだったのでこのように記述して実行をしてみました。
結果
Uncaught SyntaxError: Unexpected identifier
エラーが表示される。
該当箇所を確認すると以下のように表示されました。
エラー内容とエラー箇所からダブルクォーテーションの中身がダブルクォーテーションなので構文エラーになっているのでは?とご指摘いただき修正したのですが同じエラーが表示されてしまいました。
解決方法
`<%= render partial: 'index', locals: { comments: @comments } %>`;
バッククオートで囲むことで解決しました。
とりあえずできるようにはなったのですが、続いて1回しか削除ができない(読み込みをしないで別のコメントを削除しようとするとエラーが表示)という事象が発生しました。
const commentArea = document.getElementById('comment-area');
commentArea.innerHTML = `<%= render partial: 'index', locals: { comments: @comments } %>`;
Identifier 'commentArea' has already been declared
commentArea
は宣言されているよということで「定数だから1回しか宣言できないのか」ということでconst→letに変更
let commentArea = document.getElementById('comment-area');
commentArea.innerHTML = `<%= render partial: 'index', locals: { comments: @comments } %>`;
変更後実行してみましたが再度同じエラーが出現。
「定数やめたのになんでよ」と思いましたが確かにletを使うのは変数を定義するときだけで値を変更する時はlet使わないよな〜ということでletを削除
commentArea = document.getElementById('comment-area');
commentArea.innerHTML = `<%= render partial: 'index', locals: { comments: @comments } %>`;
・・・これ、もう2行に分ける必要ないじゃんということで1つにまとめてようやく実装できました。
document.getElementById('comment-area').innerHTML = `
<%= render partial: 'index', locals: { comments: @comments } %>`;
すごく遠回りをした気がしますが、なんとが実装ができて一安心という感じです。
ですがとりあえずできた段階で理解には全然達していないため、理解を深めていきます!