Action Cable を使いたいときがあります。 Ruby on Rails の app を書いているのですから、サーバもクライアントも Ruby で書くのが自然です。
というわけで。Opal を導入してみました。
Rails app を用意する
rails new
コマンドで適当な Rails app を作成します。
Action Cable は有効にしておきます。
webpacker を利用する方法も調べたのですが、いまひとつうまくいかなかったので今回は Sprockets を利用する方法を書きます。
Rails に Opal を追加する
opal-rails
gem を使います。
詳細はドキュメントにありますのでそちらを確認してください。
Gemfile に gem 'opal-rails'
を追加してインストールします。
config/initializers/assets.rb
を編集してオプションを指定します。ひとまず README にある通りの内容を追加します。
Rails.application.config.opal.method_missing = true Rails.application.config.opal.optimized_operators = true Rails.application.config.opal.arity_check = !Rails.env.production? Rails.application.config.opal.const_missing = true Rails.application.config.opal.dynamic_require_severity = :ignore
app/assets/javascripts/application.js
を app/assets/javascripts/application.js.rb
に rename して内容を編集します…というか、JS から Ruby への書き直しなので前者を削除して新しくファイルを作成すると考えた方がよいです。
- Ruby の文法で書く
rails-ujs
をopal_ujs
に変更する- 先頭に
require 'opal'
を追加する。
require 'opal' require 'opal_ujs' require 'turbolinks' require_tree '.'
動作を確認します。
app/assets/javascripts/
に次のような .rb のファイルを追加して Rails app のサーバを起動します。
# app/assets/javascripts/hello.rb puts 'Hello, Opal!'
正しく動作すればブラウザのコンソールに puts した文字列が出力されます。
Action Cable を使う
対比のために、まず Opal を利用しない版を書きます。
Action Cable の channel を追加する
rails g channel
コマンドを利用して channel を追加します。
$ bin/rails g channel mumbler
サーバ側の app/channels/mumbler_channel.rb
とクライアント側の app/assets/javascripts/channels/mumbler.js
が追加されます。
それぞれ次のように編集します。
class MumblerChannel < ApplicationCable::Channel def subscribed stream_for 'any_channel' end def unsubscribed end def mumble(data) MumblerChannel.broadcast_to 'any_channel', {monologue: data['monologue']} end end
function connected() { } function disconnected() { } function received(data) { const monologue = document.createElement('li') monologue.appendChild(document.createTextNode(data.monologue)) const monologues = document.getElementById('monologues') monologues.prepend(monologue) } const handlers = { connected, disconnected, received } App.mumbler = App.cable.subscriptions.create('MumblerChannel', handlers) window.addEventListener('load', () => { const munbleButton = document.getElementById('mumbleButton') mumbleButton.addEventListener('click', () => { App.mumbler.perform('mumble', {monologue: document.getElementById('monologue').value}) }) })
Action Cable を利用するページを追加する
上で書いた Action Cable を利用する monologues
というページを追加します。
config/routes.rb
に monologues
のページのルーティングを追加します
get 'monologues', to: 'monologues#index'
コントローラ app/controllers/monologues_controller.rb
を追加します。
class MonologuesController < ApplicationController def index end end
ビュー app/views/monologues/index.html.erb
を追加します。
<h1>Monologues</h1> <input type="text" id="monologue"></input> <button type="submit" id="mumbleButton">MUMBULE</button> <ul id="monologues"></ul>
Rails app サーバを起動します。 正しく動作すれば入力欄とボタンが表示され、テキストを入力してボタンを押すとそのテキストがリストに追加されます。
複数のウィンドウでこのページを開くとボタンを押すたびに同時にテキストが追加されることが確認できます。
Action Cable のクライアントを Ruby(Opal) で書き直す
ファイル名を app/assets/javascripts/channels/mumbler.js
から app/assets/javascripts/channels/mumbler.js.rb
に変更して、内容を次のように書き換えます。
Window = Native(`window`) Document = Native(`document`) handlers = { connected: -> () { }, disconnected: -> () { }, received: -> (data) { monologue = Document.createElement('li') Native(`monologue`).appendChild(Document.createTextNode(Native(`data`).monologue)) monologues = Document.getElementById('monologues') monologues.prepend(monologue) } } mumbler = Native(`App`).cable.subscriptions.create('MumblerChannel', handlers) Window.addEventListener('load', -> (_) { mumbleButton = Document.getElementById('mumbleButton') mumbleButton.addEventListener('click', ->(_) { mumbler.perform('mumble', {monologue: Document.getElementById('monologue').value}) }) })
ページを読み込み直すと同じように動作することが確認できます。
Ruby でクライアントが書けました。
…。
これだけだとあまり嬉しくないかもしれませんが、これで Ruby(Opal) で書かれたフレームワークを利用することができるようになりました。
続く。
いつか読むはずっと読まない:暗黒通信団著
Phoenix の Channel のクライアントを Elixir で書いてみたい、という衝動もあります。
まぁそれは、 Elm を使おうかな…。
- 作者: oskimura
- 出版社/メーカー: 暗黒通信団
- 発売日: 2017/08/01
- メディア: 単行本
- この商品を含むブログを見る