初めに
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としました。
❯ 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型のとてもシンプルなカラムを作ります。
class CreateTests < ActiveRecord::Migration[6.0]
def change
create_table :tests do |t|
t.string :name
t.timestamps
end
end
end
migrateファイルが出来上がりましたので、migrationしテーブルを生成します。
❯ 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だけにします。
Rails.application.routes.draw do
root "tests#index"
resources :tests, only: [:index, :create]
end
view
画面に表示するためのページを作成します。この時にrails g controller testというコマンドでcontrollerファイルも一緒に生成します。
❯ 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ファイルを作り、以下のように記述します。
#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を返す処理を記述します。
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を読み込むようにします。
require("@rails/ujs").start();
require("turbolinks").start();
require("@rails/activestorage").start();
require("channels");
require("../test"); //追記
test.jsには以下のように記述します。test関数を用意してフォームの投稿するボタンをクリックするとfetchで非同期通信が行われます。responseはjson形式で戻ってきて、受け取ったデータをlist要素の後に追加します。
//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というオブジェクトが格納されています。
testというオブジェクトにはデータを保存する際のパラメーターがハッシュの形で入っています。nameカラムのデータに接続するにはitem.nameではなく、item.test.nameにする必要があります。