【Rails】javascriptのfetchを使って非同期通信（POST）を実装してみた

# 初めに
railsにデータを保存する際（post)にfetchを使って非同期通信を実現できるのではないかと思い、学習した内容をもとに纏めてみました。

※内容に間違いなどがある場合はご指摘をよろしくお願いします。

前回の記事：https://qiita.com/redrabbit1104/items/1ce9f665a0fcd1d99bb2
https://qiita.com/redrabbit1104/items/b8b61a72f849fa3e8881
https://qiita.com/redrabbit1104/items/02bc16cf5abd4ed10ec1
https://qiita.com/redrabbit1104/items/c131c46897bfdf86e08b
https://qiita.com/redrabbit1104/items/806ef9849f4b0962ceaa

#やりたいこと
railsの投稿formから入力した内容をfetchを使ってrequest(post)を送り、保存されたデータのresponseをブラウザに表示させること。

# 前提
Macでrailsアプリの新規作成及びdatabaseの作成などが終わっている。

# 作業手順
- ①tableを準備（modelを作成）
- ②routing
- ③view
- ④controller
- ⑤javascriptを用意する
- ⑥結果

# tableを準備（modelを作成）
まずはデータを保存するためのtableを用意します。テーブル名はtestとしました。

```cl
❯ rails g model test
      invoke  active_record
      create    db/migrate/20210502224447_create_tests.rb
      create    app/models/test.rb
      invoke    rspec
      create      spec/models/test_spec.rb
      invoke      factory_bot
      create        spec/factories/tests.rb
```
生成された20210502224447_create_tests.rbファイルを開き、カラム名がnameでstring型のとてもシンプルなカラムを作ります。

```ruby
class CreateTests < ActiveRecord::Migration[6.0]
  def change
    create_table :tests do |t|
      t.string :name
      t.timestamps
    end
  end
end
```
migrateファイルが出来上がりましたので、migrationしテーブルを生成します。

```cl
❯ rails db:migrate
== 20210502224447 CreateTests: migrating ======================================
-- create_table(:tests)
   -> 0.0399s
== 20210502224447 CreateTests: migrated (0.0400s) =============================
```

# routing
railsを起動したらトップページに入力formを表示させたいので、その記述をします。また、testモデルは画面に表示するためのページ（view)と保存するためのcontroller側の記述が必要なため、resourcesをindexとcreateだけにします。

```ruby
Rails.application.routes.draw do
  root "tests#index"
  resources :tests, only: [:index, :create]
end
```

# view
画面に表示するためのページを作成します。この時にrails g controller testというコマンドでcontrollerファイルも一緒に生成します。

```cl
❯ rails g controller tests
      create  app/controllers/tests_controller.rb
      invoke  erb
      create    app/views/tests
      invoke  rspec
      create    spec/requests/tests_spec.rb
      invoke  helper
      create    app/helpers/tests_helper.rb
      invoke    rspec
      create      spec/helpers/tests_helper_spec.rb
      invoke  assets
      invoke    scss
      create      app/assets/stylesheets/tests.scss
```
生成されたapp/views/testsフォルダにindex.html.erbファイルを作り、以下のように記述します。

```html
#responseとして戻ってきた内容を描画するところ
<div id="list">         
</div>

#testテーブルに保存されたnameカラムを全て表示
<% @tests.each do |test| %>       
   <%= test.name %>
   <br>
<% end%>

#testテーブルにnameを投稿するための入力フォーム
<%=form_with url: "/tests" , method: :post do |form| %>          
  <%= form.text_field :test %>
  <%= form.submit '投稿する' %>
<% end %>
```
fetchで非同期通信をして戻ってきたresponseを描画するところと、テーブルに保存されたデータを表示させる場所、そしてtestテーブルにnameデータを保存するための入力フォームを用意しました。

#controller
indexアクションにはtestテーブルに保存されているnameを全て取得し、@testsに格納します。また、createアクションにはフォームから入力されたnameを保存し、json形式でresponseを返す処理を記述します。

```ruby
class TestsController < ApplicationController

  def index
    @tests = Test.all.order(id: "DESC")         #テストテーブルのデータを全て取得し、@testsに格納
  end

  def create
    name = Test.create(name: params[:name])    #フォームから入力したnameをtestテーブルに保存。その結果を変数testに格納。
    render json:{ test: name }     #変数testに格納されているnameデータをjson形式でresponseとして返す。
  end

end
```

# javascriptを用意する
app/javascriptの直下にtest.jsを作成します。また、app/javascript/packsのapplication.jsファイルを開き、先ほど作成したtest.jsを読み込むようにします。

```javascript
require("@rails/ujs").start();
require("turbolinks").start();
require("@rails/activestorage").start();
require("channels");
require("../test");                  //追記
```

test.jsには以下のように記述します。test関数を用意してフォームの投稿するボタンをクリックするとfetchで非同期通信が行われます。responseはjson形式で戻ってきて、受け取ったデータをlist要素の後に追加します。

```javascript
//test関数の定義
function test() {
  const submit = document.querySelector(".submit_test");
  if (!submit) {
    return false;
  }
  submit.addEventListener("click", (event) => {
    const formData = new FormData(document.querySelector(".test_form"));
    const url = "/tests";
    const post_options = {
      method: "post",
      body: formData,
    };
    fetch(url, post_options)
      .then((response) => {
        return response.json();
      })
      .then((item) => {
        const list = document.getElementById("list");
        const formText = document.querySelector(".input_name");
        const HTML = `
                    <div>${item.test.name}</div>
                    `;
        list.insertAdjacentHTML("afterend", HTML);
        formText.value = " ";
      });
    event.preventDefault();
  });
}

window.addEventListener("load", test);
```
ここで注意したいのは、responseのitemはpromiseであり、その中にtestというオブジェクトが格納されています。
![スクリーンショット 2021-05-03 9.40.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/966135/612d02fa-20bb-ac36-93cd-76eb84262189.png)
testというオブジェクトにはデータを保存する際のパラメーターがハッシュの形で入っています。nameカラムのデータに接続するにはitem.nameではなく、item.test.nameにする必要があります。


# 結果
railsを立ち上げ、投稿してみます。投稿した内容がページの更新をしなくても表示されるようになりました。
![スクリーンショット 2021-05-03 9.46.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/966135/2bb6b065-d41f-de86-dc4a-aa7fa2c41070.png)