こんにちは、鈴木です。
Rails4 の新機能の一つに ActionController::Live というものがあります。
ActionController::Live はリアルタイムの Push 通知を可能にする機能です。
「チャットができるよ。」「またかよ!」
リアルタイム Push 通信を行う他の技術としては、Commet や WebSocket などもあります。
これ系の話の応用例として良く挙げられるのはチャットですが・・、そんなにいつもチャットを作るわけではありません。「リアルタイム Push 通信ができる○○登場!チャットができるよ。」→「またかよ!」と思うことも多いです。
とはいえ「チャット以外の応用例って何?」と言われてもすぐには良いアイディアが浮かばないのも事実です。「こういう使い方ができるんじゃない?」という思いつきを形にすることを積み重ねていけば、いずれ「これぞ!」という応用が見つかるかもしれません。
地図上にマーカーをじわじわ表示するサンプル
ということで、思いつきの一つとして地図上にマーカーをじわじわ表示するサンプルを作りました。
Heroku にサンプルをデプロイしました。以下の URL で確認できるので、良ければ触ってみてください。
http://techscore-ac-live-sample.herokuapp.com/
地図をドラッグすると、画面外となったマーカーは削除されるとともに、新しく画面外に入った領域に表示すべきマーカーの情報がじわじわ表示されます。
ソースコードは以下の場所にあります。
https://github.com/suzuki-kei/rails4_action_controller_live_with_google_maps_sample
ソースコードの解説
Model
地図上に表示するマーカーのテーブルには、「名前」「緯度」「経度」の情報を持たせました。
マイグレーションのコード ( db/migrate/20130907160100_create_markers.rb ) は以下の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class CreateMarkers < ActiveRecord::Migration def change create_table :markers do |table| # 名前. table.string :name, null: false # 緯度. table.decimal :latitude, null: false # 経度. table.decimal :longitude, null: false end end end |
Controller
Controller は次のように実装しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
class IndexController < ApplicationController include ActionController::Live def index end def markers response.header['Content-Type'] = 'text/event-stream' latitude = Range.new(*[params[:north], params[:south]].sort) longitude = Range.new(*[params[:east], params[:west]].sort) exclude_ids = params[:excludes].to_s.split(',') Marker.where(latitude: latitude, longitude: longitude).where.not(id: exclude_ids).each do |marker| logger.debug(marker.inspect) response.stream.write("event: marker\n") response.stream.write("data: #{marker.attributes.to_json}\n\n") sleep 0.1 end response.stream.write("event: done\n") response.stream.write("data: bye\n\n") rescue IOError # クライアント側から close された場合の例外は無視する. ensure response.stream.close end end |
ポイントは、Content-Type は text/event-stream で返すことと、データを一つずつ response.stream.write で出力しているところです。
「sleep 0.1」を入れているのは、わざと処理を遅くしてマーカーがじわじわ表示されることが分かるようにするためです。
View
View はこのようになっています。
1 2 3 4 5 6 7 8 9 10 11 |
<%= javascript_tag do %> $(function() { if(window.EventSource) { new Map("map"); } else { $("#map").text("EventSource を利用できるブラウザでアクセスしてください。"); } }); <% end %> <div id="map"> </div> |
Server-Sent Events (EventSource) が使えなければゴメンナサイということで、使える場合は独自に作成した Map オブジェクトが仕事の全てを行います。
Javascript
クライアントコードのメインは app/assets/javascripts/map.js に閉じ込めてあります。
100 行弱のコードですが、地図がドラッグされたり倍率が変更されたタイミングで以下の処理を行っています(mapChanged 関数)。
- サーバと通信中の場合は、通信をキャンセルする。(cancelRequest)
- 地図の範囲外となったマーカーを削除する。(removeOutsideMarkers)
- サーバから新しい範囲に含まれるマーカーを取得する。(requestMarkers)
1. はサーバ側に処理が溜まらないようにするため、2. はクライアント側のメモリ使用量が増え続けないようにするために行っています。
また、3 の処理では画面内に既に表示されているマーカーの情報をサーバに送信し、サーバ側は不足分のマーカー情報だけ返せるように工夫しました。
まとめ
ActionController::Live の利用例として、またリアルタイム Push 通信活用の一例としてサンプルプログラムを作成しました。
何かの参考になれば幸いです。