RailsでAjaxとjQueryを使ってコメントの編集方法

Ajax化概要

ビューのフォームでアクションを指定し、コントローラーでそれを実行するのだが、remote: trueが記入されていることで、そのアクション名.js.erbというファイルが実行され、その中にjqueryコードを書いておく。
そのjqueryコードが実行され、実際にビューに反映されるものはrubyのコードなので大抵、パーシャルhtml.erbに書き込まれrenderされる。

準備

今回、このコメントの編集と削除をjqueryajaxを使って実装していく。

f:id:shoheimoment:20171209154022p:plain

まず、初めのビューファイル view/pictures/index.html.erb

<div class="other_comments">
  <% if picture.other_comments.present? %>
    <% all_other_comments = picture.other_comments.order(created_at: :asc) %>
    <% all_other_comments.each do |each_other_comment| %>
      <div class="for_edit_comment">
        <%= render 'other_comments/each_other_comment', each_other_comment: each_other_comment %>
      </div>
    <% end %>
  <% end %>

render部分 view/other_coments/_each_other_comment.html.erb

<div class="each_other_comment row" data-comment="<%= each_other_comment.id %>">
  <p class="col-xs-10"><strong><%= each_other_comment.user.name %></strong>&nbsp;&nbsp;<span id="for_edit"><%= each_other_comment.other_comment %></span></p>
  <div class="col-xs-2">
    <div class="dropdown">
      <button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
      <span class="caret"></span>
      </button>
      <ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
      <li><%= link_to '編集', edit_other_comment_path(each_other_comment.id), remote: true %></li>
      <li><%= link_to '削除', other_comment_path(each_other_comment.id),
      remote: true, data: { confirm: '削除しますか?' },
      method: :delete %>
      </li>
      </ul>
    </div>
  </div>
</div>

上の写真では写ってはいないがマウスオーバーすると編集削除ボタンがでる。

f:id:shoheimoment:20171209161705p:plain

 編集ボタンを押すとフォームに変わる

_each_other_comment

<li><%= link_to '編集', edit_other_comment_path(each_other_comment.id), remote: true %></li>

ここのremote: trueを加えることにより、ajaxで行うことを意味する。

※フォームに直すのはjqueryだけでできるが、要素の取得がajaxでやれば簡単だったので今回はajaxで行う。 編集ボタンを押すとother_commentコントローラーのeditアクショへ行く。

other_comments_controller.rb #edit

def edit
    @other_comment = OtherComment.find(params[:id])
    respond_to do |format|
      format.html { redirect_to pictures_path }
      format.js { @id_comment = @other_comment.id }
    end
  end

ajaxで命令が来ると、format.jsが適応されて、デフォルトでview/other_comments/edit.js.erbが呼ばれて実行される。

view/other_comments/edit.js.erb

var idComment = <%= @id_comment %>;

$('.row[data-comment="'+idComment+'"]').parent().html("<%= escape_javascript(render 'edit_other_comment') %>");

他の人のコメントはeach文繰り返し処理を行っているので、classやidだけで要素を取得するのが難しい。そこであらかじめdivタグにdata-comment属性にそのレコードのid値を持たせておく。
この部分 view/other_coments/_each_other_comment.html.erb

<div class="each_other_comment row" data-comment="<%= each_other_comment.id %>">

それを元に取得している。 これがajaxでこの処理を行った理由。

なので、edit.js.erbでは取得した要素の一つ上の親要素(for_edit_comment)を取得してhtmlをrender先の_edit_other_commentに書き換えなさいという意味。
※ここでparent()メソッドを使っているのは、html()メソッドは取得した要素の子孫要素を書き換えるメソッドであり、その要素自体を取得して書き換えると、コメントを編集する度に取得した要素が加えられていってしまい、cssがおかしくなったりするから。

次にedit.js.erbからrenderされる部分
_edit_other_comment

<div class="each_other_comment row" data-comment="<%= @id_comment %>">
  <%= form_with(model: @other_comment, remote: true) do |form| %>
    <div class="add_other_comment row">
      <%= form.text_area :other_comment, rows: 3, placeholder: "コメントを追加...", class: "col-xs-11" %>
      <%= form.submit '投稿する', data: { confirm: '投稿しますか?' }, class: 'btn btn-default' %>
    </div>
  <% end %>
</div>

これで編集ボタンを押すとフォームに切り替わる。 f:id:shoheimoment:20171209164103p:plain

コメントを更新する

ここからコメントを更新する作業である _edit_other_comment

 <%= form_with(model: @other_comment, remote: true) do |form| %>

この部分のremote: trueでajax化している。先ほどと同様にコントローラーでDBの命令を出すと同時にupdate.js.erbが実行される。

other_comments_controller.rb #update

def update
    @other_comment = OtherComment.find(params[:id])
    respond_to do |format|
      if @other_comment.update(other_comment: params[:other_comment][:other_comment])
        format.html { redirect_to pictures_path }
        format.js { @id_comment = @other_comment.id }
      else
        format.html { render 'pictures/index' }
        format.js { @status = 'fail' }
      end
    end
  end

update.js.erb

var idComment = <%= @id_comment %>;

$('.row[data-comment="'+idComment+'"]').parent().html("<%= escape_javascript(render 'each_other_comment', each_other_comment: @other_comment) %>");

編集の時と同様に、取得した要素のhtmlを書き換えている。 render先は上の方で書いているview/other_coments/_each_other_comment.html.erbである。 これで投稿ボタンを押すとajaxで更新される。

f:id:shoheimoment:20171209170119p:plain

まとめ

link_toのedit→#edit→edit.js.erb→edit_other_comment.html.erb→元のビューに反映されてフォームの記入ができるようになる。→#update→update.js.erb→each_other_comment.html.erbで更新される