あらすじ
簡易チャットアプリを作成中。
特定のページにsetIntervalとAjaxを用いたメッセージの自動更新を実装しようとしたところ、
アプリの全ページにsetIntervalが作動してしまい、特定ページ以外で無限のエラーメッセージが生じ悩まされることに...。
version
Programming歴: 65日目
Ruby: 2.3.1
Rails: 5.0.1
原因
調べて見たところ、
Railsにデフォルトで搭載されているTurbolinksという機能のせいで、
一度読み込まれたJavaScriptのプロセスが、他のページでも生き続けてしまうことが原因でした。
とあるページでsetIntervalするとどのページに行っても止まらない、POSTしたりして止まっても今度は再開すべきページで再開しない、という事故が簡単に起きます。
これはPOSTやPUTをしてjsの再読み込みが行われるまでページ遷移してもjsのプロセスがずっと生き続ける為です。(引用:「Turbolinksさんと上手く付き合う10の方法」)
問題解決には、Turbolinksさんをどうにかする必要があるみたいです、
Turbokinksとは
ざっくりいうと、ウェブアプリ上でページを移動する際、新規ページ全体をリロードするのではなく、必要部分のみajaxを使って書き換えてくれるもの。これによってページ移動を高速化している。
現在のRailsにデフォルトでついている機能みたいですね。
詳細な解説は、以下の記事・サイトがわかりやすくオススメです。
・KRAY「Rails 4のturbolinksについて最低でも知っておきたい事」
→図がわかりやすく、ajaxでの書き換えのイメージが掴めます。
・TECHSCORE 「6. Turbolinks」
→解説が丁寧でわかりやすいです。
・Qiita「Turbolinksさんと上手く付き合う10の方法」
→Turbolinksでの基本的な注意点がまとまっています。
・seri::diary「今更ながらTurbolinksを初めて仕事で使ってみたので色々調べてみた」
→上記記事の筆者seri_kさんのブログ。Turbolinks以外の記事も非常に参考になります。
解決策
① TurbolinksをOFFにし、特定のページに個別でJSを当てる
ベストかどうかはわかりませんが、これが一番手っ取り早いです。
まずTurbolinksをOFFにし、その上で特定のページのみに適用させたいJSを個別に当てます。
手順1. Turbolinksのgemを削除する
# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
gem 'turbolinks' #削除する
削除後にbundle install
をお忘れなく。
$ bundle install
手順2. application.jsを編集する(Turbolinksの削除)
//= require jquery
//= require jquery_ujs
//= require turbolinks #削除する
//= require_tree .
手順3. application.html.erb から turbolinks を削除する
Before
= javascript_include_tag 'application', { turbolinks: { track: true } }
After
= javascript_include_tag 'application'
ちなみにhamlで記述しています。
ここまで、1~3の手順で、TurbolinksをOFFにすることができました。
参照:Rails 4 で turbolinks をオフにする方法
手順4. application.jsを編集する(stubを用いて記述)
//= require jquery
//= require jquery_ujs
//= require_tree .
//= stub reload #reload.jsは特定のページのみに適用させたい
「require_tree .」によって、application.js以下のディレクトリに存在するjsファイルが全て読み込まれる。
特定のページのみに適用させたいjsファイルはstubを用いて除外する。
手順5. JSを適用させたいviewファイルにjavascript_include_tagを記述する。
= javascript_include_tag 'reload'
// JSを適用させたいviewファイルにjavascript_include_tagを記述する。
.chat
.chat-side
= render "partials/user-info"
= render "partials/chat-groups"
.chat-main
= render "chat-main"
.chat-body
%ul.chat-messages
= render @messages
.chat-footer
= render "chat-footer"
手順6. config/assets.rbに追記
# Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = '1.0'
Rails.application.config.assets.precompile += %w( reload.js ) #追記する
これで、特定のページ(messages/index)のみに、特定のJS(reload.js)が当たるようになりました。
②「Turbolinksさんと上手く付き合う10の方法」の「3. setIntervalは慎重に使う」を実行
$(document).on 'page:change' ->
if repeatable_condition # setIntarvalをセットする条件。特定のDOMがあるとか
if window.set_timer_on == null
window.timer = setInterval('something_method()',3000)
else if window.set_timer_on? # setIntarvalを動かしたくないページに来てtimerが動いる場合
clearInterval(window.timer)
window.timer = null
記事では、上記のようなコードでTurbolinksとSetintervalの併用ができるとのことでしたが、
試行錯誤して見たものの、うまくいかず。Setintervalがどこのページでも作動してしまいました。
clearIntervalの挙動やJSにおけるグローバル変数など勉強する必要がありそうです。
以下は試行錯誤の上、試して見たコードです(うまく働かない。)
function auto_reload(){
(中略)
}
(中略)
$(document).on("turbolinks:load",function() {
if (document.getElementsByClassName("chat").length == 1) {
// chatクラスが存在する場合、以下を行う。
var id = setInterval(auto_reload, 1000);
} else if (document.getElementsByClassName("chat").length == 0) {
// chatクラスが存在しない場合、以下を行う。
clearInterval(id);
}
});
参考リンク
・KRAY「Rails 4のturbolinksについて最低でも知っておきたい事」
・TECHSCORE 「6. Turbolinks」
・Qiita「Turbolinksさんと上手く付き合う10の方法」
・seri::diary「今更ながらTurbolinksを初めて仕事で使ってみたので色々調べてみた」