【Rails4】ActionController::Liveで簡単なチャットアプリサンプルを作ってみる
Rails4から新たに取り入れられた非同期な実装を可能にする!?ActionController::Liveを使って、簡単なチャットアプリを作ってみようと思います。
主に、RailsCastの401のエピソードに詳細は書いてあります。(が、proなので、課金必要ですorz)
あと、ここも有難い感じです。
「ActionController::Liveで始めるリアルタイムWeb at Ginza.rb」
まず、ActionController::Live入れてないバージョンを見ます。
コードはこちら(GitHub)から見れます。
index.html.erb
<h1>Chat</h1> <ul id="chat"> <%= render @messages %> </ul> <%= form_for Message.new, remote: true do |f| %> <%= f.text_field :name, placeholder: 'Name' %> <br> <%= f.text_area :content, placeholder: 'Write something...' %> <%= f.submit "Send" %> <% end %>
create.js.erb
$('#chat').append("<%= j render(@message) %>"); $("#message_content").val('');
これで、とりあえず、チャットっぽくはなりますが、
オンタイムで他の人が入れたメッセージはリダイレクトしないと出てきません。これをTwitterみたく出てくるようにします!
ActionController::LiveではServer Sent Eventsを使います。こちらにざっくり説明が書いてあります。
https://developer.mozilla.org/ja/docs/Server-sent_events/Using_server-sent_events
Server Sent EventsでWEBrickやunicornが使えないみたいで、pumaやthin,rainbowをアプリケーション・サーバーに使う必要があるみたいです。
ここではpumaを使います。
また、もう一点。
develepment.rbで
config.cache_classes = true config.eager_load = true
デフォルトでfalseになってる両者をtrueにする必要があるみたいです。
config.cache_classesをtrueにすることで、クラスの読み込みが起動時の1回になるそうな。
config.eager_loadをtrueにすることで、Railsアプリケーションの全体とすべてのクラスが、autoloadではなく、eager-loadされることによりthread-safeになるそうな。
詳しくは以下。
http://railscasts.com/episodes/400-what-s-new-in-rails-4?language=ja&view=asciicast
http://railscasts.com/episodes/365-thread-safety
messages_controller.rb(一部)
class MessagesController < ApplicationController include ActionController::Live def create @message = Message.create(attributes) end def events response.headers["Content-Type"] = "text/event-stream" start = Time.zone.now 10.times do Message.uncached do Message.where('created_at > ?', start).each do |message| response.stream.write "data: #{message.to_json}\n\n" start = message.created_at end end sleep 2 end rescue IOError logger.info "Stream closed" ensure response.stream.close end private def attributes params.require(:message).permit(:name, :content) end end
message.js.coffee
source = new EventSource('/messages/events') source.addEventListener 'message', (e) -> message = $.parseJSON(e.data) $('#chat').append($('<li>').text("#{message.name}: #{message.content}").css("list-Style-Type","none"))
create.js.erb
$("#message_content").val('');
routes.rb(一部)
resources :messages do collection { get :events } end
これで、$ rails s pumaで起動して、いくつかブラウザ立ち上げて試すと、eventが定期的に見に行ってくれてて、確かに、同時っぽくなる。
けど、結構不安定だったり、ややラグがある。
なので、ここで、RedisのPub/Subの機能を使ってやってみる。
・http://redis.io/topics/pubsub
・日本語版
・edis の Pub/Sub を使って Node.js + WebSocket のスケールアウトを実現する方法
gem 'redis'を入れて、
initializers/redis.rb
$redis = Redis.new
messages_controller.rb(一部)
def create response.headers["Content-Type"] = "text/javascript" @message = Message.create(attributes) $redis.publish('messages.create', @message.to_json) end def events response.headers["Content-Type"] = "text/event-stream" redis = Redis.new redis.subscribe('messages.create') do |on| on.message do |event, data| response.stream.write("data: #{data}\n\n") end end rescue IOError logger.info "Stream closed" ensure redis.quit response.stream.close end
これで、試すと、素早く、Twitterみたいに動きます!yay!
コードはこちら(GitHub)。
なんかおかしなところなどありましたら、ご指摘下さい。
コードもうちょいちっさく表示した方が見やすい気がするなぁ
Gistとか貼るのめんどいしなー、うーむ。