Help us understand the problem. What is going on with this article?

Rails初学者がSetIntervalとTurbolinksの狭間で困った(ている)話

More than 1 year has passed since last update.

あらすじ

簡易チャットアプリを作成中。
特定のページにsetIntervalとAjaxを用いたメッセージの自動更新を実装しようとしたところ、
アプリの全ページにsetIntervalが作動してしまい、特定ページ以外で無限のエラーメッセージが生じ悩まされることに...。

gif.gif

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を削除する

Gemfile
# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
gem 'turbolinks' #削除する

削除後にbundle installをお忘れなく。

terminal
$ bundle install

手順2. application.jsを編集する(Turbolinksの削除)

application.js
//= require jquery
//= require jquery_ujs
//= require turbolinks #削除する
//= require_tree .

手順3. application.html.erb から turbolinks を削除する

Before

application.html.haml
= javascript_include_tag 'application', { turbolinks: { track: true } }

After

application.html.haml
= javascript_include_tag 'application'

ちなみにhamlで記述しています。

ここまで、1~3の手順で、TurbolinksをOFFにすることができました。
参照:Rails 4 で turbolinks をオフにする方法

手順4. application.jsを編集する(stubを用いて記述)

application.js
//= require jquery
//= require jquery_ujs
//= require_tree .
//= stub reload #reload.jsは特定のページのみに適用させたい

「require_tree .」によって、application.js以下のディレクトリに存在するjsファイルが全て読み込まれる。
特定のページのみに適用させたいjsファイルはstubを用いて除外する。

手順5. JSを適用させたいviewファイルにjavascript_include_tagを記述する。

messages/index.html.haml
= 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に追記

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におけるグローバル変数など勉強する必要がありそうです。
以下は試行錯誤の上、試して見たコードです(うまく働かない。)

reload.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を初めて仕事で使ってみたので色々調べてみた」

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away