hkroger-websocket-rails 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
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