Rails 6.0.x標準で Ajax+(jQuery+Partial) でHTML部分更新する世界一シンプルなサンプル

# TL;DR (長い! 3行で!)
↓ の記事をRails 6.0.1 版に書き直したものです．
[Rails 5.x標準で Ajax+(jQuery+Partial) でHTML部分更新する世界一シンプルなサンプル](https://qiita.com/fezrestia/items/e669107a4a6e66618738)

Front EndがWebpackに変わっており，
・ Coffee Script → JavaScript への変更
・ jQuery導入方法の変更
が大きな変更点です．



# 動作確認環境

```
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 14.04.6 LTS ← 古いLTSなのでわりとダメ，できれば最新LTS使ってください
Release:        14.04
Codename:       trusty

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]

$ rails --version
Rails 6.0.1

$ yarn --version
1.16.0

$ nvm --version
0.35.1

$ node --version
v12.13.1
```

#### NOTICE
`node`のversionが古いと(LTSじゃないと?)，`$ rails new`の途中で ↓ のようなErrorが出ます．

```
error get-caller-file@2.0.5: The engine "node" is incompatible with this module. 
Expected version "6.* || 8.* || >= 10.*". Got "9.10.1"
error Found incompatible module.
```

これ以降，webpackが使えなくなります．
(`error Command "webpack" not found.`)
`$ rails new`自体は "Webpacker successfully installed" のメッセージがでて正常終了してるように見えてしまうため気付けない．．．


# ざっくりシーケンス
だいたい ↓ のようなシーケンスを実現します．
Client-Server間でSession張って双方向通信とかそういうところはRails Frameworkが全部やってくれます．

```
Front End : UserがInputしたTextをAjaxでBack End側にPost
↓
Back End : PostされたTextとPartialを使って部分的に差し替えるHTMLをRendering
↓
Front End : JavaScriptでAjaxのCallback(部分的に差し替えるHTML)を受け取る
↓
Front End : jQueryでHTMLの部分差し替え
```

<font color="Gray">
Rails 6 でFront EndがWebpack化されたことで，Rails 5 に比べてReactなども導入しやすくなっています．そのため，Back Endから返すCallbackはpre-renderしたHTMLでなく，Jsonなどでデータを返して，Front End側でrenderする，という方法もあります．
</font>

# Project初期化 + Default動作確認

```
$ rails new ajax-test
```
でRails Projectを初期化します．`$ cd ajax-test`でRailsのrootに移動して，

```
$ rails server
```

でサーバを起動して，
`http://localhost:3000/`
にアクセスして "Yay! You're on Rails!" のDefaultページが出たらOKです．



# Static Page作成
Ajax動作のベースになるStatic Page部分を実装します．

## Controller

```
$ rails generate controller AjaxTest
```
でControllerのTemplateを作成後，Action(Method)を追加します．

```ruby:app/controller/ajax_test_controller.rb
class AjaxTestController < ApplicationController
  def top
    # NOP.
  end

  def update
    # TBD.
  end
end
```
`top` : Topページ表示用，特に処理はありません．
`update` : Ajaxリクエストを受ける用，実装はあとで追加します．



## Route Config
ControllerのActionに繋げるためのRoute定義を追加します．

```ruby:config/routes.rb
get  'ajax_test/top',    to: 'ajax_test#top',    as: 'ajax_test_top'
post 'ajax_test/update', to: 'ajax_test#update', as: 'ajax_test_update'
```

`GET` : Topページ表示用．
`POST` : Ajaxリクエスト用.


## View
`ajax/test/top` にアクセスしたときに表示するViewを追加します．

```erb:app/views/ajax_test/top.html.erb
<div id="request_ajax_update" >
  <%= form_tag(ajax_test_update_path, method: :post, remote: true) { %>
    <%= text_field :data, :text %>
    <%= submit_tag 'Post AJAX' %>
  <% } %>
</div>

<hr>

<div id="updated_by_ajax" >
  DEFAULT
</div>

<hr>
```

大きく2つのブロックだけです．

・ `<div id="request_ajax_update" >`
Ajax RequestをPostするFormを持つブロック．
Userが入力したTextを `params[:data][:text]` に詰めて，
`ajax_test_update_path` ( = `/ajax_test/update`) にPOSTします．

・ `<div id="updated_by_ajax" >`
Ajax Callbackを受けてHTMLの部分更新をするブロック．


## Static Page動作確認
この時点で，
`http://localhost:3000/ajax_test/top`
にアクセスすると，↓ のようなページが表示されると思います．
![default.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129035/12ba389f-e294-bd91-8254-bf280464a04f.png)

ただし，Ajaxの実装がまだ無いので，`Post AJAX` ボタンをClickしても見た目上は何も起こりませんが，`$ rails server` が動いているConsoleにText Fieldに入れた文字列がParametersとして通知されているのを確認できると思います．

```
Parameters: {"data"=>{"text"=>"Hello World !"}, "commit"=>"Post AJAX"}
```

# Ajax実装
いままでに作ったStatic PageにAjax実装を組み込んでInteractiveな機能を追加します．

## Partial View
部分的に更新するHTMLの部品を追加します．

```erb:app/views/ajax_test/_ajax_partial.html.erb
<div>
  Callback Msg = <%= results[:message] %>
</div>
```

このPartialで `<div id="updated_by_ajax" >` の中身を差し替えます．

## Controller Action
さきほど作ったControllerの `update` の中身を実装します．

```ruby:app/controller/ajax_test_controller.rb
class AjaxTestController < ApplicationController
  def top
    # NOP.
  end

  def update
    post_text = params[:data][:text]
    results = { :message => post_text }
    render partial: 'ajax_partial', locals: { :results => results }
  end
end
```

Controllerに渡ってきた Parameters の中に格納されているUserが入力したTextを使ってPartialをrenderしています．
Partialのファイル名 `_ajax_partial.html.erb` が `ajax_partial` で使えるあたりはRailsの規約に沿っています．

ここで，

```ruby
res = render_to_string partial: 'ajax_partial', locals: { :results => results }
puts res
```

のようなCodeを書いておくとPartialをrenderしたときの実際のOutputが見れます．

```html
<div>
  Callback Msg = Hello World !
</div>
```

## JavaScript実装
Rails 6 からFront EndにWebpackが標準で使われることになり，Coffee ScriptがDefaultで使われなくなっています．

かわりに，
`app/javascript/packs/application.js`
のようなWebpackに対応したJavaScriptのDir構成に変わっています．
(Rails 5 でWebpack使っていた人にはおなじみの構成かも)


### ViewにJavaScriptのEntry Pointを追加

ViewのHTMLにJavaScriptの読み込み部分を追加します．


```erb:app/views/ajax_test/top.html.erb
<div id="request_ajax_update" >
  <%= form_tag(ajax_test_update_path, method: :post, remote: true) { %>
    <%= text_field :data, :text %>
    <%= submit_tag 'Post AJAX' %>
  <% } %>
</div>

<hr>

<div id="updated_by_ajax" >
  DEFAULT
</div>

<hr>

<!-- ↓ 追加 -->
<%= javascript_pack_tag 'ajax_test' %>
<script>
  register_callback();
</script>
```

`<%= javascript_pack_tag 'ajax_test' %>`
は `app/javascript/packs/` 以下に置いてある `ajax_test` というファイル(の中身)を読み込む，という指示です．
実際にはWebpackがひとかたまりの`.js`にしてしまうので，この名前のファイルを読み込んでいるわけではありません．

その後，`<script></script>` タグで `register_callback()` という関数を呼び出します．
この関数の実装は次で追加します．



### JavaScript本体の実装

Webpack用Dir構成に沿って，Viewから呼び出せるように ↓ のJavaScriptを追加します．

```javascript:app/javascript/packs/ajax_test.js
import * as $ from "jquery";

function register_callback() {

  $("#request_ajax_update").on(
      "ajax:complete",
      function(event) {
        var res = event.detail[0].response
        $('#updated_by_ajax').html(res)
      }
  );

}

window.register_callback = register_callback;
```

Viewから呼び出す`register_callback()`関数の中で，AjaxリクエストをPOSTする `#request_ajax_update` Tagに `ajax:complete` のCallback関数を登録しています．

Viewから呼び出せるようにするため，
`window.register_callback = register_callbcak;`
の行で`window`の名前空間(global)にexportしています．
注) この方法は動作はしますがあまり行儀よくないはずです...


