websocket-rails 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +6 -1
- data/Gemfile.lock +28 -12
- data/MIT-LICENSE +1 -1
- data/README.md +122 -14
- data/Rakefile +18 -10
- data/bin/thin-socketrails +30 -0
- data/lib/websocket-rails.rb +11 -2
- data/lib/websocket_rails/base_controller.rb +91 -10
- data/lib/websocket_rails/connection_manager.rb +57 -27
- data/lib/websocket_rails/data_store.rb +34 -4
- data/lib/websocket_rails/dispatcher.rb +25 -46
- data/lib/websocket_rails/events.rb +53 -0
- data/lib/websocket_rails/version.rb +1 -1
- data/{test → spec}/dummy/Rakefile +0 -0
- data/{test → spec}/dummy/app/controllers/application_controller.rb +0 -0
- data/spec/dummy/app/controllers/chat_controller.rb +57 -0
- data/{test → spec}/dummy/app/helpers/application_helper.rb +0 -0
- data/{test → spec}/dummy/app/views/layouts/application.html.erb +0 -0
- data/{test → spec}/dummy/config.ru +0 -0
- data/{test → spec}/dummy/config/application.rb +1 -1
- data/{test → spec}/dummy/config/boot.rb +0 -0
- data/{test → spec}/dummy/config/database.yml +0 -0
- data/{test → spec}/dummy/config/environment.rb +0 -0
- data/{test → spec}/dummy/config/environments/development.rb +0 -0
- data/{test → spec}/dummy/config/environments/production.rb +0 -0
- data/{test → spec}/dummy/config/environments/test.rb +0 -0
- data/{test → spec}/dummy/config/initializers/backtrace_silencers.rb +0 -0
- data/spec/dummy/config/initializers/events.rb +7 -0
- data/{test → spec}/dummy/config/initializers/inflections.rb +0 -0
- data/{test → spec}/dummy/config/initializers/mime_types.rb +0 -0
- data/{test → spec}/dummy/config/initializers/secret_token.rb +0 -0
- data/{test → spec}/dummy/config/initializers/session_store.rb +0 -0
- data/{test → spec}/dummy/config/locales/en.yml +0 -0
- data/{test → spec}/dummy/config/routes.rb +0 -0
- data/{test/dummy/public/favicon.ico → spec/dummy/db/test.sqlite3} +0 -0
- data/{test/dummy/public/stylesheets/.gitkeep → spec/dummy/log/development.log} +0 -0
- data/spec/dummy/log/production.log +0 -0
- data/spec/dummy/log/server.log +0 -0
- data/spec/dummy/log/test.log +0 -0
- data/{test → spec}/dummy/public/404.html +0 -0
- data/{test → spec}/dummy/public/422.html +0 -0
- data/{test → spec}/dummy/public/500.html +0 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/{test → spec}/dummy/public/javascripts/application.js +0 -0
- data/{test → spec}/dummy/public/javascripts/controls.js +0 -0
- data/{test → spec}/dummy/public/javascripts/dragdrop.js +0 -0
- data/{test → spec}/dummy/public/javascripts/effects.js +0 -0
- data/{test → spec}/dummy/public/javascripts/prototype.js +0 -0
- data/{test → spec}/dummy/public/javascripts/rails.js +0 -0
- data/spec/dummy/public/stylesheets/.gitkeep +0 -0
- data/{test → spec}/dummy/script/rails +0 -0
- data/spec/spec_helper.rb +60 -0
- data/spec/support/mock_web_socket.rb +27 -0
- data/spec/unit/connection_manager_spec.rb +111 -0
- data/spec/unit/data_store_spec.rb +15 -0
- data/spec/unit/dispatcher_spec.rb +57 -0
- data/spec/unit/events_spec.rb +70 -0
- data/websocket-rails.gemspec +2 -2
- metadata +65 -53
- data/lib/websocket_rails/extensions/common.rb +0 -11
- data/lib/websocket_rails/extensions/websocket_rack.rb +0 -55
- data/test/integration/navigation_test.rb +0 -7
- data/test/support/integration_case.rb +0 -5
- data/test/test_helper.rb +0 -22
- data/test/websocket_rails_test.rb +0 -7
data/.gitignore
CHANGED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
websocket-rails (0.0
|
5
|
-
|
4
|
+
websocket-rails (0.1.0)
|
5
|
+
faye-websocket
|
6
6
|
rack
|
7
7
|
thin
|
8
|
-
websocket-rack
|
9
8
|
|
10
9
|
GEM
|
11
10
|
remote: http://rubygems.org/
|
@@ -37,16 +36,15 @@ GEM
|
|
37
36
|
activemodel (= 3.0.12)
|
38
37
|
activesupport (= 3.0.12)
|
39
38
|
activesupport (3.0.12)
|
40
|
-
addressable (2.2.7)
|
41
39
|
arel (2.0.10)
|
42
40
|
builder (2.1.2)
|
43
41
|
daemons (1.1.8)
|
44
|
-
|
45
|
-
addressable (>= 2.1.1)
|
46
|
-
eventmachine (>= 0.12.9)
|
42
|
+
diff-lcs (1.1.3)
|
47
43
|
erubis (2.6.6)
|
48
44
|
abstract (>= 1.0.0)
|
49
|
-
eventmachine (0.
|
45
|
+
eventmachine (1.0.0.beta.4)
|
46
|
+
faye-websocket (0.4.5)
|
47
|
+
eventmachine (>= 0.12.0)
|
50
48
|
i18n (0.5.0)
|
51
49
|
json (1.6.5)
|
52
50
|
mail (2.2.19)
|
@@ -55,6 +53,7 @@ GEM
|
|
55
53
|
mime-types (~> 1.16)
|
56
54
|
treetop (~> 1.4.8)
|
57
55
|
mime-types (1.18)
|
56
|
+
multi_json (1.3.6)
|
58
57
|
polyglot (0.3.3)
|
59
58
|
rack (1.2.5)
|
60
59
|
rack-mount (0.6.14)
|
@@ -78,6 +77,23 @@ GEM
|
|
78
77
|
rake (0.9.2.2)
|
79
78
|
rdoc (3.12)
|
80
79
|
json (~> 1.4)
|
80
|
+
rspec (2.9.0)
|
81
|
+
rspec-core (~> 2.9.0)
|
82
|
+
rspec-expectations (~> 2.9.0)
|
83
|
+
rspec-mocks (~> 2.9.0)
|
84
|
+
rspec-core (2.9.0)
|
85
|
+
rspec-expectations (2.9.1)
|
86
|
+
diff-lcs (~> 1.1.3)
|
87
|
+
rspec-mocks (2.9.0)
|
88
|
+
rspec-rails (2.9.0)
|
89
|
+
actionpack (>= 3.0)
|
90
|
+
activesupport (>= 3.0)
|
91
|
+
railties (>= 3.0)
|
92
|
+
rspec (~> 2.9.0)
|
93
|
+
simplecov (0.6.4)
|
94
|
+
multi_json (~> 1.0)
|
95
|
+
simplecov-html (~> 0.5.3)
|
96
|
+
simplecov-html (0.5.3)
|
81
97
|
sqlite3 (1.3.5)
|
82
98
|
thin (1.3.1)
|
83
99
|
daemons (>= 1.0.9)
|
@@ -88,16 +104,16 @@ GEM
|
|
88
104
|
polyglot
|
89
105
|
polyglot (>= 0.3.1)
|
90
106
|
tzinfo (0.3.32)
|
91
|
-
websocket-rack (0.3.3)
|
92
|
-
em-websocket (~> 0.3.6)
|
93
|
-
rack
|
94
|
-
thin
|
95
107
|
|
96
108
|
PLATFORMS
|
97
109
|
ruby
|
98
110
|
|
99
111
|
DEPENDENCIES
|
112
|
+
eventmachine (>= 1.0.0.beta.3)
|
113
|
+
faye-websocket
|
100
114
|
rails
|
101
115
|
rake
|
116
|
+
rspec-rails
|
117
|
+
simplecov
|
102
118
|
sqlite3
|
103
119
|
websocket-rails!
|
data/MIT-LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,45 +1,71 @@
|
|
1
1
|
# Websocket-Rails
|
2
2
|
|
3
|
-
|
3
|
+
[![Build Status](https://secure.travis-ci.org/DanKnox/websocket-rails.png)](https://secure.travis-ci.org/DanKnox/websocket-rails)
|
4
|
+
|
5
|
+
Plug and play WebSocket support for ruby on rails. Includes event router for mapping javascript events to controller actions. There is no need for a separate WebSocket server process. Requests to `/websocket` will be passed through to the `ConnectionManager` class which is a simple Rack based WebSocket server developed using the `Faye::WebSocket` library.
|
4
6
|
|
5
7
|
*Important Note*
|
6
8
|
|
7
|
-
This
|
9
|
+
This is mostly a proof of concept as of right now. Please try it out and let me know what you like or dislike. We will be adding much more soon including a development road map.
|
10
|
+
|
11
|
+
*Update*
|
12
|
+
|
13
|
+
We are finally very close to the first production release. Any comments or suggestions would be appreciated. The test coverage is now solid and the `ConnectionManager` class has been completely rewritten. There were a few bugs in the connection management on the first release that have been eliminated. Please upgrade to the latest version if you have not yet done so.
|
8
14
|
|
9
15
|
## Installation
|
10
16
|
|
11
|
-
|
17
|
+
Check out the [Example Application](https://github.com/DanKnox/websocket-rails-Example-Project) for additional information.
|
18
|
+
|
19
|
+
1. Add the gem to your Gemfile
|
20
|
+
3. Create a WebsocketRails controller - [See Documentation](http://rdoc.info/github/DanKnox/websocket-rails/master/WebsocketRails/BaseController)
|
21
|
+
4. Create an `events.rb` initializer file to map events to your controller - [See Documentation](http://rdoc.info/github/DanKnox/websocket-rails/master/WebsocketRails/Events)
|
22
|
+
5. Launch the web server and connect a WebSocket client to `ws://yourserver:port/websocket`
|
12
23
|
|
13
24
|
*Important Note About Web Servers*
|
14
25
|
|
15
|
-
Thin is the only web server currently supported. Use the `thin-
|
26
|
+
Thin is the only web server currently supported. Use the `thin-socketrails` executable to override the Thin connection timeout setting. The full command to start the server in development is `thin-socketrails -p 3000 start`. Be sure to enable config.threadsafe! in your rails application and use the Rack::Fiberpool middleware to take advantage of Thin's asynchronous request processing.
|
16
27
|
|
17
28
|
````ruby
|
18
|
-
gem 'websocket-
|
29
|
+
gem 'websocket-rails'
|
19
30
|
````
|
20
31
|
|
32
|
+
## Event Router
|
33
|
+
|
21
34
|
Map WebSocket events to controller actions by creating an `events.rb` file in your app/config/initializers directory
|
22
35
|
|
36
|
+
There are two built in events that are fired automatically by the dispatcher. The built in events are `:client_connected` and `:client_disconnected`. They are triggered when a new WebSocket client connects or disconnects to the server. You can handle them however you like by subscribing to the appropriate event in your `events.rb` file.
|
37
|
+
|
38
|
+
You can subscribe multiple controllers and actions to the same event to provide very clean event handling logic. The new message will be available in each controller using the `message` method discussed in the *Controllers* section below. The example event router below demonstrates subscribing to the `:new_message` event with one controller action to rebroadcast the message out to all connected clients and another controller action to log the message to a database.
|
39
|
+
|
23
40
|
````ruby
|
24
41
|
# app/config/initializers
|
25
42
|
|
26
|
-
WebsocketRails::
|
43
|
+
WebsocketRails::Events.describe_events do
|
44
|
+
# The :client_connected method is fired automatically when a new client connects
|
27
45
|
subscribe :client_connected, to: ChatController, with_method: :client_connected
|
46
|
+
|
47
|
+
# You can subscribe any number of controller actions to a single event
|
28
48
|
subscribe :new_message, to: ChatController, with_method: :new_message
|
49
|
+
subscribe :new_message, to: ChatLogController, with_method: :log_message
|
50
|
+
|
29
51
|
subscribe :new_user, to: ChatController, with_method: :new_user
|
30
52
|
subscribe :change_username, to: ChatController, with_method: :change_username
|
53
|
+
|
54
|
+
# The :client_disconnected method is fired automatically when a client disconnects
|
31
55
|
subscribe :client_disconnected, to: ChatController, with_method: :delete_user
|
32
56
|
end
|
33
57
|
````
|
34
58
|
|
35
59
|
The `subscribe` method takes the event name as the first argument, then a hash where `:to` is the Controller class and `:with_method` is the action to execute.
|
36
60
|
|
61
|
+
## Javascript Client
|
62
|
+
|
37
63
|
The websocket client must connect to `/websocket`. You can connect using the following javascript. Replace the port with the port that your web server is running on.
|
38
64
|
|
39
65
|
````javascript
|
40
66
|
var conn = new WebSocket("ws://localhost:3000/websocket")
|
41
67
|
conn.onopen = function(evt) {
|
42
|
-
dispatcher.trigger('new_user',current_user)
|
68
|
+
dispatcher.trigger('new_user',current_user) // Dispatcher not included
|
43
69
|
}
|
44
70
|
|
45
71
|
conn.onmessage = function(evt) {
|
@@ -54,21 +80,103 @@ We will be posting a basic javascript event dispatcher soon.
|
|
54
80
|
|
55
81
|
## Controllers
|
56
82
|
|
57
|
-
The
|
83
|
+
There are a few differences between WebSocket controllers and standard Rails controllers. The biggest of which, is that each event will be handled by the same, continually running instance of your controller class. This means that if you set any instance variables in your methods, they will still be available when the next event is processed. On top of that, every single client that is connected will share these same instance variables. This can be an advantage if used properly, but it can also lead to bugs if not expected. We provide our own `DataStore` object accessible in a WebsocketRails controller to make it easier to store data isolated from each connected client. This is explained further below.
|
84
|
+
|
85
|
+
Do not override the `initialize` method in your class to set up. Instead, define an `initialize_session` method and perform your set up there. The `initialize_session` method will be called the first time a controller is subscribed to an event in the event router. Instance variables defined in the `initialize_session` method will be available throughout the course of the server lifetime.
|
86
|
+
|
87
|
+
````
|
88
|
+
class ChatController < WebsocketRails::BaseController
|
89
|
+
def initialize_session
|
90
|
+
# perform application setup here
|
91
|
+
@message_count = 0
|
92
|
+
end
|
93
|
+
end
|
94
|
+
````
|
95
|
+
|
96
|
+
The Websocket::BaseController class provides methods for working with the WebSocket connection. Make sure you extend this class for controllers that you are using. The two most important methods are `send_message` and `broadcast_message`. The `send_message` method sends a message to the client that initiated this event, the `broadcast_message` method broadcasts messages to all connected clients. Both methods take two arguments, the event name to trigger on the client, and the message that accompanies it.
|
58
97
|
|
59
98
|
````ruby
|
60
|
-
|
61
|
-
broadcast_message :event_name,
|
62
|
-
send_message :event_name,
|
99
|
+
new_message = {:message => 'this is a message'}
|
100
|
+
broadcast_message :event_name, new_message
|
101
|
+
send_message :event_name, new_message
|
63
102
|
````
|
64
103
|
|
65
|
-
|
104
|
+
Here is an example controller for handling the `:new_message` event for a basic chat application.
|
66
105
|
|
67
|
-
|
106
|
+
````ruby
|
107
|
+
class ChatController < WebsocketRails::BaseController
|
108
|
+
def new_message
|
109
|
+
# Print the new message and client id to the console
|
110
|
+
puts "Message from client #{client_id} received: #{message.inspect}"
|
111
|
+
|
112
|
+
# Broadcast the new message to all connected clients
|
113
|
+
broadcast_message :new_message, message
|
114
|
+
end
|
115
|
+
end
|
116
|
+
````
|
117
|
+
|
118
|
+
We are using several of the methods provided by `WebsocketRails::BaseController` here, two of which are in the `puts` statement.
|
119
|
+
|
120
|
+
The first method used is the `client_id` method. This method contains the ID of the current WebSocket client that initiated this event. Each connected client is randomly assigned an ID upon connecting to the server. You can keep track of who is who by storing the `client_id` associated with each user somewhere. You can also use the provided `DataStore` (explained later) to make keeping track of users easier.
|
121
|
+
|
122
|
+
The next method used is the `message` method. This method will always return the message, if any, that was received along with the event initiated by the client. These messages are JSON decoded by the dispatcher automatically so you can serialize objects in your javascript client and send them along with events.
|
123
|
+
|
124
|
+
Lastly, the `broadcast_message` method is called, triggering the `:new_message` event on every connected client and sending the `message` received from the client along with it.
|
68
125
|
|
69
126
|
## Data Store
|
70
127
|
|
71
|
-
|
128
|
+
The `DataStore` object is a private Hash. When you access it in a controller, you will be accessing a Hash that is private to the client that initiated the event currently executing. You can use it exactly as you would a regular Hash, except you do not have to worry about it being overridden by the next client that triggers an event. This means that unlike instance variables in WebsocketRails controllers which are shared amongst all connected clients, the data store is an easy place to temporarily persist data for each user between events.
|
129
|
+
|
130
|
+
You can access the `DataStore` object by using the `data_store` controller method.
|
131
|
+
|
132
|
+
````ruby
|
133
|
+
class ChatController < WebsocketRails::BaseController
|
134
|
+
def new_user
|
135
|
+
# The instance variable would be overwritten when the next user joins
|
136
|
+
@user = User.new(name: message[:user_name]) # No Good!
|
137
|
+
|
138
|
+
# This will be private for each user
|
139
|
+
data_store[:user] = User.new(name: message[:user_name]) # Good!
|
140
|
+
broadcast_user_list
|
141
|
+
end
|
142
|
+
end
|
143
|
+
````
|
144
|
+
|
145
|
+
If you wish to output an Array of the assigned values in the data store for every connected client, you can use the `each_<key>` method, replacing `<key>` with the hash key that you wish to collect.
|
146
|
+
|
147
|
+
Given our ongoing chat server example, we could collect all of the current `User` objects like so:
|
148
|
+
|
149
|
+
````ruby
|
150
|
+
data_store[:user] = 'User3'
|
151
|
+
data_store.each_user
|
152
|
+
=> ['User1','User2','User3']
|
153
|
+
````
|
154
|
+
|
155
|
+
A simple method for broadcasting the current user list to all clients would look like this:
|
156
|
+
|
157
|
+
````ruby
|
158
|
+
def broadcast_user_list
|
159
|
+
users = data_store.each_user
|
160
|
+
broadcast_message :user_list, users
|
161
|
+
end
|
162
|
+
````
|
163
|
+
|
164
|
+
## Message Format
|
165
|
+
|
166
|
+
The message can be a string, hash, or array. The message is serialized as JSON before being sent to the client. The message arrives at the client as a two element serialized array with the `event_name` string as the first element and the message object you passed to the `message` parameter of the `send_message` method as the second element.
|
167
|
+
|
168
|
+
If you executed this code in your controller:
|
169
|
+
|
170
|
+
````ruby
|
171
|
+
new_message = {:message => 'this is a message'}
|
172
|
+
send_message :new_message, new_message
|
173
|
+
````
|
174
|
+
|
175
|
+
The message that arrives on the client would look like:
|
176
|
+
|
177
|
+
````javascript
|
178
|
+
['new_message',{message: 'this is a message'}]
|
179
|
+
````
|
72
180
|
|
73
181
|
## Development
|
74
182
|
|
data/Rakefile
CHANGED
@@ -17,16 +17,7 @@ end
|
|
17
17
|
|
18
18
|
Bundler::GemHelper.install_tasks
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
Rake::TestTask.new(:test) do |t|
|
23
|
-
t.libs << 'lib'
|
24
|
-
t.libs << 'test'
|
25
|
-
t.pattern = 'test/**/*_test.rb'
|
26
|
-
t.verbose = false
|
27
|
-
end
|
28
|
-
|
29
|
-
task :default => :test
|
20
|
+
task :default => :spec
|
30
21
|
|
31
22
|
RDoc::Task.new(:rdoc) do |rdoc|
|
32
23
|
rdoc.rdoc_dir = 'rdoc'
|
@@ -35,3 +26,20 @@ RDoc::Task.new(:rdoc) do |rdoc|
|
|
35
26
|
rdoc.rdoc_files.include('README.md')
|
36
27
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
37
28
|
end
|
29
|
+
|
30
|
+
require 'rspec/core/rake_task'
|
31
|
+
|
32
|
+
desc 'Default: run specs.'
|
33
|
+
task :default => :spec
|
34
|
+
|
35
|
+
desc "Run specs"
|
36
|
+
RSpec::Core::RakeTask.new do |t|
|
37
|
+
t.pattern = "./spec/**/*_spec.rb"
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "Generate code coverage"
|
41
|
+
task :coverage do
|
42
|
+
ENV['COVERAGE'] = 'true'
|
43
|
+
Rake::Task["spec"].execute
|
44
|
+
`open coverage/index.html`
|
45
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# * Borrowed from the <tt>websocket-rack</tt>
|
3
|
+
# Gem as a temporary workaround for the
|
4
|
+
# timeout problem.
|
5
|
+
#
|
6
|
+
# Modified Thin command line interface script.
|
7
|
+
# This is fallback for WebSocket-Rack.
|
8
|
+
# Use it when you have EventMachine version < 1.0.0
|
9
|
+
# Rationale:
|
10
|
+
# Older versions of EM have bug that prevent to
|
11
|
+
# clearing connection inactivity once it's set.
|
12
|
+
# This one will set connection timeout to 0 at
|
13
|
+
# default, so there will be no need to overwrite it.
|
14
|
+
# Be aware that this will also change inactivity
|
15
|
+
# timeout for "normal" connection, so it will be
|
16
|
+
# easy to make DoS attack.
|
17
|
+
|
18
|
+
require 'rubygems'
|
19
|
+
require 'thin'
|
20
|
+
|
21
|
+
if EM::VERSION < "1.0.0"
|
22
|
+
begin
|
23
|
+
old_verbose, $VERBOSE = $VERBOSE, nil
|
24
|
+
::Thin::Server.const_set 'DEFAULT_TIMEOUT', 0
|
25
|
+
ensure
|
26
|
+
$VERBOSE = old_verbose
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
Thin::Runner.new(ARGV).run!
|
data/lib/websocket-rails.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "active_support/dependencies"
|
2
|
+
require 'thin'
|
2
3
|
|
3
4
|
module WebsocketRails
|
4
5
|
mattr_accessor :app_root
|
@@ -7,12 +8,20 @@ module WebsocketRails
|
|
7
8
|
yield self
|
8
9
|
end
|
9
10
|
|
11
|
+
def self.route_block=(routes)
|
12
|
+
@event_routes = routes
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.route_block
|
16
|
+
@event_routes
|
17
|
+
end
|
10
18
|
end
|
11
19
|
|
12
20
|
require "websocket_rails/engine"
|
13
21
|
require 'websocket_rails/connection_manager'
|
14
22
|
require 'websocket_rails/dispatcher'
|
23
|
+
require 'websocket_rails/events'
|
15
24
|
require 'websocket_rails/base_controller'
|
16
|
-
require 'websocket_rails/extensions/common'
|
17
25
|
|
18
|
-
|
26
|
+
::Thin::Server.send( :remove_const, 'DEFAULT_TIMEOUT' )
|
27
|
+
::Thin::Server.const_set( 'DEFAULT_TIMEOUT', 0 )
|
@@ -1,29 +1,110 @@
|
|
1
1
|
require 'websocket_rails/data_store'
|
2
2
|
|
3
3
|
module WebsocketRails
|
4
|
+
# Provides controller helper methods for developing a WebsocketRails controller. Action methods
|
5
|
+
# defined on a WebsocketRails controller can be mapped to events using the {Events} class.
|
6
|
+
# This class should be sub classed in a user's application, similar to the ApplicationController
|
7
|
+
# in a Rails application. You can create your WebsocketRails controllers in your standard Rails
|
8
|
+
# controllers directory.
|
9
|
+
#
|
10
|
+
# == Example WebsocketRails controller
|
11
|
+
# class ChatController < WebsocketRails::BaseController
|
12
|
+
# # Can be mapped to the :client_connected event in the events.rb file.
|
13
|
+
# def new_user
|
14
|
+
# send_message :new_message, {:message => 'Welcome to the Chat Room!'}
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# It is best to use the provided {DataStore} to temporarily persist data for each client between
|
19
|
+
# events. Read more about it in the {DataStore} documentation.
|
4
20
|
class BaseController
|
21
|
+
|
22
|
+
# Add observers to specific events or the controller in general. This functionality is similar
|
23
|
+
# to the Rails before_filter methods. Observers are stored as Proc objects and have access
|
24
|
+
# to the current controller environment.
|
25
|
+
#
|
26
|
+
# Observing all events sent to a controller:
|
27
|
+
# class ChatController < WebsocketRails::BaseController
|
28
|
+
# observe {
|
29
|
+
# if data_store.each_user.count > 0
|
30
|
+
# puts 'a user has joined'
|
31
|
+
# end
|
32
|
+
# }
|
33
|
+
# end
|
34
|
+
# Observing a single event that occurrs:
|
35
|
+
# observe(:new_message) {
|
36
|
+
# puts 'new_message has fired!'
|
37
|
+
# }
|
38
|
+
def self.observe(event = nil, &block)
|
39
|
+
if event
|
40
|
+
@@observers[event] << block
|
41
|
+
else
|
42
|
+
@@observers[:general] << block
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Stores the observer Procs for the current controller. See {observe} for details.
|
47
|
+
@@observers = Hash.new {|h,k| h[k] = Array.new}
|
48
|
+
|
5
49
|
def initialize
|
6
50
|
@data_store = DataStore.new(self)
|
7
51
|
end
|
8
52
|
|
53
|
+
# Provides direct access to the Faye::WebSocket connection object for the client that
|
54
|
+
# initiated the event that is currently being executed.
|
55
|
+
def connection
|
56
|
+
@_connection
|
57
|
+
end
|
58
|
+
|
59
|
+
# The numerical ID for the client connection that initiated the event. The ID is unique
|
60
|
+
# for each currently active connection but can not be used to associate a client between
|
61
|
+
# multiple connection attempts.
|
9
62
|
def client_id
|
10
|
-
|
63
|
+
connection.object_id
|
11
64
|
end
|
12
|
-
|
65
|
+
|
66
|
+
# The current message that was passed from the client when the event was initiated. The
|
67
|
+
# message is typically a standard ruby Hash object. See the README for more information.
|
13
68
|
def message
|
14
|
-
@_message
|
69
|
+
@_message
|
15
70
|
end
|
16
|
-
|
17
|
-
|
18
|
-
|
71
|
+
|
72
|
+
# Sends a message to the client that initiated the current event being executed. Messages
|
73
|
+
# are serialized as JSON into a two element Array where the first element is the event
|
74
|
+
# and the second element is the message that was passed, typically a Hash.
|
75
|
+
# message_hash = {:message => 'new message for the client'}
|
76
|
+
# send_message :new_message, message_hash
|
77
|
+
# # Will arrive on the client as JSON string like the following:
|
78
|
+
# # ['new_message',{message: 'new message for the client'}]
|
79
|
+
def send_message(event, message)
|
80
|
+
@_dispatcher.send_message event.to_s, message, connection if @_dispatcher.respond_to?(:send_message)
|
19
81
|
end
|
20
|
-
|
21
|
-
|
22
|
-
|
82
|
+
|
83
|
+
# Broadcasts a message to all connected clients. See {#send_message} for message passing details.
|
84
|
+
def broadcast_message(event, message)
|
85
|
+
@_dispatcher.broadcast_message event.to_s, message if @_dispatcher.respond_to?(:broadcast_message)
|
23
86
|
end
|
24
87
|
|
88
|
+
# Provides access to the {DataStore} for the current controller. The {DataStore} provides convenience
|
89
|
+
# methods for keeping track of data associated with active connections. See it's documentation for
|
90
|
+
# more information.
|
25
91
|
def data_store
|
26
92
|
@data_store
|
27
|
-
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
# Executes the observers that have been defined for this controller. General observers are executed
|
98
|
+
# first and event specific observers are executed last. Each will be executed in the order that
|
99
|
+
# they have been defined. This method is executed by the {Dispatcher}.
|
100
|
+
def execute_observers(event)
|
101
|
+
@@observers[:general].each do |observer|
|
102
|
+
instance_eval( &observer )
|
103
|
+
end
|
104
|
+
@@observers[event].each do |observer|
|
105
|
+
instance_eval( &observer )
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
28
109
|
end
|
29
110
|
end
|