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.
Files changed (68) hide show
  1. data/.gitignore +4 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +3 -0
  4. data/Gemfile +6 -1
  5. data/Gemfile.lock +28 -12
  6. data/MIT-LICENSE +1 -1
  7. data/README.md +122 -14
  8. data/Rakefile +18 -10
  9. data/bin/thin-socketrails +30 -0
  10. data/lib/websocket-rails.rb +11 -2
  11. data/lib/websocket_rails/base_controller.rb +91 -10
  12. data/lib/websocket_rails/connection_manager.rb +57 -27
  13. data/lib/websocket_rails/data_store.rb +34 -4
  14. data/lib/websocket_rails/dispatcher.rb +25 -46
  15. data/lib/websocket_rails/events.rb +53 -0
  16. data/lib/websocket_rails/version.rb +1 -1
  17. data/{test → spec}/dummy/Rakefile +0 -0
  18. data/{test → spec}/dummy/app/controllers/application_controller.rb +0 -0
  19. data/spec/dummy/app/controllers/chat_controller.rb +57 -0
  20. data/{test → spec}/dummy/app/helpers/application_helper.rb +0 -0
  21. data/{test → spec}/dummy/app/views/layouts/application.html.erb +0 -0
  22. data/{test → spec}/dummy/config.ru +0 -0
  23. data/{test → spec}/dummy/config/application.rb +1 -1
  24. data/{test → spec}/dummy/config/boot.rb +0 -0
  25. data/{test → spec}/dummy/config/database.yml +0 -0
  26. data/{test → spec}/dummy/config/environment.rb +0 -0
  27. data/{test → spec}/dummy/config/environments/development.rb +0 -0
  28. data/{test → spec}/dummy/config/environments/production.rb +0 -0
  29. data/{test → spec}/dummy/config/environments/test.rb +0 -0
  30. data/{test → spec}/dummy/config/initializers/backtrace_silencers.rb +0 -0
  31. data/spec/dummy/config/initializers/events.rb +7 -0
  32. data/{test → spec}/dummy/config/initializers/inflections.rb +0 -0
  33. data/{test → spec}/dummy/config/initializers/mime_types.rb +0 -0
  34. data/{test → spec}/dummy/config/initializers/secret_token.rb +0 -0
  35. data/{test → spec}/dummy/config/initializers/session_store.rb +0 -0
  36. data/{test → spec}/dummy/config/locales/en.yml +0 -0
  37. data/{test → spec}/dummy/config/routes.rb +0 -0
  38. data/{test/dummy/public/favicon.ico → spec/dummy/db/test.sqlite3} +0 -0
  39. data/{test/dummy/public/stylesheets/.gitkeep → spec/dummy/log/development.log} +0 -0
  40. data/spec/dummy/log/production.log +0 -0
  41. data/spec/dummy/log/server.log +0 -0
  42. data/spec/dummy/log/test.log +0 -0
  43. data/{test → spec}/dummy/public/404.html +0 -0
  44. data/{test → spec}/dummy/public/422.html +0 -0
  45. data/{test → spec}/dummy/public/500.html +0 -0
  46. data/spec/dummy/public/favicon.ico +0 -0
  47. data/{test → spec}/dummy/public/javascripts/application.js +0 -0
  48. data/{test → spec}/dummy/public/javascripts/controls.js +0 -0
  49. data/{test → spec}/dummy/public/javascripts/dragdrop.js +0 -0
  50. data/{test → spec}/dummy/public/javascripts/effects.js +0 -0
  51. data/{test → spec}/dummy/public/javascripts/prototype.js +0 -0
  52. data/{test → spec}/dummy/public/javascripts/rails.js +0 -0
  53. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  54. data/{test → spec}/dummy/script/rails +0 -0
  55. data/spec/spec_helper.rb +60 -0
  56. data/spec/support/mock_web_socket.rb +27 -0
  57. data/spec/unit/connection_manager_spec.rb +111 -0
  58. data/spec/unit/data_store_spec.rb +15 -0
  59. data/spec/unit/dispatcher_spec.rb +57 -0
  60. data/spec/unit/events_spec.rb +70 -0
  61. data/websocket-rails.gemspec +2 -2
  62. metadata +65 -53
  63. data/lib/websocket_rails/extensions/common.rb +0 -11
  64. data/lib/websocket_rails/extensions/websocket_rack.rb +0 -55
  65. data/test/integration/navigation_test.rb +0 -7
  66. data/test/support/integration_case.rb +0 -5
  67. data/test/test_helper.rb +0 -22
  68. data/test/websocket_rails_test.rb +0 -7
data/.gitignore CHANGED
@@ -1,6 +1,10 @@
1
+ .yardoc/
2
+ coverage/
3
+ .DS_Store
1
4
  .bundle/
2
5
  log/*.log
3
6
  pkg/
7
+ doc/
4
8
  test/dummy/db/*.sqlite3
5
9
  test/dummy/log/*.log
6
10
  test/dummy/tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
data/Gemfile CHANGED
@@ -1,3 +1,8 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- gemspec
3
+ gemspec
4
+
5
+ gem "rspec-rails"
6
+ gem "eventmachine", ">= 1.0.0.beta.3"
7
+ gem "faye-websocket"
8
+ gem "simplecov"
data/Gemfile.lock CHANGED
@@ -1,11 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- websocket-rails (0.0.1)
5
- em-websocket (~> 0.3.6)
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
- em-websocket (0.3.6)
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.12.10)
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
@@ -1,4 +1,4 @@
1
- Copyright 2012 YOURNAME
1
+ Copyright 2012 Dan Knox and Kyle Whalen
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,45 +1,71 @@
1
1
  # Websocket-Rails
2
2
 
3
- 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 embedded WebSocket server provided by the em-websocket gem.
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 gem is not even close to production ready. 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 and full test coverage.
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
- Add the gem to your Gemfile
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-websocket` executable provided by the websocket-rack gem to override the Thin connection timeout setting. The full command to start the server in development is `thin-websocket -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.
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-ruby'
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::Dispatcher.describe_events do
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 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_data` and `broadcast_data`. The `send_data` method sends a message to the client that initiated this event, the `broadcast_data` 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.
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
- message = {:message => 'this is a message'}
61
- broadcast_message :event_name, message
62
- send_message :event_name, message
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
- The message can be a string, hash, or array. The message is serialized as JSON before being sent to the client.
104
+ Here is an example controller for handling the `:new_message` event for a basic chat application.
66
105
 
67
- TODO: Show examples of using these methods.
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
- TODO: write documentation for the data store
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
- require 'rake/testtask'
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!
@@ -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
- WebsocketRails::Extensions::Common.apply!
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
- @_message.first
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.last
69
+ @_message
15
70
  end
16
-
17
- def send_message(event,message)
18
- @_dispatcher.send_message event.to_s, [client_id,message]
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
- def broadcast_message(event,message)
22
- @_dispatcher.broadcast_message event.to_s, message
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