Railsアプリ / 非同期通信

非同期通信とは

簡単に言うと、「画面が切り替わらずに処理が行われる仕組み」です。

非同期通信を使用する案件

ランサーズの"仕事を探す"から「非同期通信」というワードで検索をかけてみると、

11件見つかりました。

 

その中で、募集終了していますが、こんな案件がありました。

【改修依頼】jquery、Ajax、非同期通信、googlemapAPIプレイス、php、mysqlなどの仕事・依頼・料金 | Webシステム開発・プログラミングの仕事 【クラウドソーシング ランサーズ】[ID:970756]

f:id:khirok:20200119121715p:plain

依頼の目的・背景

例えば、セレクトボックスで北海道を選択したら、それと連動して北海道の市町村だけが表示されるセレクトボックスが出現する・・・といった動きを非同期通信で実現したいという依頼内容です。

非同期通信を使った簡単なアプリ

案件とは異なる機能ですが、今回は非同期通信の実装がテーマなので、そんなアプリをRuby on Rails で作ってみます。

事前準備

作業用ディレクトリを作成する

「非同期通信」は英語で「asynchronous communication」なので、ディレクトリ名は、"asynchronous_communication"にします。

新規rails アプリを立ち上げる

今回は、非同期通信を実装したいだけなので、オプション指定は省こうと思います。

 

rails new . というコマンドをターミナルで打つと、現在いるディレクトリで新規railsアプリを立ち上げることができます。

f:id:khirok:20200119124903p:plain

新規railsアプリの立ち上げ

 

ターミナルでrails s、ブラウザのアドレスバーにlocalhost:3000と打ち込んで、次の画面を表示させます。

f:id:khirok:20200119125349p:plain

Rails が歓迎してくれました

 

ルーティング、コントローラー、ビューを設定する

config > routes.rb

Rails.application.routes.draw do
 root "homes#top"
end

 

localhost:3000にアクセスしたら、homesコントローラーのtopアクションが動くように、root "homes#top"を追記しました。

 

app > controllers > homes_controller.rb

class HomesController < ApplicationController

 def top
 end

end

 

controllersフォルダの中に、homes_controller.rbを新規作成して、中身は自作しました。

 

app > views > homes > top.html.erb

テスト

 

viewsフォルダの中に、homesフォルダを作成しました。

そして、homesフォルダの中に、top.html.erbを作成しました。

 

ローカルサーバーを再起動して、ブラウザを確認する

設定を読み込ませるために、ターミナルに「Ctrl + c → rails s」を打ち込んで、ローカルサーバーを再起動させます。

 

その後、ブラウザでlocalhost:3000にアクセスします。

f:id:khirok:20200119131839p:plain

rootパスのビュー

このページで非同期通信を作っていきます。

完成形

gif動画

f:id:khirok:20200119164924p:plain

簡単なTO DOを投稿する機能

gif動画を見てもらうと、画面がリロードせずに投稿ができていることが分かります。

この現象が起きてる裏側でどのような処理が起きているのかを書いていきます。

構成ファイル

まずは先ほどの処理を作り出しているファイル群を紹介します。

ルーティング

config > routes.rb

Rails.application.routes.draw do
 root "homes#top"
 post "posts/create" => "posts#create"
end
 ビュー

app > views > homes > top.html.erb

やるべき事

<%= form_with url: posts_create_path, local: true, id: "new_post" do |form| %>
 <%= form.text_area :text,rows: "2", class: "textbox" %>
 <%= form.submit "SEND", class: "form__submit" %>
<% end %>
 
<div class="posts">
 <% if @post %>
  <% @post.each do |post| %>
   <p>
    <%= post.text %>
   </p>
  <% end %>
 <% end %>
</div>

 

コントローラー

app > controllers > posts.controller.rb

class PostsController < ApplicationController
 def create
   @post = Post.new(text: params[:text])
   @post.save
   respond_to do |format|
   format.html { redirect_to root_path }
   format.json
  end
 end
end
ジェイソンビルダー

app > views > posts > create.json.jbuilder 

 json.text @post.text
javascriptファイル

app > assets > javascripts > script.js

$(function() {
function buildHTML(post) {
var html = `<p>
        ${post.text}
       </p>`
return html;
}
$('#new_post').on('submit', function(e) {
 e.preventDefault();
 var formData = new FormData(this);
 var url = $(this).attr('action')
 $.ajax({
  url: url,
  type: "POST",
  data: formData,
  dataType: 'json',
  processData: false,
  contentType: false
 })
 .done(function(data){
  var html =buildHTML(data);
  $('.posts').append(html);
  $('.textbox').val('');
  $('.form__submit').prop('disabled', false);
 })

.fail(function(){
 alert('error');
 })
 })

});
共通するhtmlファイル