ただし，このままでTopページから`Post AJAX`をClickしても何も起こりません．
ChromeのDeveloper Tool等でConsole Logをみると，

```
Uncaught Error: Cannot find module 'jquery'
    at webpackMissingModule (ajax_test.js:1)
    at Module../app/javascript/packs/ajax_test.js (ajax_test.js:1)
```

のようなError Logがでていて，`jquery` の名前解決ができてないことがわかります．


### jQueryを使えるようにする

Rails 5 から引き続きRails標準ではjQueryはサポートされていないため，WebpackからjQueryを使えるようにします．

```
$ yarn add jquery
```

のCommandでjqueryをInstallします．


#### 必要かもしれない追加

"rails6 + jquery" とかのキーワードでぐぐると，↓ の2つの追加も必要，と出てきますが，手元の環境だと必要ありませんでした．
RailsのVersionによるのか，他の環境によるのか...

ref: https://www.botreetechnologies.com/blog/introducing-jquery-in-rails-6-using-webpacker


```javascript:app/javascript/packs/application.js
// 他のrequireがいろいろ...

require("jquery") // 追加

```

```javascript:config/webpack/environment.js
const { environment } = require('@rails/webpacker')

// 追加
const webpack = require('webpack')
environment.plugins.prepend('Provide',
  new webpack.ProvidePlugin({
    $: 'jquery/src/jquery',
    jQuery: 'jquery/src/jquery'
  })
)

module.exports = environment
```

## Ajax動作確認

これで必要な変更はすべてです．
`http://localhost:3000/ajax_test/top`
にアクセスして，Text Fieldになにか文字列を入力して`Post AJAX`ボタンを押したら，`DEFAULT` の文字列が更新されて ↓ のような画面になっていれば成功です．

![ajax.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129035/94718927-eed0-0935-10d6-4cc6d60b2b49.png)

ChromeのDeveloper ToolなどでDOM構造を見てみると，↓ のようになっていて，`<div id="updated_by_ajax" >`の中身が差し替わっているのが見えます．

![dev_tool.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129035/99c6fece-3940-6cca-936f-e5862556f7fe.png)


# おわり
Rails 6 でAjaxを使ったシーケンスを一本通すまでをできる限りDefaultのままで実現してみました．
環境依存でたまたまうまく動いている部分などあるかもしれません．もし何かおかしな点がありましたら教えていただけると助かります．

---///