0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Ruby on Railsでgonを美しく使うために〜turbolinksで踏んだバグ〜

Last updated at Posted at 2025-02-02

はじめに

Ruby on RailsでRails5まで推奨されていたsprockets環境下での開発でgonを使用する際の注意事項をまとめました。
にしてもRailsは直近フロントエンドのフレームワーク選定が非常に難しくなってきてますね...

gonとは?

Controller内で定義した変数をJSでアクセスできるよう勝手にセットしてくれるものです。
たとえば下記のような例では、Controller内でセットした値をビューとして表示する際にJSが即時実行されてコンソール上にgon.messageとgon.userの内容が表示されます。

Controller

class HomeController < ApplicationController
  def index
    gon.message = "Hello from Rails!"
    gon.user = { name: "Taro", email: "taro@example.com" }
  end
end

html.erb

<%= include_gon %> <!-- `gon` を JavaScript で使うためのヘルパー -->
<h1>Gon Test</h1>
<p>Check the console!</p>

<script>
  console.log("Gon message:", gon.message);
  console.log("Gon user:", gon.user);
</script>

概要

バグとの遭遇

クライアント側で動作させたいパーツが多い画面を開発中のこと。
こんなコードを書きました(一部抜粋)

  let contentGonManager = {
    default__title: "",
    default__body: "",
    initialize() {
      this.default_title = gon.default_title || "";
      this.default_body = gon.default_body || "";
    },
  };
  //その他DOM操作時の動きをトリガーするfunction等

   $(document).ready(function () {
    contentGonManager.initialize();
    //ここでトリガーするfunctionを書く
  });

上記、このJSで管理すべき対象のgonを明示する、DOMのレンダリングが完了してからそのgonを受け取ることを保証させたいという意図があります。gonも結局HTML要素に情報ねじ込んでいるだけなので、DOMがちゃんと用意できた後に読み取らせたい。

そこで画面遷移を行うとこんなエラーが

Identifier has already been declared

あれ...?

原因:turbolinksが悪かった

turboinksとは、Rails環境でページ遷移の高速化のために使用されますが、こいつが悪さしていました。
当初、各リクエストごとにHTMLとそのアセットを取得すると思っていました。

画面:取得する内容

A画面:A画面のHTML,JS,CSS
B画面:B画面のHTML,JS,CSS
A画面:A画面のHTML,JS,CSS

しかしturbolinks環境下ではJS,CSSがキャッシュされHTMLの

のみ更新されていたため、JSが同じ実行環境で2回実行されていました。
画面:取得する内容

A画面:A画面のHTML,JS,CSS
B画面:B画面のHTML,JS,CSS
A画面:A画面のHTML

となるとJSの実行環境はAから引き継ぐため、2回アクセスした時点でブラウザ上のJSの処理は

//1回目のアクセス
  let contentGonManager = {
    default__title: "",
    default__body: "",
    initialize() {
      this.default_title = gon.default_title || "";
      this.default_body = gon.default_body || "";
    },
  };
  //その他DOM操作時の動きをトリガーするfunction等

   $(document).ready(function () {
    contentGonManager.initialize();
    //ここでトリガーするfunctionを書く
  });
//2回目のアクセス
  let contentGonManager = {
    default__title: "",
    default__body: "",
    initialize() {
      this.default_title = gon.default_title || "";
      this.default_body = gon.default_body || "";
    },
  };
  //その他DOM操作時の動きをトリガーするfunction等

   $(document).ready(function () {
    contentGonManager.initialize();
    //ここでトリガーするfunctionを書く
  });

のようになっていました。
数珠繋ぎ的に2個実行されてしまってたわけですね。回避策としてはいくつかあります。

turbolinks:loadイベントを使う
②グローバル変数をwindowに保存する
③turbolinksのキャッシュを無効化する
④スコープを1リクエスト単位に限定して多重定義を防ぐ。

今回は④の選択肢を取りました。
即時実行関数で閉じ、contentGonManagerが多重定義されないようにしました。

$(function () {
let contentGonManager = {
  default_title: "",
  default_body: "",
  initialize() {
    this.default_title = gon.default_title || "";
    this.default_body = gon.default_body || "";
  },
};

// DOM 操作やイベントのトリガー
function setupTriggers() {
  $("#some-button").on("click", function () {
    console.log("Button clicked!");
    console.log("Default Title:", contentGonManager.default_title);
    console.log("Default Body:", contentGonManager.default_body);
  });
}

contentGonManager.initialize();
setupTriggers();
});

こうしてあげると1個目のletと2個目のletは別のスコープとして認識されるのでエラーが解消されます。

終わりに

turbolinks使用時にはJS側の変数のスコープ意識してあげないと変なバグを踏むので気をつけたいですね。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?