app > views > layouts > application.html.erb

<!DOCTYPE html>
 <html>
  <head>
   <title>AsynchronousCommunication</title>
   <%= csrf_meta_tags %>
   <%= csp_meta_tag %>
   <script type="text/javascript">
    WebFontConfig = {
    google: { families: [ 'Unica+One::latin' ] }
    };
    (function() {
    var wf = document.createElement('script');
    wf.src = ('https:' == document.location.protocol ? 'https' : 'http') +
    '://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js';
    wf.type = 'text/javascript';
    wf.async = 'true';
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(wf, s);
    })();
   </script>
   </script>
   <%= stylesheet_link_tag 'application', media: 'all',
   'data-turbolinks-track': 'reload' %>
   <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>
  <body>
  <%= yield %>
  <script src="script.js"></script>
  </body>
</html>

javascriptを読み込む処理が書かれています。

大まかな処理の流れ

次に、フォームにデータを入力して、Sendボタンを押すと、非同期通信が起こり、データが表示される・・・という現象の裏側で起こっている、データの処理の流れについて書いていきます。

createアクションが動く

まずは、form_withに書かれているurlに従ってpostsコントローラーのcreateアクションが動きます。

<%= form_with url: posts_create_path, local: true, id: "new_post" do |form| %>
 <%= form.text_area :text,rows: "2", class: "textbox" %>
 <%= form.submit "SEND", class: "form__submit" %>
<% end %>
データベースにデータが保存される

コントローラーで、フォームから送られてきたparams[:text]を@postに代入し、保存しています。

 @post = Post.new(text: params[:text])
 @post.save
submitイベントが発火する

#new_postはボタンに付いているidです。

以下の記述は、ボタンを押した時に実行されます。

$('#new_post').on('submit', function(e) {
e.preventDefault();
var formData = new FormData(this);
var url = $(this).attr('action')
$.ajax({
url: url,
type: "POST",
data: formData,
dataType: 'json',
processData: false,
contentType: false
})
.done(function(data){
var html =buildHTML(data);
$('.posts').append(html);
$('.textbox').val('');
$('.form__submit').prop('disabled', false);
})

.fail(function(){
alert('error');
})

})

通常のデータ送信を止める

e.preventDefault();によって、通常のデータ送信を止めています。

これによりjson形式でデータを送ることができます。

 

2種類の変数

・var formData = new FormData(this);

javascriptのオブジェクトとしてフォームに入力されたデータを、formDataに代入しています。

・var url = $(this).attr('action')

→フォームのアクション属性を取得したものをurlに代入しています。

ajax通信がスタートする
$.ajax({
url: url,
type: "POST",
data: formData,
dataType: 'json',
processData: false,
contentType: false
})

processDataとcontentType

データをformDataで送るときは、どちらもfalseにしておきます。

json形式に振り分けられる

その後、respond_toでhtmlとjsonで処理を分けています。

今回は、通常のデータ送信を止めているので、jsonが動いています。

 respond_to do |format|
  format.html { redirect_to root_path }
  format.json
 end
json語に翻訳される

ジェイソンビルダーで、フォームから送られてきたデータをjson形式に変換しています。

 json.text @post.text
jsonをdoneメソッドで受け取りhtmlを組み立てる
.done(function(data){
 var html =buildHTML(data);
 $('.posts').append(html);
 $('.textbox').val('');
 $('.form__submit').prop('disabled', false);
})

 

2回目以降もボタンを押せるように

$('.form__submit').prop('disabled',false);

→デフォルトでは1回ボタンを押したら2回目以降は押せなくなってしまうので、それを停止しています。

 

おわりに 

まだまだ非同期通信については、調べることがたくさんあると思ったので、いつか調べてまとめてみたいと思います。

参考URL

https://qiita.com/yuitnnn/items/b45bba658d86eabdbb26

新規Railsプロジェクトの作成手順まとめ

https://dotinstall.com/lessons/basic_rails_v3/41809

ドットインストール #09ルートパスを設定してみよう

https://prog-8.com/lessons/rails5/study/2

Progate Ruby on Rails5 学習コースⅡ 「データベースを使ってみよう」

https://qiita.com/Kta-M/items/254a1ba141827a989cb7

Railsを始めてsqlite3まわりのエラーで躓いている人たちへ

https://techtechmedia.com/form-with-rails/#form_with

Rails】フォーム実装できる「form_with」について分かりやすく解説!