websocket-rails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. data/.gitignore +6 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +103 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +79 -0
  6. data/Rakefile +37 -0
  7. data/config/routes.rb +3 -0
  8. data/lib/websocket-rails.rb +18 -0
  9. data/lib/websocket_rails/base_controller.rb +29 -0
  10. data/lib/websocket_rails/connection_manager.rb +38 -0
  11. data/lib/websocket_rails/data_store.rb +49 -0
  12. data/lib/websocket_rails/dispatcher.rb +71 -0
  13. data/lib/websocket_rails/engine.rb +15 -0
  14. data/lib/websocket_rails/extensions/common.rb +11 -0
  15. data/lib/websocket_rails/extensions/websocket_rack.rb +55 -0
  16. data/lib/websocket_rails/version.rb +3 -0
  17. data/test/dummy/Rakefile +7 -0
  18. data/test/dummy/app/controllers/application_controller.rb +3 -0
  19. data/test/dummy/app/helpers/application_helper.rb +2 -0
  20. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  21. data/test/dummy/config.ru +4 -0
  22. data/test/dummy/config/application.rb +45 -0
  23. data/test/dummy/config/boot.rb +10 -0
  24. data/test/dummy/config/database.yml +22 -0
  25. data/test/dummy/config/environment.rb +5 -0
  26. data/test/dummy/config/environments/development.rb +26 -0
  27. data/test/dummy/config/environments/production.rb +49 -0
  28. data/test/dummy/config/environments/test.rb +35 -0
  29. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  30. data/test/dummy/config/initializers/inflections.rb +10 -0
  31. data/test/dummy/config/initializers/mime_types.rb +5 -0
  32. data/test/dummy/config/initializers/secret_token.rb +7 -0
  33. data/test/dummy/config/initializers/session_store.rb +8 -0
  34. data/test/dummy/config/locales/en.yml +5 -0
  35. data/test/dummy/config/routes.rb +58 -0
  36. data/test/dummy/public/404.html +26 -0
  37. data/test/dummy/public/422.html +26 -0
  38. data/test/dummy/public/500.html +26 -0
  39. data/test/dummy/public/favicon.ico +0 -0
  40. data/test/dummy/public/javascripts/application.js +2 -0
  41. data/test/dummy/public/javascripts/controls.js +965 -0
  42. data/test/dummy/public/javascripts/dragdrop.js +974 -0
  43. data/test/dummy/public/javascripts/effects.js +1123 -0
  44. data/test/dummy/public/javascripts/prototype.js +6001 -0
  45. data/test/dummy/public/javascripts/rails.js +202 -0
  46. data/test/dummy/public/stylesheets/.gitkeep +0 -0
  47. data/test/dummy/script/rails +6 -0
  48. data/test/integration/navigation_test.rb +7 -0
  49. data/test/support/integration_case.rb +5 -0
  50. data/test/test_helper.rb +22 -0
  51. data/test/websocket_rails_test.rb +7 -0
  52. data/websocket-rails.gemspec +26 -0
  53. metadata +218 -0
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ .bundle/
2
+ log/*.log
3
+ pkg/
4
+ test/dummy/db/*.sqlite3
5
+ test/dummy/log/*.log
6
+ test/dummy/tmp/
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,103 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ websocket-rails (0.0.1)
5
+ em-websocket (~> 0.3.6)
6
+ rack
7
+ thin
8
+ websocket-rack
9
+
10
+ GEM
11
+ remote: http://rubygems.org/
12
+ specs:
13
+ abstract (1.0.0)
14
+ actionmailer (3.0.12)
15
+ actionpack (= 3.0.12)
16
+ mail (~> 2.2.19)
17
+ actionpack (3.0.12)
18
+ activemodel (= 3.0.12)
19
+ activesupport (= 3.0.12)
20
+ builder (~> 2.1.2)
21
+ erubis (~> 2.6.6)
22
+ i18n (~> 0.5.0)
23
+ rack (~> 1.2.5)
24
+ rack-mount (~> 0.6.14)
25
+ rack-test (~> 0.5.7)
26
+ tzinfo (~> 0.3.23)
27
+ activemodel (3.0.12)
28
+ activesupport (= 3.0.12)
29
+ builder (~> 2.1.2)
30
+ i18n (~> 0.5.0)
31
+ activerecord (3.0.12)
32
+ activemodel (= 3.0.12)
33
+ activesupport (= 3.0.12)
34
+ arel (~> 2.0.10)
35
+ tzinfo (~> 0.3.23)
36
+ activeresource (3.0.12)
37
+ activemodel (= 3.0.12)
38
+ activesupport (= 3.0.12)
39
+ activesupport (3.0.12)
40
+ addressable (2.2.7)
41
+ arel (2.0.10)
42
+ builder (2.1.2)
43
+ daemons (1.1.8)
44
+ em-websocket (0.3.6)
45
+ addressable (>= 2.1.1)
46
+ eventmachine (>= 0.12.9)
47
+ erubis (2.6.6)
48
+ abstract (>= 1.0.0)
49
+ eventmachine (0.12.10)
50
+ i18n (0.5.0)
51
+ json (1.6.5)
52
+ mail (2.2.19)
53
+ activesupport (>= 2.3.6)
54
+ i18n (>= 0.4.0)
55
+ mime-types (~> 1.16)
56
+ treetop (~> 1.4.8)
57
+ mime-types (1.18)
58
+ polyglot (0.3.3)
59
+ rack (1.2.5)
60
+ rack-mount (0.6.14)
61
+ rack (>= 1.0.0)
62
+ rack-test (0.5.7)
63
+ rack (>= 1.0)
64
+ rails (3.0.12)
65
+ actionmailer (= 3.0.12)
66
+ actionpack (= 3.0.12)
67
+ activerecord (= 3.0.12)
68
+ activeresource (= 3.0.12)
69
+ activesupport (= 3.0.12)
70
+ bundler (~> 1.0)
71
+ railties (= 3.0.12)
72
+ railties (3.0.12)
73
+ actionpack (= 3.0.12)
74
+ activesupport (= 3.0.12)
75
+ rake (>= 0.8.7)
76
+ rdoc (~> 3.4)
77
+ thor (~> 0.14.4)
78
+ rake (0.9.2.2)
79
+ rdoc (3.12)
80
+ json (~> 1.4)
81
+ sqlite3 (1.3.5)
82
+ thin (1.3.1)
83
+ daemons (>= 1.0.9)
84
+ eventmachine (>= 0.12.6)
85
+ rack (>= 1.0.0)
86
+ thor (0.14.6)
87
+ treetop (1.4.10)
88
+ polyglot
89
+ polyglot (>= 0.3.1)
90
+ tzinfo (0.3.32)
91
+ websocket-rack (0.3.3)
92
+ em-websocket (~> 0.3.6)
93
+ rack
94
+ thin
95
+
96
+ PLATFORMS
97
+ ruby
98
+
99
+ DEPENDENCIES
100
+ rails
101
+ rake
102
+ sqlite3
103
+ websocket-rails!
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # Websocket-Rails
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.
4
+
5
+ *Important Note*
6
+
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.
8
+
9
+ ## Installation
10
+
11
+ Add the gem to your Gemfile
12
+
13
+ *Important Note About Web Servers*
14
+
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.
16
+
17
+ ````ruby
18
+ gem 'websocket-ruby'
19
+ ````
20
+
21
+ Map WebSocket events to controller actions by creating an `events.rb` file in your app/config/initializers directory
22
+
23
+ ````ruby
24
+ # app/config/initializers
25
+
26
+ WebsocketRails::Dispatcher.describe_events do
27
+ subscribe :client_connected, to: ChatController, with_method: :client_connected
28
+ subscribe :new_message, to: ChatController, with_method: :new_message
29
+ subscribe :new_user, to: ChatController, with_method: :new_user
30
+ subscribe :change_username, to: ChatController, with_method: :change_username
31
+ subscribe :client_disconnected, to: ChatController, with_method: :delete_user
32
+ end
33
+ ````
34
+
35
+ 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
+
37
+ 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
+
39
+ ````javascript
40
+ var conn = new WebSocket("ws://localhost:3000/websocket")
41
+ conn.onopen = function(evt) {
42
+ dispatcher.trigger('new_user',current_user)
43
+ }
44
+
45
+ conn.onmessage = function(evt) {
46
+ var data = JSON.parse(evt.data),
47
+ event_name = data[0],
48
+ message = data[1];
49
+ console.log(data)
50
+ }
51
+ ````
52
+
53
+ We will be posting a basic javascript event dispatcher soon.
54
+
55
+ ## Controllers
56
+
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.
58
+
59
+ ````ruby
60
+ message = {:message => 'this is a message'}
61
+ broadcast_message :event_name, message
62
+ send_message :event_name, message
63
+ ````
64
+
65
+ The message can be a string, hash, or array. The message is serialized as JSON before being sent to the client.
66
+
67
+ TODO: Show examples of using these methods.
68
+
69
+ ## Data Store
70
+
71
+ TODO: write documentation for the data store
72
+
73
+ ## Development
74
+
75
+ This gem is created and maintained by Dan Knox and Kyle Whalen under the MIT License.
76
+
77
+ Brought to you by:
78
+
79
+ Three Dot Loft LLC
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ # encoding: UTF-8
2
+ require 'rubygems'
3
+ require 'rake'
4
+ begin
5
+ require 'bundler/setup'
6
+ rescue LoadError
7
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
8
+ end
9
+
10
+ begin
11
+ require 'rdoc/task'
12
+ rescue LoadError
13
+ require 'rdoc/rdoc'
14
+ require 'rake/rdoctask'
15
+ RDoc::Task = Rake::RDocTask
16
+ end
17
+
18
+ Bundler::GemHelper.install_tasks
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
30
+
31
+ RDoc::Task.new(:rdoc) do |rdoc|
32
+ rdoc.rdoc_dir = 'rdoc'
33
+ rdoc.title = 'websocket-rails'
34
+ rdoc.options << '--line-numbers'
35
+ rdoc.rdoc_files.include('README.md')
36
+ rdoc.rdoc_files.include('lib/**/*.rb')
37
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ Rails.application.routes.draw do
2
+ match "/websocket", :to => WebsocketRails::ConnectionManager.new
3
+ end
@@ -0,0 +1,18 @@
1
+ require "active_support/dependencies"
2
+
3
+ module WebsocketRails
4
+ mattr_accessor :app_root
5
+
6
+ def self.setup
7
+ yield self
8
+ end
9
+
10
+ end
11
+
12
+ require "websocket_rails/engine"
13
+ require 'websocket_rails/connection_manager'
14
+ require 'websocket_rails/dispatcher'
15
+ require 'websocket_rails/base_controller'
16
+ require 'websocket_rails/extensions/common'
17
+
18
+ WebsocketRails::Extensions::Common.apply!
@@ -0,0 +1,29 @@
1
+ require 'websocket_rails/data_store'
2
+
3
+ module WebsocketRails
4
+ class BaseController
5
+ def initialize
6
+ @data_store = DataStore.new(self)
7
+ end
8
+
9
+ def client_id
10
+ @_message.first
11
+ end
12
+
13
+ def message
14
+ @_message.last
15
+ end
16
+
17
+ def send_message(event,message)
18
+ @_dispatcher.send_message event.to_s, [client_id,message]
19
+ end
20
+
21
+ def broadcast_message(event,message)
22
+ @_dispatcher.broadcast_message event.to_s, message
23
+ end
24
+
25
+ def data_store
26
+ @data_store
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,38 @@
1
+ require 'rack'
2
+ require 'rack/websocket'
3
+ require 'json'
4
+ module WebsocketRails
5
+ class ConnectionManager < Rack::WebSocket::Application
6
+ def initialize(*args)
7
+ @dispatcher = Dispatcher.new(self)
8
+ super
9
+ end
10
+
11
+ def on_open(env)
12
+ puts "Client connected\n"
13
+ @dispatcher.dispatch('client_connected',{},env)
14
+ end
15
+
16
+ def on_message(env, msg)
17
+ @dispatcher.receive( msg, env )
18
+ end
19
+
20
+ def on_error(env, error)
21
+ puts "Error occured: " + error.message
22
+ end
23
+
24
+ def on_close(env)
25
+ close_connection(env['websocket.client_id'])
26
+ @dispatcher.dispatch('client_disconnected',{},env)
27
+ puts "Client disconnected\n"
28
+ end
29
+
30
+ def send_message(msg,uid)
31
+ send_data msg, uid
32
+ end
33
+
34
+ def broadcast_message(msg)
35
+ send_data_all msg
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,49 @@
1
+ module WebsocketRails
2
+ class DataStore
3
+ def initialize(base_controller)
4
+ @base = base_controller
5
+ @data = Hash.new {|h,k| h[k] = Hash.new}
6
+ @data = @data.with_indifferent_access
7
+ end
8
+
9
+ def []=(k,v)
10
+ @data[cid] = Hash.new unless @data[cid]
11
+ @data[cid][k] = v
12
+ end
13
+
14
+ def [](k)
15
+ @data[cid][k] = Hash.new unless @data[cid]
16
+ @data[cid][k]
17
+ end
18
+
19
+ def each(&block)
20
+ @data.each do |cid,hash|
21
+ block.call(hash) if block
22
+ end
23
+ end
24
+
25
+ def remove_client
26
+ @data.delete(cid)
27
+ end
28
+
29
+ def delete(key)
30
+ @data[cid].delete(key)
31
+ end
32
+
33
+ def cid
34
+ @base.client_id
35
+ end
36
+
37
+ def method_missing(method, *args, &block)
38
+ if /each_(?<hash_key>\w*)/ =~ method
39
+ results = []
40
+ @data.each do |cid,hash|
41
+ results << hash[hash_key]
42
+ end
43
+ results
44
+ else
45
+ super
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,71 @@
1
+ require 'json'
2
+
3
+ module WebsocketRails
4
+ class Dispatcher
5
+ def initialize(connection)
6
+ puts "Initializing dispatcher\n"
7
+ @connection = connection
8
+ @events = Hash.new {|h,k| h[k] = Array.new}
9
+ @classes = Hash.new
10
+ evaluate(&@@event_routes) if @@event_routes
11
+ end
12
+
13
+ def receive(enc_message,env)
14
+ message = JSON.parse( enc_message )
15
+ event_name = message.first
16
+ data = message.last
17
+ data['received'] = Time.now.strftime("%I:%M:%p")
18
+ dispatch( event_name, data, env )
19
+ end
20
+
21
+ def send_message(event_name,data)
22
+ @connection.send_message encoded_message( event_name, data.last ), data.first
23
+ end
24
+
25
+ def broadcast_message(event_name,data)
26
+ @connection.broadcast_message encoded_message( event_name, data )
27
+ end
28
+
29
+ def dispatch(event_name,data,env)
30
+ puts "#{event_name} is handled by #{@events[event_name.to_sym].inspect}\n\n"
31
+ message = [env['websocket.client_id'],data]
32
+ Fiber.new {
33
+ @events[event_name.to_sym].each do |event|
34
+ handler = event.first
35
+ klass = @classes[handler]
36
+ klass.instance_variable_set(:@_message,message)
37
+ method = event.last
38
+ klass.send( method )
39
+ end
40
+ }.resume
41
+ end
42
+
43
+ def close_connection
44
+ @connection.close_connection
45
+ end
46
+
47
+ def encoded_message(event_name,data)
48
+ [event_name, data].to_json
49
+ end
50
+
51
+ def subscribe(event_name,options)
52
+ klass = options[:to] || raise("Must specify a class for to: option in event route")
53
+ method = options[:with_method] || raise("Must specify a method for with_method: option in event route")
54
+ controller = klass.new
55
+ if @classes[klass].nil?
56
+ @classes[klass] = controller
57
+ controller.instance_variable_set(:@_dispatcher,self)
58
+ controller.send :initialize_session if controller.respond_to?(:initialize_session)
59
+ end
60
+ @events[event_name] << [klass,method]
61
+ end
62
+
63
+ def self.describe_events(&block)
64
+ @@event_routes = block
65
+ end
66
+
67
+ def evaluate(&block)
68
+ instance_eval &block
69
+ end
70
+ end
71
+ end