Ruby on Rails プログラミング

『Rails』非同期通信(Ajax)を使用して、コメント「投稿」「削除」を実装

masahide

14年間務めた大手自動車会社を退職 TECH CAMP短期集中コース受講 地元の自社、受託開発企業へ就職 やればできるを発信しています。

4月20日〜6月末(76期)までテックキャンプ エンジニア転職コースを受講し、現在IT企業へ転職活動中のまさひで(@john01tgmck)です。

テックキャンプ では、基礎〜応用カリキュラムが終わった後に、個人アプリ開発を行います。

参考質問NG!?テックキャンプ の個人アプリは孤独との戦い

期間は1週間!?ポートフォリオの必要性と作成難易度をまとめています。できるだけ早くカリキュラムを終わらせる事がオススメ

続きを見る

作成したWEBアプリ 公園を共有して親子の絆を深めよう『Park Connect』

非同期通信と自動更新を使用して、チャットっぽく見えるようになっています。

ただ、レスポンシブ対応していなかったり、応用カリキュラムの転用になっていたので、Railsの復習も兼ねて新しくアプリを立ち上げました。

そこで、非同期通信がワンライナーで実装できる記事を見つけたので、つまづいた部分も解説していきます。

完成したアプリ

環境

  • Ruby 2.5.1
  • Rails    5.2.4
  • Mysql  14.14
  • CSSフレームワーク:Bluma使用
masahide(筆者)
投稿機能、削除機能は作成済みからスタートします。

 

アプリケーションの構造

テーブル構造

Schema.rb

create_table "comments", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
    t.string "content"
    t.bigint "user_id"
    t.bigint "post_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["post_id"], name: "index_comments_on_post_id"
    t.index ["user_id"], name: "index_comments_on_user_id"
  end

  create_table "likes", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
    t.bigint "post_id"
    t.bigint "user_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["post_id"], name: "index_likes_on_post_id"
    t.index ["user_id"], name: "index_likes_on_user_id"
  end

  create_table "posts", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
    t.integer "user_id"
    t.string "title"
    t.text "body"
    t.string "image_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
    t.string "username", null: false
    t.text "profile"
    t.string "profile_image_id"
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

  add_foreign_key "comments", "posts"
  add_foreign_key "comments", "users"
  add_foreign_key "likes", "posts"
  add_foreign_key "likes", "users"

ルーティング

resources :posts do
    resources :comments, only: [:create, :destroy]
  end

どの投稿に対するコメントなのかわかるようにネストさせます。

投稿ページ

views / posts / show.html.erb

<section class="hero is-success">
  <div class="hero-body">
    <div class="container">
      <h1 class="title">
        投稿詳細
      </h1>
    </div>
  </div>
</section>

<section class="section">
  <div class="container">
    <div class="columns is-centered">
      <div class="column is-7">
        <div class="card">
          <div class="card-image">
            <figure class="image is-4by3">
              <%= attachment_image_tag @post, :image %>
            </figure>
          </div>
          <div class="card-content">
            <div class="media">
              <div class="media-content">
                <p class="title is-4">タイトル:<%= @post.title %></p>
              </div>
            </div>
            <div class="content">
              <table class="table is-narrow">
                <tr>
                  <th>内容</th>
                </tr>
                <tr>
                  <td><%= simple_format @post.body %></td>
                </tr>
                <tr>
                  <td class="display-flex">
                    <div id="likes_buttons_<%= @post.id %>">
                      <%= render partial: 'likes/like', locals: { post: @post} %>
                    </div>
                  </td>
                </tr>
              </table>
              <% if @post.user.id == current_user.id %>
                <%= link_to "編集画面へ", edit_post_path(@post), class: "button is-success" %>
              <% end %>
            </div>
          </div>
        </div>
      </div>

      <div class="column is-4">
        <article class="panel is-link">
          <p class="panel-heading">
            <%= @post.user.username %>さん
          </p>
          <div class="panel-block">
            <p class="control">
              一言:<%= @post.user.profile %>
            </p>
          </div>
          <%= link_to user_path(@post.user), class: "panel-block" do %>
            <span class="panel-icon">
              <i class="fas fa-user" aria-hidden="true"></i>
            </span>
            <%= @post.user.username %> さんのページへ
          <% end %>
        </article>
        <h2>コメント一覧</h2>
        <div class="comments_index">
          <div id='comments_area'>
            <%= render partial: 'comments/index', locals: { comments: @comments } %>
          </div>
        </div>
        <% if user_signed_in? %>
          <%= render partial: 'comments/form', locals: { comment: @comment, post: @post } %>
        <% end %>
        
      </div>
    </div>
  </div>
</section>

views/posts/show.html.erbファイル

