hkroger-websocket-rails 0.7.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 (120) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +328 -0
  3. data/Gemfile +27 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +237 -0
  6. data/Rakefile +72 -0
  7. data/bin/thin-socketrails +45 -0
  8. data/lib/assets/javascripts/websocket_rails/abstract_connection.js.coffee +45 -0
  9. data/lib/assets/javascripts/websocket_rails/channel.js.coffee +70 -0
  10. data/lib/assets/javascripts/websocket_rails/event.js.coffee +42 -0
  11. data/lib/assets/javascripts/websocket_rails/http_connection.js.coffee +66 -0
  12. data/lib/assets/javascripts/websocket_rails/main.js +6 -0
  13. data/lib/assets/javascripts/websocket_rails/websocket_connection.js.coffee +29 -0
  14. data/lib/assets/javascripts/websocket_rails/websocket_rails.js.coffee +158 -0
  15. data/lib/config.ru +3 -0
  16. data/lib/generators/websocket_rails/install/install_generator.rb +33 -0
  17. data/lib/generators/websocket_rails/install/templates/events.rb +14 -0
  18. data/lib/generators/websocket_rails/install/templates/websocket_rails.rb +63 -0
  19. data/lib/hkroger-websocket-rails.rb +1 -0
  20. data/lib/rails/app/controllers/websocket_rails/delegation_controller.rb +13 -0
  21. data/lib/rails/config/routes.rb +7 -0
  22. data/lib/rails/tasks/websocket_rails.tasks +42 -0
  23. data/lib/spec_helpers/matchers/route_matchers.rb +65 -0
  24. data/lib/spec_helpers/matchers/trigger_matchers.rb +113 -0
  25. data/lib/spec_helpers/spec_helper_event.rb +34 -0
  26. data/lib/websocket-rails.rb +108 -0
  27. data/lib/websocket_rails/base_controller.rb +197 -0
  28. data/lib/websocket_rails/channel.rb +97 -0
  29. data/lib/websocket_rails/channel_manager.rb +55 -0
  30. data/lib/websocket_rails/configuration.rb +169 -0
  31. data/lib/websocket_rails/connection_adapters.rb +195 -0
  32. data/lib/websocket_rails/connection_adapters/http.rb +120 -0
  33. data/lib/websocket_rails/connection_adapters/web_socket.rb +36 -0
  34. data/lib/websocket_rails/connection_manager.rb +119 -0
  35. data/lib/websocket_rails/controller_factory.rb +80 -0
  36. data/lib/websocket_rails/data_store.rb +145 -0
  37. data/lib/websocket_rails/dispatcher.rb +129 -0
  38. data/lib/websocket_rails/engine.rb +26 -0
  39. data/lib/websocket_rails/event.rb +189 -0
  40. data/lib/websocket_rails/event_map.rb +184 -0
  41. data/lib/websocket_rails/event_queue.rb +33 -0
  42. data/lib/websocket_rails/internal_events.rb +37 -0
  43. data/lib/websocket_rails/logging.rb +133 -0
  44. data/lib/websocket_rails/spec_helpers.rb +3 -0
  45. data/lib/websocket_rails/synchronization.rb +182 -0
  46. data/lib/websocket_rails/user_manager.rb +276 -0
  47. data/lib/websocket_rails/version.rb +3 -0
  48. data/spec/dummy/Rakefile +7 -0
  49. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  50. data/spec/dummy/app/controllers/chat_controller.rb +53 -0
  51. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  52. data/spec/dummy/app/models/user.rb +2 -0
  53. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  54. data/spec/dummy/config.ru +4 -0
  55. data/spec/dummy/config/application.rb +45 -0
  56. data/spec/dummy/config/boot.rb +10 -0
  57. data/spec/dummy/config/database.yml +22 -0
  58. data/spec/dummy/config/environment.rb +5 -0
  59. data/spec/dummy/config/environments/development.rb +26 -0
  60. data/spec/dummy/config/environments/production.rb +49 -0
  61. data/spec/dummy/config/environments/test.rb +34 -0
  62. data/spec/dummy/config/events.rb +7 -0
  63. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  64. data/spec/dummy/config/initializers/inflections.rb +10 -0
  65. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  66. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  67. data/spec/dummy/config/initializers/session_store.rb +8 -0
  68. data/spec/dummy/config/locales/en.yml +5 -0
  69. data/spec/dummy/config/routes.rb +58 -0
  70. data/spec/dummy/db/development.sqlite3 +0 -0
  71. data/spec/dummy/db/migrate/20130902222552_create_users.rb +10 -0
  72. data/spec/dummy/db/schema.rb +23 -0
  73. data/spec/dummy/db/test.sqlite3 +0 -0
  74. data/spec/dummy/log/development.log +17 -0
  75. data/spec/dummy/log/production.log +0 -0
  76. data/spec/dummy/log/server.log +0 -0
  77. data/spec/dummy/public/404.html +26 -0
  78. data/spec/dummy/public/422.html +26 -0
  79. data/spec/dummy/public/500.html +26 -0
  80. data/spec/dummy/public/favicon.ico +0 -0
  81. data/spec/dummy/public/javascripts/application.js +2 -0
  82. data/spec/dummy/public/javascripts/controls.js +965 -0
  83. data/spec/dummy/public/javascripts/dragdrop.js +974 -0
  84. data/spec/dummy/public/javascripts/effects.js +1123 -0
  85. data/spec/dummy/public/javascripts/prototype.js +6001 -0
  86. data/spec/dummy/public/javascripts/rails.js +202 -0
  87. data/spec/dummy/script/rails +6 -0
  88. data/spec/integration/connection_manager_spec.rb +135 -0
  89. data/spec/javascripts/support/jasmine.yml +52 -0
  90. data/spec/javascripts/support/jasmine_helper.rb +38 -0
  91. data/spec/javascripts/support/vendor/sinon-1.7.1.js +4343 -0
  92. data/spec/javascripts/websocket_rails/channel_spec.coffee +112 -0
  93. data/spec/javascripts/websocket_rails/event_spec.coffee +69 -0
  94. data/spec/javascripts/websocket_rails/helpers.coffee +6 -0
  95. data/spec/javascripts/websocket_rails/websocket_connection_spec.coffee +158 -0
  96. data/spec/javascripts/websocket_rails/websocket_rails_spec.coffee +274 -0
  97. data/spec/spec_helper.rb +41 -0
  98. data/spec/spec_helpers/matchers/route_matchers_spec.rb +109 -0
  99. data/spec/spec_helpers/matchers/trigger_matchers_spec.rb +247 -0
  100. data/spec/spec_helpers/spec_helper_event_spec.rb +66 -0
  101. data/spec/support/helper_methods.rb +42 -0
  102. data/spec/support/mock_web_socket.rb +41 -0
  103. data/spec/unit/base_controller_spec.rb +74 -0
  104. data/spec/unit/channel_manager_spec.rb +58 -0
  105. data/spec/unit/channel_spec.rb +169 -0
  106. data/spec/unit/connection_adapters/http_spec.rb +88 -0
  107. data/spec/unit/connection_adapters/web_socket_spec.rb +30 -0
  108. data/spec/unit/connection_adapters_spec.rb +259 -0
  109. data/spec/unit/connection_manager_spec.rb +148 -0
  110. data/spec/unit/controller_factory_spec.rb +76 -0
  111. data/spec/unit/data_store_spec.rb +106 -0
  112. data/spec/unit/dispatcher_spec.rb +203 -0
  113. data/spec/unit/event_map_spec.rb +120 -0
  114. data/spec/unit/event_queue_spec.rb +36 -0
  115. data/spec/unit/event_spec.rb +181 -0
  116. data/spec/unit/logging_spec.rb +162 -0
  117. data/spec/unit/synchronization_spec.rb +150 -0
  118. data/spec/unit/target_validator_spec.rb +88 -0
  119. data/spec/unit/user_manager_spec.rb +165 -0
  120. metadata +320 -0
