『非同期でのメッセージ投稿』が理解できる最低限のRailsアプリを丁寧に作る（Ajax苦手の自分とお別れしよう）

# この記事の基本的な方針
Ajaxはなんだか難しい！は、勘違いです。一歩一歩きちんと進んでいけば、普通のことだと思えてくるでしょう。
ここではAjax非同期通信を理解するためだけの簡易なアプリを一つ丁寧に作成して、Ajax学習の基礎を完了することを目的としています。

この記事は、以下の「登録画面」「ログイン画面」「TOP画面」の3画面の簡単なアプリを元に拡張していきます
【TOP画面(ログイン前)】             　　　 【TOP画面(ログイン後)】
<img width="230" display="inline" alt="a0.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559746/066baa43-6945-9174-0040-403550137cbe.png">    <img width="230" alt="a9.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559746/23eb0bd8-130a-e4f5-2c6e-2e5071388877.png">
【登録画面】
<img width="230" alt="a1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559746/a0c57e94-7fd4-45ee-34c7-bf08dcb9a4a5.png">
【ログイン画面】
<img width="230" alt="a2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559746/8c1ea7d6-3f1b-5624-71b8-50a5f16770b3.png">

手を動かしながら読みたいようでしたら、以下でこの3画面アプリを手に入れてください。