68行目(コメント一覧)〜76行目あたりがコメントを投稿したり、表示する箇所になります。

renderで切り出して、Ajaxで差し替えやすいようにします。

それより上のコードは今回のコメント非同期とは関係ないので、割愛します。

コントローラーの構造

controllers / posts_controller.rb

def show
    @post = Post.find(params[:id])
    @comment = Comment.new
    @comments = @post.comments.order(created_at: :desc)
  end

@postにポストテーブルのレコードを代入
@commentsに新しいコメントから表示できるように並べ替えています。

class CommentsController < ApplicationController
  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(:content, :post_id, :user_id)
  end
end

保存されると、views/comments/index.js.erbを表示します。(render :index)

 

フォーム構造

form_forを使用しているので、remote :trueを記載します。

この一文を記載するだけで、対応するJSファイルを探しに行ってくれます。*コメント一覧表示であれば(index.js.erb)

views / comments / _form.html.erb

<%= form_for [@post, @comment], remote: true do |f| %>
  <div class="field">
    <div class="control">
      <%= f.text_area :content, class:"input", placeholder:"コメント" %>
      <%= f.hidden_field :post_id, value: @post.id %>
    </div>
  </div>
  <br>
  <div class="field is-grouped">
    <div class="control">
      <%= f.submit 'コメントする', class:"button is-link form__submit" %>
    </div>
  </div>
<% end %>

JSファイル作成

Views / comments / index.js.erb

$("#comments_area").html("<%= j(render 'index', { comments: @comment }) %>")
$("textarea").val('')

posts/show.html.erbファイルのid="comments_area"を書き換える処理になりす。

2行目は投稿後にフォームを空にする処理です。

masahide(筆者)

え!?こんな短くていいの??

テックキャンプ で行っていた非同期通信は、JavaScriptフォルダに〇〇.jsっていうファイルを作って、functionとかajax,done,failなどを書いていくものでした。

チャットアプリで使用していたAjax処理です。

半分くらいしか理解できていないので、ざっくり言うと、送信ボタン(submit)が押された時にmessagesクラスの表示を上書き(append)する。

何を上書きするかと言うと、htmlと言う変数です。

変数に何が入っているかは見えていませんが、どのように表示したいかと言うhtml形式の記述になります。

このような冗長なコードがワンライナーで書けるとQiitaに乗っていたので、驚きでした。

参考URL([Rails]Ajaxを用いて非同期でコメント機能の実装

上書きするファイル作成

views / comments / _index.html.erb

<% comments.each do |c| %>
  <div>
    <a href="/users/<%= c.user.id %>"><%= c.user.username %></a>
    <%= c.content %>
    <% if c.user.id == current_user.id %>
      <br>
      投稿日:<%= c.created_at.to_s(:datetime_jp) %>
        <%= link_to post_comment_path(c.post_id, c.id), method: :delete, remote: true, data: {confirm: "削除しますか?"} do %>
          <span class="panel-icon">
            <i class="fas fa-trash"></i>
          </span>
        <% end %>
      <hr>
    <% else %>
      <br>
        投稿日:<%= c.created_at.to_s(:datetime_jp) %>
      <hr>
    <% end %>
  </div>
<% end %>

このファイルで先ほどJS変換した情報を使って表示を差し替えています。

つまづいたポイント

データの受け渡しはできているのに、非同期処理されない

自分のイメージはこんな感じです。

投稿フォームから送信された内容をJSファイルに渡し、非同期表示したいファイルに上書きすることにより、その部分だけが切り替わる非同期通信が完了する。

masahide(筆者)
なぜ非同期処理されないのか?
  • binding.pry

デバックする時に、createアクションにどのようなparams(情報)が渡ってきているか調べるgemになります。

例えば、createアクションにbinding.pryと記載して、コメントを投稿すると、以下のようになります。

paramsと入力することで、どのようなデータが送信されているか調べることができます。

欲しい値が入ってきているにもかかわらず、表示が変わらなかったので、上書きするViewファイルが怪しいと思い確認。

ポイント

_index.html.erbファイル

<% comments.each do |c| %>

c に commentsを代入しているのに、表示する変数が『comment』だった・・・。

Qiita記事をコピペしていたために起こった不具合でした。

 

非同期処理はしっかりと理解しないままテックキャンプ を卒業してしまいました。

個人アプリを作る上で非同期はかなり便利!ユーザビリティ向上にも繋がるので、積極的に使っていきます。

ここまで読んでいただき、ありがとうございました。

まだまだ作成中のイクメンアプリですが、コードはこちらから確認できます。(イクメンアプリ

-Ruby on Rails, プログラミング

© 2020 まさブログ Powered by AFFINGER5