猫Rails

ねこー🐈

Gonの使い方 まとめ

  • 自分用のメモを公開したものです。ドキュメント/ソースコード等をまとめただけで試していないコードも多いので、信頼性は低いです。

Gonとは

  • Railsの変数をJSで使えるようになる

導入

1. インストール

# Gemfile
gem 'gon'
$ bundle

2. セットアップ

# application.html.erb

<head>
  ...
  # application.jsの前に読み込めばロード前にgon変数にアクセスできる
  #
  # 以下のようなHTMLを生成してくれる
  # <script>
  # //<![CDATA[
  # window.gon={};gon.hoge=1;
  # //]]>
  # </script>
  <%= Gon::Base.render_data %>
  <%= javascript_include_tag "application" %>
  ...
</head>

3. 使ってみる

class ProductsController < ApplicationController
  def index
    # Rails側で変数をセット
    gon.hoge = 1

    ...
  end
end
# coffee

# JS側で使用できる
# 型キャストやエスケープもいい感じにやってくれる
gon.hoge // => 1

設定

使い方

  • Gon::Base.render_dataのオプションとして指定する
Gon::Base.render_data(watch: true)

watch: watch機能を有効化する

  • watch機能 を参照
  • 実際にはfalseでもwatch機能は使える。明示的に宣言するだけ
  • デフォルト: false

camel_case: キャメルケースに変換する

  • デフォルト: false
# 設定
Gon::Base.render_data(camel_case: true)

# ruby
gon.int_cased = 1

# js
gon.intCased #=> 1

camel_depth: キャメルケースを適用する深さ

  • デフォルト: 1
# 設定
Gon::Base.render_data(:camel_case => true, :camel_depth => 2)

# js
gon.testHash.testDepthOne.test_depth_two

namespace: gonという名前空間を変える

  • デフォルト: 'gon'
# 設定
Gon::Base.render_data(:namespace => 'serverExports')

# js
serverExports.your_int

init: window.gon = {}で初期化する

  • デフォルト: true
# js
# 初期化されてるので、データがない場合でもエラーにならない
window.gon // => {}

type: <script>type="text/javascript"を追加する

  • false
Gon::Base.render_data(type: true) #=> <script type="text/javascript">window.gon=...</script>

nonce: <script>nonce=...を追加する

  • CSP対応
  • デフォルト: nil
Gon::Base.render_data(nonce: 'test') #=> "<script nonce=\"test">...

need_tag: <script>タグあり

  • デフォルト: true
Gon::Base.render_data(need_tag: true)
#=> <script>
#=> //<![CDATA[
#=> window.gon={};gon.hoge="piyo";
#=> //]]>
#=> </script>
Gon::Base.render_data(need_tag: false)
#=> window.gon={};gon.hoge="piyo";

cdata: CDATAあり

  • デフォルト: true

global_root: globalという名前空間を変える

  • デフォルト: 'global'

amd: AMD対応

  • デフォルト: false
  • include_gon_amdと同じ

<script>タグのrenderメソッド*3

Gon::Base.render_data

  • 基本コレを使えばok

include_gon

  • Rails3の場合はこっちらしい
  • 内部でGon::Base.render_dataを利用してる
<%= include_gon %>

include_gon_amd

  • AMDの場合はこっち
  • 内部でGon::Base.render_dataを利用してる
<%= include_gon_amd %>

メソッド

gon.hoge=: set

gon.hoge = 1

gon.push: set

gon.push(hoge: 1, piyo: 2)

gon.hoge: get

gon.hoge #=> 1

gon.all_variables: setした全ての値

gon.hoge = 1
gon.piyo = 2
gon.all_variables #=> {"hoge"=>1, "piyo"=>2}

gon.clear: setした値を消す

gon.hoge = 1
gon.piyo = 2
gon.all_variables #=> {"hoge"=>1, "piyo"=>2}
gon.clear
gon.all_variables #=> {}

watch機能

  • Ajaxでポーリングして、gonのデータをリアルタイムで取得する

導入

1. watchオプションをtrueにする

  • どうもwatchオプションは明示的にする意味しかなく、指定しなくてもOKっぽい