```Terminal:Terminal
$ git clone -b 超最低限のRailsアプリ（messageコントローラVer）  https://github.com/annaPanda8170/minimum_rails_application.git
$ bundle install
$ bundle exec rake db:create
$ bundle exec rake db:migrate
```
これ自体の作り方は[こちら](https://qiita.com/annaPanda8170/items/8856425be9d563fbffa7)。

# 想定する読み手
既に一度Railsアプリをチュートリアルやスクール等で作ったことがある方であり、JQueryの基本文法を理解している方を想定しております。
Mac使用で、パソコンの環境構築は完了していることが前提です。
# 取り急ぎ同期通信のメッセージ投稿機能をつくる
※本筋ではないので急ぎ足で説明します。詳しい説明が必要な方は、[別記事](https://qiita.com/annaPanda8170/items/c042d533a3afb28213ee)をご覧ください（newアクションを使わず、formをindexに置くという違いがあります）。
## ① メッセージテーブルを作る
`マイグレーションファイル`と`モデル`を作るため、

```Terminal:Terminal
$ rails g model message
```


を打ちます。
マイグレーションファイルに

```ruby:db/migrate/2020xxxxxxxxxx_create_messages.rb
class CreateMessages < ActiveRecord::Migration[5.2]
  def change
    create_table :messages do |t|
      t.string :message
      t.references :user, foreign_key: true
      t.timestamps
    end
  end
end
```

となるように追記し、

```Terminal:Terminal
$ rails db:migrate
```

します。
これを済ませたら、メッセージテーブルは完成です。一応ちゃんと出来ているかデータベースを見に行ってみましょう。
<img width="441" alt="mysql.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559746/2cb082f0-485a-b707-cb4c-0139cfb6098b.png">
私はSequelProを使っていてこんな感じです。

モデルにテーブル同士の関係を書きましょう。これが無くても投稿できなくはないのですが、投稿した内容を引き出して扱う上で便利なので今済ませてしまいましょう。以下を追記します。

```ruby:app/models/message.rb
belongs_to :user
```

```ruby:app/models/user.rb
has_many :messages
```

## ②メッセージだけを投稿できるようにする
ルーティング、ビュー、コントローラを編集します。今回はindexにフォームを置くのでnewアクションはなしです。

```ruby:config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  root 'messages#index'
  resources :messages, only: [:index, :create]
end
```

```erb:app/views/messages/index.html.erb
<% if user_signed_in? %>
  <%= current_user.email %>
  <%= link_to 'ログアウト', destroy_user_session_path, method: :delete %>
  <%= form_with(model: @message, local: true, class: "form") do |f| %>
    <%= f.text_field :message %>
    <%= f.submit "投稿" %>
  <% end %>
<% else %>
  <%= link_to '新規登録', new_user_registration_path %>
  <%= link_to 'ログイン', new_user_session_path %>
<% end %>
```

この時点で、`localhost:3000`でも`localhost:3000/messages`でも
<img width="202" alt="log.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559746/4e6d6742-4011-0794-749d-e919cd241adf.png">
が表示されるはずです。この時点ではこの投稿フォームただの飾りなので、データベースに保存できるよう中身を作ります。
コントローラは以下です。

```ruby:app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  before_action :to_root, except: [:index]
  def index
    @message = Message.new
  end
  def create
    @message = Message.new(message_params)
    @message.save
    redirect_to root_path
  end
  private
  def message_params
    params.require(:message).permit(:message).merge(user_id: current_user.id)
  end
  def to_root
    redirect_to root_path unless user_signed_in?
  end
end
```

これで一度投稿してみましょう。

<img width="372" alt="goodafternoon.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559746/5458ca75-71e0-c0dc-d329-85ac79cf1aab.png">
問題なさそうですね。
## ③投稿を表示
あとはTOP画面に投稿されたものを全て表示させます。以下を追記します。

```ruby:app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  〜省略〜
  def index
    @messages = Message.all
    @message = Message.new
  end
  〜省略〜
end
```

```erb:app/views/messages/new.html.erb
〜省略〜
<% @messages.each do |m| %>
  <div style="margin-top: 20px;"><span style="color: red;"><%= m.user.email %></span><%= m.message %></div>
<% end %>
```

<img width="340" alt="hello.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559746/6207b671-5ad5-a57f-497a-677ec00f3cbb.png">
大丈夫ですね。

投稿するときに画像の上の丸い矢印が一瞬`×`になって投稿が反映されると思いますが、これなしに反映されるのが非同期通信です。
## ④JQueryを導入し、turbolinksを削除する
turbolinksを削除する理由は、JQueryの動きを阻害する可能性があるからです。（リロードすればjsが動作するのに、リンクで移動すると動作しない、等）

`Gemfile`に`gem 'jquery-rails'`を加え、`gem 'turbolinks'`をコメントアウトするか消し、

```:gemfile
×  gem 'turbolinks'

   gem 'jquery-rails'
```

`bundle install`しサーバの再起動します。
`app/assets/javascripts/application.js`に`//= require jquery`と`//= require jquery_ujs`を`//= require_tree .`より上に追記し、`//= require turbolinks`を消します。

```:app/assets/javascripts/application.js
× //= require turbolinks

  //= require jquery
  //= require jquery_ujs
  //= require_tree .
```


以下を修正します。

```erb:app/views/layouts/application.html.erb`
×　<%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
   <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>

       |
       v

◯　<%= stylesheet_link_tag    'application', media: 'all' %>
   <%= javascript_include_tag 'application' %>
```

続いて`app/assets/javascripts`に`messages.js`を作ります。`app/assets/javascripts/messages.coffee`があると作成したファイルが機能しないので削除します。

```js:app/assets/javascripts/messages.js
$(function () {
  console.log("OK")
});
```

を書いて、ブラウザをどの画面でもいいのでリロードします。
コンソールにOKが表示されたら成功です。

これで準備は終わりです。
# いよいよ本筋、非同期実装
[完成品GitHub](https://github.com/annaPanda8170/minimum_rails_application/tree/%E3%80%8E%E9%9D%9E%E5%90%8C%E6%9C%9F%E3%81%A7%E3%81%AE%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E6%8A%95%E7%A8%BF%E3%80%8F%E3%81%8C%E7%90%86%E8%A7%A3%E3%81%A7%E3%81%8D%E3%82%8B%E6%9C%80%E4%BD%8E%E9%99%90%E3%81%AERails%E3%82%A2%E3%83%97%E3%83%AA%E3%82%92%E4%B8%81%E5%AF%A7%E3%81%AB%E4%BD%9C%E3%82%8B%EF%BC%88Ajax%E8%8B%A6%E6%89%8B%E3%81%AE%E8%87%AA%E5%88%86%E3%81%A8%E3%81%8A%E5%88%A5%E3%82%8C%E3%81%97%E3%82%88%E3%81%86%EF%BC%89)（masterではなく一つのブランチなので注意して下さい）
## ①投稿ボタンを押すとイベント発火させる
以下のようにjsファイルを直しフォームの投稿ボタンが押されたときにコンソールに`Ok`が出てくるか確認します。
function後の()にeをお忘れなく。

```js:app/assets/javascripts/messages.js
$(function (e) {
  $(".form").on("submit", function () {
    console.log("Ok")
  })
});
```

一瞬だけ表示されてすぐに消えますね。投稿されたらTOP画面に（つまり同じ画面に）リダイレクトするのですから当然ですね。今はリダイレクトせずに投稿が反映されるようにするためにこの動きをjs内で止めます。以下に直してください。

```js:app/assets/javascripts/messages.js
$(function () {
  $(".form").on("submit", function (e) {
    console.log("Ok")
    e.preventDefault();
  })
});
```

これで`Ok`が残るようになりました。
## ②formの情報をjsで受け取り、createアクションに渡して投稿する
まずjsでformの情報を受け取る型は以下のような感じです。

```js:app/assets/javascripts/messages.js
$(function () {
  $(".form").on("submit", function (e) {
    e.preventDefault();
    $.ajax({
      url:  (1) ,
      type: (2) ,
      data:  (3) ,  
      dataType: 'json',
    })
  })
});
```

加わったのは`$.ajax`のくだりですね。
4つの項目がありますが、`dataType`はとりあえず`'json'`でいいです。jsonとはデータの形式で、`{a: b,c: d}`みたいなやつです。Rubyでいうハッシュ、JavaScriptでいくオブジェクトですね。簡単です。
（json以外にも、XMLやHTMLでもできるみたいですね）

(1)〜(3)を埋めて行きます。
(1)はcreateアクションにいくurlです。`rails routes`で確認すれば一発ですね。今回の私の場合は`/messages`です。
(2)は、HTTPメソッドです。createアクションに行くので、`'POST'`ですね。
(3)は、検証ツールでメッセージを書き込むinputタグのname属性をみればわかります。
<img width="636" alt="jjjj.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559746/c2411a6e-55cf-3546-23fd-cf57fc9b489b.png">
ありました。このように参照される値なので、`{message: {message: <投稿内容> }}`のように渡せばいいですね。
では投稿内容はどうすれば良いかというとidが`message_message`になっているので、`$("#message_message").val()`で取れます。（詳しい説明は省きます）
これを埋めて、投稿完了したときにたどり着くdoneメソッドをajaxメソッドに連ねて書きます。

```js:app/assets/javascripts/messages.js
$(function () {
  $(".form").on("submit", function (e) {
    e.preventDefault()
    $.ajax({
      url:  "/messages" ,
      type: "POST" ,
      data:  {message: {message: $("#message_message").val() }} ,  
      dataType: 'json',
    }).done(function (data) {
      console.log("ok");
    });
  })
});
```

これで一度投稿してみましょう。データベースを見れば投稿は成功しているのがみて取れます。
以下に同期・非同期の両方でのターミナルでの状態を掲載します。

<img width="812" alt="html.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559746/5fd101e9-2bab-5277-7817-3e03dc57cb86.png">
<img width="819" alt="json.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559746/28c798a4-f208-354c-b065-f98bba6903f1.png">

このような違いが出てますね。そしてなぜかコントローラのcreateアクションの最後の`redirect_to root_path`が効かなくなりました。今コンソールに`ok`は見られません。コントローラに残って機能しなくなった`redirect_to root_path`が阻害しているようです。これを削除してもう一度投稿すれば、コンソールに`ok`が見られるはずです。

このあと,投稿が完了した時に、formの値を全てなくして、投稿ボタンを蘇るようにします。
formの値をまとめてなくすには`$('.form')[0].reset();`を追記します。`[0]`がなぜ必要なのかはよくわかりません。
続いて投稿ボタンはerbファイルでボタンに適当にidを指定して（私は`<%= f.submit "投稿", id:"bbb" %>`こうしました）、`$('#bbb').prop('disabled', false);`を追記します。
全体を見てみます。


```js:app/assets/javascripts/messages.js
$(function () {
  $(".form").on("submit", function (e) {
    e.preventDefault()
    $.ajax({
      url:  "/messages" ,
      type: "POST" ,
      data:  {message: {message: $("#message_message").val() }} ,  
      dataType: 'json',
    }).done(function () {
      $('.form')[0].reset();
      $('#bbb').prop('disabled', false);
    });
  })
});
```
これでリロードしなくでも、何回でも投稿できるようになりました。

あとは表示できるようにすればOKですね。
## ③投稿内容を非同期で表示させる
Ajaxでやってきたデータを扱うには`respond_to`メソッドを使います。

createアクションの最後の`redirect_to root_path`があった場所に、以下を追記します。

```ruby:app/controllers/messages_controller.rb
respond_to do |format|
  format.json {render json: { ccc: @message.message , ddd: @message.user.email}}
end
```

メッセージ投稿内容とメッセージを送った人のEmailをそれぞれcccとdddに格納してjson形式でレンダーしますよってことですね。

あとは、doneイベント内で情報を受け取ってHTMLに整形してappendするだけです。
doneイベント内のfunction後の`()`内に何か文字をおけばそこに上のjsonデータが格納されます。今回は`eee`としてみました。これをコンソール出力してみます。

```js:app/assets/javascripts/messages.js
〜省略〜
}).done(function (eee) {
  console.log(eee);
  $('.form')[0].reset();
  $('#bbb').prop('disabled', false);
});
〜省略〜
```

これで投稿してみると、コンソールで

```js:Output
{ccc: "ハロー", ddd: "aaa@aaa"}
```

大丈夫そうですね。　これが`eee`の中に入っているわけですから、"ハロー"を取得するには`eee.ccc`で、"aaa@aaa"を取得するには…大丈夫ですね。

あとは、これを表示させます。appendするために

```erb:app/views/messages/new.html.erb
〜省略〜
<% @messages.each do |m| %>
  <div style="margin-top: 20px;"><span style="color: red;"><%= m.user.email %></span><%= m.message %></div>
