/
ja.html
191 lines (142 loc) · 12.5 KB
/
ja.html
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
<p><a href="http://railscasts.com/episodes/260-messaging-with-faye">エピソード260</a>では<a href="http://faye.jcoglan.com/">Faye</a>を使ってユーザのブラウザをリアルタイムに更新する方法を紹介しました。Fayeは優れているものの、動作させるまでに少し手間がかかります。完全にセキュアな状態にしたい場合は特にそうです。これを解決するためにRyan Batesが作成したのが、Fayeの上で動作する<a href="https://github.com/ryanb/private_pub">Private Pub</a>というgemです。彼が目指したのは、Railsアプリケーションでリアルタイムイベントをさらに簡単に配信/購読できるようにすることです。</p>
<p>今回扱うアプリケーションはシンプルなチャット用アプリケーションです。ページの下の方にあるテキストフィールドにメッセージを入力して「Send」をクリックすると、ブラウザはメッセージを保存するためにAJAXリクエストを送信し、ブラウザを更新してメインのチャットウィンドウにメッセージを追加します。</p>
<div class="imageWrapper">
<img src="http://asciicasts.com/system/photos/943/original/E316I01.png" width="800" height="399" alt="チャットアプリケーション"/>
</div>
<p>しかしこのアプリケーションには問題があります。複数のチャットクライアントが開いている場合、メッセージが送信されたときにすべてがリアルタイムには更新されません。メッセージを送信したクライアントだけに新しいメッセージが表示されます。他のすべてのクライアントは、メッセージを表示するためにページをリロードする必要があります。この問題を解決する方法はいくつかあります。一つは各クライアントが数秒ごとに新規メッセージを確認するためにポーリングをおこなう方法ですが、ベストなアイデアとはありません。もう一つの方法は、各クライアントでサーバへのソケットコネクションを開いたままにして、新規メッセージが来たらサーバがそれをプッシュして、クライアントにリアルタイムに表示されるようにします。この方法の問題は、Railsのアーキテクチャが長いリクエストを扱うように設定されていないという点です。</p>
<p>FayeとPrivate Pubがこの問題を解決してくれます。その詳細を見る前に、我々のチャットアプリケーションが動作する様子を見てみましょう。まずチャットページのビューテンプレートから始めます。</p>
``` /app/views/messages/index.html.erb
<h1>Chat</h1>
<ul id="chat">
<%= render @messages %>
</ul>
<%= form_for Message.new, remote: true do |f| %>
<%= f.text_field :content %>
<%= f.submit "Send" %>
<% end %>
```
<p>このテンプレートは<code>messages</code>と呼ばれる部分テンプレート(partial)をrenderしてメッセージのリストを表示し、新規メッセージを作成するためのフォームが含まれています。フォームは<code>remote: true</code>オプションを使用してAJAXリクエストを介して送信されます。フォームが送信されると<code>MessagesController</code>の<code>create</code>アクションに送られます。次にそれを見てみましょう。</p>
``` /app/controllers/messages_controller.rb
class MessagesController < ApplicationController
def index
@messages = Message.all
end
def create
@message = Message.create!(params[:message])
end
end
```
<p><code>create</code>アクションはとてもシンプルです。新規の<code>Message</code>レコードを作成しますが、このアクションがAJAXを介して呼び出されてJavaScriptテンプレートを表示するときに、何もレスポンスがないことに注意してください。そのJavaScriptテンプレートは以下のとおりです。</p>
``` /app/views/messages/create.js.erb
$("#chat").append("<%= j render(@message) %>");
$("#new_message")[0].reset();
```
<p>このJavaScriptは、新規に作成されたメッセージをチャットリストの末尾に追加し、新規メッセージを入力するテキストフィールドの中身をクリアします。やりたいことは、このメッセージをすべてのチャットクライアントにbroadcastして、送信したクライアントでだけでなく、すべてで新規メッセージが表示されるようにすることです。Private Pubを利用すれば、これを実現することができます。</p>
<h3>Private Pubのインストール</h3>
<p>Private Pubは通常の方法でインストールします。アプリケーションのgemfileにgemを追加して<code>bundle</code>コマンドを実行します。インストールをおこなうと、同時にFayeとその依存関係もインストールされます。</p>
``` /Gemfile
source 'http://rubygems.org'
gem 'rails', '3.1.3'
gem 'sqlite3'
# Gems used only for assets and not required
# in production environments by default.
group :assets do
gem 'sass-rails', '~> 3.1.5'
gem 'coffee-rails', '~> 3.1.1'
gem 'uglifier', '>= 1.0.3'
end
gem 'jquery-rails'
gem 'private_pub'
```
<p>次に以下のジェネレータを実行して、Fayeサーバを起動するのに必要な設定ファイルとRackupファイルを生成します。</p>
``` terminal
$ rails g private_pub:install
create config/private_pub.yml
create private_pub.ru
```
<p>以下のコマンドを実行してそのRackサーバを起動します。</p>
``` terminal
$ rackup private_pub.ru -s thin -E production
>> Thin web server (v1.3.1 codename Triple Espresso)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:9292, CTRL+C to stop
```
<p>このコマンドは、本番稼働環境でFayeを動作させるのに必要なThinサーバを使ってFayeを起動します。最後にアプリケーションのJavaScript manifestファイルに<code>private_pub</code>を追加します。</p>
``` /app/assets/javascripts/application.js
// This is a manifest file that'll be compiled into including all the files listed below.
// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
// be included in the compiled file accessible from http://example.com/assets/application.js
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// the compiled file.
//
//= require jquery
//= require jquery_ujs
//= require private_pub
//= require_tree .
```
<p>もし対象のアプリケーションがRails 3.1よりも前のバーションを使用している場合、このJavaScriptファイルをlayoutファイルにインクルードします。</p>
<h3>アプリケーションでPrivate Pubを使用する</h3>
<p>Private Pubが設定できたら、簡単にチャンネルを購読してそのチャンネルに更新通知を発行することができます。必要な作業は、ビューテンプレート(例えば今回作成したメッセージを表示するページ)でチャンネルを購読するために、<code>subscribe_to</code>を呼び出してチャンネル名を渡すだけです。Fayeのチャンネルの形式はパスの表記に似ていて、ここでは<code>/messages/new</code>を使用します。</p>
``` /app/views/messages/index.html.erb
<h1>Chat</h1>
<ul id="chat">
<%= render @messages %>
</ul>
<%= form_for Message.new, remote: true do |f| %>
<%= f.text_field :content %>
<%= f.submit "Send" %>
<% end %>
<%= subscribe_to "/messages/new" %>
```
<p>次にAJAXリクエストの結果として表示されるJavaScriptテンプレート(今回の例では新規のチャットメッセージを表示する部分)で、コードを<code>publish_to</code>ブロックでラップします。</p>
``` /app/views/messages/create.js.erb
<% publish_to "/messages/new" do %>
$("#chat").append("<%= j render(@message) %>");
$("#new_message")[0].reset();
<% end %>
```
<p>これをおこなうことはつまり、このJavaScriptが元のリクエストをおこなったクライアントだけに返されるのではなく、購読しているすべてのクライアントの<code>/messages/new</code>チャンネルに配信されることを意味します。ではこれを試してみましょう。アプリケーションを2つの別々のブラウザで開き、片方でメッセージを入力すると、すぐにもう一方に表示されます。</p>
<div class="imageWrapper">
<img src="http://asciicasts.com/system/photos/944/original/E316I02.png" width="851" height="418" alt="メッセージはすぐに全クライアントに表示される"/>
</div>
<p>つねにAJAXレスポンスから直接チャンネルに発行したいとは限りません。そうではなく例えばコントローラアクションから発行したい場合もあるでしょう。これをRubyコードのどこからでもおこなうことができます。<code>PrivatePub.publish_to</code>を使って、希望する発行先のチャンネル名とすべての購読しているクライアントで実行したいJavaScriptコードを渡します。これをアラートを返す方法で実際にやってみます。</p>
``` /app/controllers/messages_controller.rb
def create
@message = Message.create!(params[:message])
PrivatePub.publish_to("/messages/new", "alert('#{@message.content}');")
end
```
<p>一つのブラウザでメッセージを入力すると、両方のブラウザでアラートが表示されます。</p>
<div class="imageWrapper">
<img src="http://asciicasts.com/system/photos/945/original/E316I03.png" width="852" height="417" alt="アラートが両方のウィンドウで表示される"/>
</div>
<h3>JSONデータを扱う</h3>
<p>JavaScriptコードではなくJSONデータを使った処理を好むならそれも可能です。その場合は文字列以外のオブジェクト(例えばハッシュ)を<code>publish_to</code>に渡すとJSONに変換されます。</p>
``` /app/controllers/messages_controller.rb
def create
@message = Message.create!(params[:message])
PrivatePub.publish_to("/messages/new", message: @message)
end
```
<p>この方法をとる場合、返されるJSONを処理するために少しJavaScriptを書きます。これを<code>messages.js.coffee</code>に記述します。</p>
``` /app/assets/javascripts/messages.js.coffee
PrivatePub.subscribe "/messages/new", (data, channel) ->
alert data.message.content
```
<p>このファイルで<code>PrivatePub.subscribe</code>を呼び出します。これは引数にチャンネル名とコールバック関数(<code>data</code>と<code>channel</code>を引数にとる)をとります。このコールバックはチャンネルがJSONを受け取るたびに起動されます。我々のコードでは、返されたデータの<code>message.content</code>をただアラートで表示するだけです。ページをリロードして再度メッセージを入力すると、サーバから返されたJSONデータに基づくアラートが表示されます。</p>
<div class="imageWrapper">
<img src="http://asciicasts.com/system/photos/946/original/E316I04.png" width="800" height="415" alt="JSONからのアラート"/>
</div>
<p>Private Pubを利用する一つの利点は、デフォルトでチャンネルがプライベートになることです。つまりユーザが明示的に購読していないチャンネルを受信してしますことを心配する必要がありません。これによって例えば、チャンネルを購読しているユーザだけがメッセージを読むことができる、プライベートなチャットルームを簡単に作成することができます。また一定期間後に購読を期限切れにすることで、ログアウト後にユーザから見えないようにできます。Private Pubを利用するRailsアプリケーションには<code>private_pub.yml</code>ファイルがあり、そこで設定をおこないます。</p>
``` /config/private_pub.yml
development:
server: "http://localhost:9292/faye"
secret_token: "secret"
test:
server: "http://localhost:9292/faye"
secret_token: "secret"
production:
server: "http://example.com/faye"
secret_token: "210eb617b6ce1c351d986a3185d34025cf42e5091a37502f18f595f7e8773853"
signature_expiration: 3600 # one hour
```