@@ -0,0 +1,106 @@
1
+ require 'spec_helper'
2
+
3
+ module WebsocketRails
4
+ module DataStore
5
+ describe Base do
6
+ it "extends Hash" do
7
+ subject.should be_a Hash
8
+ end
9
+
10
+ it "allows indifferent access" do
11
+ subject['key'] = true
12
+ subject[:key].should == true
13
+ end
14
+
15
+ describe "#instances" do
16
+ before do
17
+ Base.clear_all_instances
18
+ @connection = double('connection')
19
+ @controller = double('controller')
20
+ end
21
+
22
+ it "keeps track of all instantiated instances" do
23
+ store_one = Base.new
24
+ store_two = Base.new
25
+
26
+ store_one.instances.count.should == 2
27
+ store_two.instances.count.should == 2
28
+ end
29
+
30
+ it "separates instances based on class name" do
31
+ 2.times { Connection.new(@connection) }
32
+ 4.times { Controller.new(@controller) }
33
+
34
+ Connection.new(@connection).instances.count.should == 3
35
+ Controller.new(@controller).instances.count.should == 5
36
+ end
37
+ end
38
+
39
+ describe "#destroy!" do
40
+ before do
41
+ Base.clear_all_instances
42
+ @store = Base.new
43
+ @other = Base.new
44
+ end
45
+
46
+ it "removes itself from the instances collection" do
47
+ @other.instances.count.should == 2
48
+ @store.destroy!
49
+ @other.instances.count.should == 1
50
+ end
51
+ end
52
+
53
+ describe "#collect_all" do
54
+ before do
55
+ Base.clear_all_instances
56
+ @store_one = Base.new
57
+ @store_two = Base.new
58
+
59
+ @store_one[:secret] = 'token_one'
60
+ @store_two[:secret] = 'token_two'
61
+ end
62
+
63
+ context "called without a block" do
64
+ it "returns an array of values for the specified key from all store instances" do
65
+ secrets = @store_one.collect_all(:secret)
66
+ secrets.should == ['token_one', 'token_two']
67
+ end
68
+ end
69
+
70
+ context "called with a block" do
71
+ it "yields each value to the block" do
72
+ @store_one.collect_all(:secret) do |item|
73
+ item.should be_in ['token_one', 'token_two']
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ describe Connection do
81
+ before do
82
+ @connection = double('connection')
83
+ @connection.stub(:client_id).and_return(1)
84
+ end
85
+
86
+ let(:subject) { DataStore::Connection.new(@connection) }
87
+
88
+ it "stores a reference to it's connection" do
89
+ subject.connection.should == @connection
90
+ end
91
+ end
92
+
93
+ describe Controller do
94
+ before do
95
+ @controller = double('controller')
96
+ end
97
+
98
+ let(:subject) { DataStore::Controller.new(@controller) }
99
+
100
+ it "stores a reference to it's controller" do
101
+ subject.controller.should == @controller
102
+ end
103
+ end
104
+ end
105
+
106
+ end
@@ -0,0 +1,203 @@
1
+ require 'spec_helper'
2
+ require 'support/mock_web_socket'
3
+
4
+ def rename_module_const(mod, old_name, new_name)
5
+ if mod.const_defined? old_name
6
+ mod.const_set(new_name, mod.const_get(old_name))
7
+ mod.send(:remove_const, old_name)
8
+ end
9
+ end
10
+
11
+ def swizzle_module_const(mod, name, temp_name, &block)
12
+ rename_module_const(mod, name, temp_name)
13
+ yield block
14
+ rename_module_const(mod, temp_name, name)
15
+ end
16
+
17
+ def set_temp_module_const(mod, name, value, &block)
18
+ mod.const_set(name, value)
19
+ yield block
20
+ mod.send(:remove_const, name)
21
+ end
22
+
23
+ module WebsocketRails
24
+
25
+ class EventTarget
26
+ attr_reader :_event, :_dispatcher, :test_method, :catch_all_method
27
+ end
28
+
29
+ describe Dispatcher do
30
+
31
+ let(:event) { double('Event').as_null_object }
32
+ let(:connection) { MockWebSocket.new }
33
+ let(:connection_manager) { double('connection_manager').as_null_object }
34
+ subject { Dispatcher.new(connection_manager) }
35
+
36
+ describe "#receive_encoded" do
37
+ context "receiving a new message" do
38
+ before do
39
+ Event.stub(:new_from_json).and_return( event )
40
+ end
41
+
42
+ it "should be decoded from JSON and dispatched" do
43
+ subject.stub(:dispatch) do |dispatch_event|
44
+ dispatch_event.should == event
45
+ end
46
+ subject.receive_encoded(encoded_message,connection)
47
+ end
48
+ end
49
+ end
50
+
51
+ describe "#receive" do
52
+ before { Event.stub(:new).and_return( event ) }
53
+
54
+ it "should dispatch a new event" do
55
+ subject.stub(:dispatch) do |dispatch_event|
56
+ dispatch_event.should == event
57
+ end
58
+ subject.receive(:test_event,{},connection)
59
+ end
60
+ end
61
+
62
+ context "dispatching an event" do
63
+ before do
64
+ EventMap.any_instance.stub(:routes_for).with(any_args).and_yield(EventTarget, :test_method)
65
+ event.stub(:name).and_return(:test_method)
66
+ event.stub(:data).and_return(:some_message)
67
+ event.stub(:connection).and_return(connection)
68
+ event.stub(:is_channel?).and_return(false)
69
+ event.stub(:is_invalid?).and_return(false)
70
+ event.stub(:is_internal?).and_return(false)
71
+ end
72
+
73
+ it "should execute the correct method on the target class" do
74
+ EventTarget.any_instance.should_receive(:process_action).with(:test_method, event)
75
+ subject.dispatch(event)
76
+ end
77
+
78
+ context "channel events" do
79
+ before do
80
+ subject.stub(:filtered_channels).and_return({})
81
+ end
82
+ it "should forward the data to the correct channel" do
83
+ event = Event.new 'test', :data => 'data', :channel => :awesome_channel
84
+ channel = double('channel')
85
+ channel.should_receive(:trigger_event).with(event)
86
+ WebsocketRails.should_receive(:[]).with(:awesome_channel).and_return(channel)
87
+ subject.dispatch event
88
+ end
89
+ end
90
+
91
+ context "filtered channel events" do
92
+ before do
93
+ subject.stub(:filtered_channels).and_return({:awesome_channel => EventTarget})
94
+ end
95
+ it "should execute the correct method on the target class" do
96
+ event = Event.new 'test_method', :data => 'some data', :channel => :awesome_channel
97
+ EventTarget.any_instance.should_receive(:process_action).with(:test_method, event)
98
+ subject.dispatch(event)
99
+ end
100
+ end
101
+
102
+ context "filtered channel catch all events" do
103
+ before do
104
+ subject.stub(:filtered_channels).and_return({:awesome_channel => [EventTarget, :catch_all_method]})
105
+ end
106
+
107
+ it "should execute the correct method on the target class" do
108
+ event = Event.new 'test_method', :data => 'some data', :channel => :awesome_channel
109
+ EventTarget.any_instance.should_receive(:process_action).with(:test_method, event)
110
+ EventTarget.any_instance.should_receive(:process_action).with(:catch_all_method, event)
111
+ subject.dispatch(event)
112
+ end
113
+ end
114
+
115
+ context "invalid events" do
116
+ before do
117
+ event.stub(:is_invalid?).and_return(true)
118
+ end
119
+
120
+ it "should not dispatch the event" do
121
+ subject.should_not_receive(:route)
122
+ subject.dispatch(event)
123
+ end
124
+ end
125
+ end
126
+
127
+ describe "#send_message" do
128
+ before do
129
+ @event = Event.new_from_json( encoded_message, connection )
130
+ end
131
+
132
+ it "should send a message to the event's connection object" do
133
+ connection.should_receive(:trigger).with(@event)
134
+ subject.send_message @event
135
+ end
136
+ end
137
+
138
+ describe "#broadcast_message" do
139
+ before do
140
+ connection_manager.stub(:connections).and_return({"connection_id" => connection})
141
+ @event = Event.new_from_json( encoded_message, connection )
142
+ end
143
+
144
+ it "should send a message to all connected clients" do
145
+ connection.should_receive(:trigger).with(@event)
146
+ subject.broadcast_message @event
147
+ end
148
+ end
149
+
150
+ describe 'record_invalid_defined?' do
151
+
152
+ it 'should return false when RecordInvalid is not defined' do
153
+ if Object.const_defined?('ActiveRecord')
154
+ swizzle_module_const(ActiveRecord, 'RecordInvalid','TempRecordInvalid') do
155
+ subject.send(:record_invalid_defined?).should be_false
156
+ end
157
+ else
158
+ set_temp_module_const(Object, 'ActiveRecord', Module.new) do
159
+ subject.send(:record_invalid_defined?).should be_false
160
+ end
161
+ end
162
+ end
163
+
164
+ it 'should return false when ActiveRecord is not defined' do
165
+ swizzle_module_const(Object, 'ActiveRecord', 'TempActiveRecord') do
166
+ subject.send(:record_invalid_defined?).should be_false
167
+ end
168
+ end
169
+
170
+ it 'should return true if ActiveRecord::RecordInvalid is defined' do
171
+ if Object.const_defined?('ActiveRecord')
172
+ if ActiveRecord.const_defined?('RecordInvalid')
173
+ subject.send(:record_invalid_defined?).should be_true
174
+ else
175
+ set_temp_module_const(ActiveRecord, 'RecordInvalid', Class.new) do
176
+ subject.send(:record_invalid_defined?).should be_true
177
+ end
178
+ end
179
+ else
180
+ set_temp_module_const(Object, 'ActiveRecord', Module.new) do
181
+ set_temp_module_const(ActiveRecord, 'RecordInvalid', Class.new) do
182
+ subject.send(:record_invalid_defined?).should be_true
183
+ end
184
+ end
185
+ end
186
+
187
+ end
188
+
189
+ context 'when ActiveRecord::RecordInvalid is not defined' do
190
+
191
+ it 'should check that exception can be converted to JSON' do
192
+ subject.should_receive(:record_invalid_defined?).and_return false
193
+ ex = double(:exception)
194
+ ex.should_receive(:respond_to?).with(:to_json).and_return true
195
+ exception_data = subject.send(:extract_exception_data, ex)
196
+ exception_data.should == ex
197
+ end
198
+
199
+ end
200
+
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,120 @@
1
+ require 'spec_helper'
2
+
3
+ # this classes are outside the WebsocketRails namespace to better reflect the actual
4
+ # situation in a normal usage
5
+
6
+ class ProductController < WebsocketRails::BaseController
7
+ def update_product
8
+ true
9
+ end
10
+ end
11
+
12
+ # name is chosen so that we know camelize is working correctly
13
+ class ComplexProductController < WebsocketRails::BaseController
14
+
15
+ def simplify_product
16
+ true
17
+ end
18
+
19
+ end
20
+
21
+
22
+ module WebsocketRails
23
+ describe EventMap do
24
+
25
+
26
+ def define_test_events
27
+ WebsocketRails.config.route_block = nil
28
+ WebsocketRails::EventMap.describe do
29
+ subscribe :client_connected, :to => ChatController, :with_method => :new_user
30
+
31
+ namespace :product do
32
+ subscribe :update, :to => ProductController, :with_method => :update_product
33
+ end
34
+
35
+ namespace :complex_product do
36
+ subscribe :simplify, 'complex_product#simplify'
37
+ end
38
+
39
+ end
40
+ end
41
+
42
+ let(:dispatcher) { double('dispatcher').as_null_object }
43
+ subject { EventMap.new(dispatcher) }
44
+ before { define_test_events }
45
+
46
+ context "EventMap.describe" do
47
+ it "should store the event route block in the global configuration" do
48
+ WebsocketRails.config.route_block.should be_present
49
+ end
50
+ end
51
+
52
+ context "Events in the global namespace" do
53
+
54
+ it "should store the event in the correct namespace" do
55
+ subject.namespace.actions[:client_connected].should be_present
56
+ subject.namespace.name.should == :global
57
+ end
58
+
59
+ it "should store the class constant and method name in the events hash" do
60
+ subject.namespace.actions[:client_connected].should == [[ChatController,:new_user]]
61
+ end
62
+
63
+ end
64
+
65
+ context "Events in a child namespace" do
66
+
67
+ before { @namespace = subject.namespace }
68
+
69
+ it "should store the event in the correct namespaces" do
70
+ @namespace.namespaces[:product].actions[:update].should be_present
71
+ @namespace.namespaces[:complex_product].actions[:simplify].should be_present
72
+ end
73
+
74
+ end
75
+
76
+ context "#routes_for" do
77
+ context "with events in the global namespace" do
78
+ it "should yield the controller class and action name for each route defined for an event" do
79
+ event = HelperMethods::MockEvent.new(:client_connected, [:global])
80
+
81
+ subject.routes_for(event) do |klass, method|
82
+ klass.should == ChatController
83
+ method.should == :new_user
84
+ end
85
+ end
86
+ end
87
+
88
+ context "with events in a child namespace" do
89
+ it "should yield the controller and action name for each route defined with a hash for an event" do
90
+ ProductController.any_instance.should_receive(:action_executed)
91
+ event = HelperMethods::MockEvent.new :update, [:global,:product]
92
+
93
+ subject.routes_for(event) do |klass, method|
94
+ controller = klass.new
95
+ controller.action_executed
96
+ controller.class.should == ProductController
97
+ method.should == :update_product
98
+ end
99
+
100
+ end
101
+
102
+ it "should yield the controller and action name for each route defined with a string for an event" do
103
+ ComplexProductController.any_instance.should_receive(:action_executed)
104
+ event = HelperMethods::MockEvent.new :simplify, [:global,:complex_product]
105
+
106
+ subject.routes_for(event) do |klass, method|
107
+ controller = klass.new
108
+ controller.action_executed
109
+ controller.class.should == ComplexProductController
110
+ method.should == :simplify
111
+ end
112
+
113
+ end
114
+
115
+ end
116
+
117
+ end
118
+
119
+ end
120
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ module WebsocketRails
4
+ describe EventQueue do
5
+
6
+ describe "#initialize" do
7
+ it "should create an empty queue" do
8
+ subject.queue.should == []
9
+ end
10
+ end
11
+
12
+ describe "#<<" do
13
+ it "should add the item to the queue" do
14
+ subject << 'event'
15
+ subject.queue.should == ['event']
16
+ end
17
+ end
18
+
19
+ describe "#flush" do
20
+ before do
21
+ subject.queue << 'event'
22
+ end
23
+
24
+ it "should yield all items in the queue" do
25
+ subject.flush do |event|
26
+ event.should == 'event'
27
+ end
28
+ end
29
+
30
+ it "should empty the queue" do
31
+ subject.flush
32
+ subject.queue.should == []
33
+ end
34
+ end
35
+ end
36
+ end