<% end %>
```

これをdivタグで囲って、適当なidをつけます。今回は`aaa`としました。

```erb:app/views/messages/new.html.erb
〜省略〜
<div id="aaa">
  <% @messages.each do |m| %>
    <div style="margin-top: 20px;"><span style="color: red;"><%= m.user.email %> </span><%= m.message %></div>
  <% end %>
</div>
```
あとは

```js:app/assets/javascripts/messages.js
$("#aaa").append(`<div style="margin-top: 20px;"><span style="color: red;">${eee.ddd}</span>${eee.ccc}</div>`)
```

を追記するだけです。
これで完成です。投稿して確認してください。

最後に再掲します。

```js:app/assets/javascripts/messages.js
$(function () {
  $(".form").on("submit", function (e) {
    e.preventDefault();
    $.ajax({
      url: "/messages",
      type: "POST",
      data: { message: { message: $("#message_message").val() } },
      dataType: 'json',
    }).done(function (eee) {
      $('.form')[0].reset();
      $('#bbb').prop('disabled', false);
      $("#aaa").append(`<div style="margin-top: 20px;"><span style="color: red;">${eee.ddd}</span>${eee.ccc}</div>`)
    });
  })
});
```

```erb:app/views/messages/new.html.erb
<% if user_signed_in? %>
  <%= current_user.email %>
  <%= link_to 'ログアウト', destroy_user_session_path, method: :delete %>
  <%= form_with(model: @message, local: true, class: "form") do |f| %>
    <%= f.text_field :message %>
    <%= f.submit "投稿" , id: "bbb"%>
  <% end %>
  <div id="aaa">
    <% @messages.each do |m| %>
      <div style="margin-top: 20px;"><span style="color: red;"><%= m.user.email %> </span><%= m.message %></div>
    <% end %>
  </div>
<% else %>
  <%= link_to '新規登録', new_user_registration_path %>
  <%= link_to 'ログイン', new_user_session_path %>
<% end %>
```
# まとめ
本当に最低限です。

これを、整えて十分な状態にするための続きをまた書きます。
フォローしてお待ち下さい。