# app/views/layouts/application.html.erb
<%= include_gon(watch: true) %>

2. アプリ作成

  • コントローラーでgon.watchを利用するのがポイント
# app/controllers/home_controller.rb
# ここにAjaxリクエストが送られる。そのたびにusers_countの値がレスポンスして更新される
def index
  @users_count = User.count
  gon.watch.users_count = @users_count
end
# app/views/home/index.html.erb
# Ajax成功時に、コールバックでここの表示が変わる
<div id='users-counter'></div>

3. JS

  • gon.watch()は一定間隔でリクエストを送り、コントローラーでgonにセットした変数をコールバックで利用する
# app/assets/javascripts/home.js.coffee
# コールバック
#   uesrs_count: Ajaxのレスポンス値
renewUsers = (uesrs_count) ->
  $('#users-counter').text(users_count)

# gon.watchの使い方
# gon.watchを使うとポーリングできる
# ここでは1秒ごとに`/home`にAjaxリクエストを送って、`#users-counter`の表示を変更している
#
# gon.watch(name_of_variable, options, callback)
#   name_of_variable: 変数名(コントローラーで`gon.watch.users_count = @users_count`とすれば、'users_count'となる)
#   options
#     interval: Ajaxリクエストする間隔。ms
#     method: HTTPメソッド。デフォルトはGET
#     url: 陸ストを送るURL
#   callback: Ajaxのコールバック
gon.watch('users_count', interval: 1000, renewUsers)

止める

  • gon.unwatchで止める
# ビュー
<a href='#' id='stop-renewing'>
  Stop renewing
</a>
# coffee
$('#stop-renewing').click ->
  # 止める
  gon.unwatch('users_count', renewUsers)
  return false

グローバルに使う

  • Gon.globalを使う
# config/initializers/some_initializer.rb
Gon.global.variable = 1
# coffee
gon.global.variable // => 1

RSpecでコントローラーテスト

# spec/support/shared_contexts/gon.rb
shared_context :gon do

  # Gonは内部でRequestStore(リクエストグローバルな変数。gem)を利用している
  # RequestStoreはRackミドルウェアで変数を削除する
  # しかしテストではRackミドルウェアを経由しない
  # そのためテストケース毎にGonの変数を手動でクリアする必要がある
  # 参考: https://tech.misoca.jp/entry/2015/06/15/151419
  let(:gon) { RequestStore.store[:gon].gon }
  before { Gon.clear }
end

# spec/controllers/thingies_controller_spec.rb
RSpec.describe ThingiesController do
  include_context :gon

  describe 'GET #new' do
    it 'gonifies as expected' do
      get :new, {}, valid_session

      # Gonに変数がsetされていることをテスト
      expect(gon['key']).to eq :value
    end
  end
end

ざっくりコードリーディング

gon.gemspec

  • 依存gem
    • actionpack: Railsのcontroller/routing
    • request_store: リクエストグローバルな変数
    • multi_json: 主要なJSON Engineに対応したJSONパーサ

lib/gon.rb

  • メインとなるGonクラス
  • コントローラーのgonの正体はGon
  • gon.hoge = 1gon.hogeにはmethod_missingを利用している。

lib/gon/base.rb

  • Gon::Base.render_data

-Gon::Base.render_dataのオプションのデフォルト値

    VALID_OPTION_DEFAULTS = {
        namespace: 'gon',
        camel_case: false,
        camel_depth: 1,
        watch: false,
        need_tag: true,
        type: false,
        cdata: true,
        global_root: 'global',
        namespace_check: false,
        amd: false,
        nonce: nil
    }

lib/gon/global.rb

  • Gon::GlobalはGonのサブクラス
  • Gon::Globalはgon.globalに対応
  • Gonとの違いはあんまりないっぽい

lib/gon/watch.rb

  • Gon::WatchはGonのサブクラス
  • Gon::Watchはgon.watchに対応

coffee/watch.coffee

  • gon.watchgon.unwatchgon.unwatchAllが定義
  • jQuery依存っぽい

js/watch.js

  • coffee/watch.coffeeをコンパイルしたファイル

参考URL