websocket-rails 0.3.0 → 0.4.0

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 (38) hide show
  1. data/CHANGELOG.md +41 -0
  2. data/README.md +1 -1
  3. data/lib/generators/websocket_rails/install/templates/events.rb +15 -5
  4. data/lib/rails/tasks/websocket_rails.tasks +8 -10
  5. data/lib/spec_helpers/matchers/route_matchers.rb +2 -1
  6. data/lib/spec_helpers/spec_helper_event.rb +4 -0
  7. data/lib/websocket-rails.rb +50 -73
  8. data/lib/websocket_rails/base_controller.rb +9 -7
  9. data/lib/websocket_rails/channel.rb +8 -1
  10. data/lib/websocket_rails/channel_manager.rb +6 -0
  11. data/lib/websocket_rails/configuration.rb +112 -0
  12. data/lib/websocket_rails/connection_adapters.rb +13 -4
  13. data/lib/websocket_rails/connection_manager.rb +3 -2
  14. data/lib/websocket_rails/controller_factory.rb +70 -0
  15. data/lib/websocket_rails/data_store.rb +128 -62
  16. data/lib/websocket_rails/dispatcher.rb +17 -16
  17. data/lib/websocket_rails/event.rb +12 -2
  18. data/lib/websocket_rails/event_map.rb +6 -41
  19. data/lib/websocket_rails/internal_events.rb +0 -7
  20. data/lib/websocket_rails/logging.rb +103 -14
  21. data/lib/websocket_rails/synchronization.rb +6 -8
  22. data/lib/websocket_rails/version.rb +1 -1
  23. data/spec/dummy/app/controllers/chat_controller.rb +6 -14
  24. data/spec/dummy/log/test.log +0 -750
  25. data/spec/integration/connection_manager_spec.rb +8 -1
  26. data/spec/spec_helper.rb +3 -16
  27. data/spec/spec_helpers/matchers/route_matchers_spec.rb +2 -11
  28. data/spec/spec_helpers/matchers/trigger_matchers_spec.rb +2 -12
  29. data/spec/unit/channel_manager_spec.rb +8 -0
  30. data/spec/unit/channel_spec.rb +16 -2
  31. data/spec/unit/connection_adapters_spec.rb +32 -11
  32. data/spec/unit/controller_factory_spec.rb +63 -0
  33. data/spec/unit/data_store_spec.rb +91 -24
  34. data/spec/unit/dispatcher_spec.rb +6 -11
  35. data/spec/unit/event_map_spec.rb +17 -27
  36. data/spec/unit/event_spec.rb +14 -0
  37. data/spec/unit/logging_spec.rb +122 -17
  38. metadata +11 -6
@@ -9,7 +9,7 @@ module WebsocketRails
9
9
  end
10
10
 
11
11
  def define_test_events
12
- WebsocketRails.route_block = nil
12
+ WebsocketRails.config.route_block = nil
13
13
  WebsocketRails::EventMap.describe do
14
14
  subscribe :client_connected, :to => ChatController, :with_method => :new_user
15
15
  subscribe :change_username, :to => ChatController, :with_method => :change_username
@@ -99,6 +99,13 @@ module WebsocketRails
99
99
  @server.call( env )
100
100
  socket.on_close
101
101
  end
102
+
103
+ it "should unsubscribe from channels" do
104
+ channel = WebsocketRails[:test_chan]
105
+ @server.call( env )
106
+ channel.should_receive(:unsubscribe).with(socket)
107
+ socket.on_close
108
+ end
102
109
  end
103
110
  end
104
111
  end
data/spec/spec_helper.rb CHANGED
@@ -34,21 +34,8 @@ RSpec.configure do |config|
34
34
  # rspec-rails.
35
35
  config.infer_base_class_for_anonymous_controllers = false
36
36
 
37
- end
38
-
39
- def silence_output
40
- @orig_stderr = $stderr
41
- @orig_stdout = $stdout
42
-
43
- # redirect stderr and stdout to /dev/null
44
- $stderr = File.new('/dev/null', 'w')
45
- $stdout = File.new('/dev/null', 'w')
46
- end
37
+ config.before(:each) do
38
+ WebsocketRails.config.logger = Logger.new(StringIO.new)
39
+ end
47
40
 
48
- # Replace stdout and stderr so anything else is output correctly.
49
- def enable_output
50
- $stderr = @orig_stderr
51
- $stdout = @orig_stdout
52
- @orig_stderr = nil
53
- @orig_stdout = nil
54
41
  end
@@ -19,9 +19,8 @@ describe 'Route Matchers' do
19
19
 
20
20
  end
21
21
 
22
-
23
22
  def define_route_test_events
24
- WebsocketRails.route_block = nil
23
+ WebsocketRails.config.route_block = nil
25
24
  WebsocketRails::EventMap.describe do
26
25
 
27
26
  namespace :product do
@@ -106,13 +105,5 @@ describe 'Route Matchers' do
106
105
  matcher.should produce_as_description 'be routed only to target route_spec_product#update_product'
107
106
  end
108
107
 
109
-
110
108
  end
111
-
112
-
113
-
114
-
115
-
116
-
117
-
118
- end
109
+ end
@@ -20,7 +20,7 @@ describe 'Trigger Matchers' do
20
20
  end
21
21
 
22
22
  def define_test_events
23
- WebsocketRails.route_block = nil
23
+ WebsocketRails.config.route_block = nil
24
24
  WebsocketRails::EventMap.describe do
25
25
 
26
26
  namespace :product do
@@ -32,16 +32,6 @@ describe 'Trigger Matchers' do
32
32
 
33
33
  before { define_test_events }
34
34
 
35
- around(:each) do |example|
36
- EM.run do
37
- example.run
38
- end
39
- end
40
-
41
- after(:each) do
42
- EM.stop
43
- end
44
-
45
35
  # as we have have 16 possible combinations of trigger messages and data matching pattern (data|no_data, success|failure,
46
36
  # no_checking|checking_with_any|checking_with_nil|checking_with_exact_data) plus the case of no message at all
47
37
  # for EACH of the matchers, resulting in a total 51 cases, we will not extensively test all cases for all matchers
@@ -254,4 +244,4 @@ describe 'Trigger Matchers' do
254
244
  end
255
245
 
256
246
 
257
- end
247
+ end
@@ -25,5 +25,13 @@ module WebsocketRails
25
25
  end
26
26
  end
27
27
 
28
+ describe "unsubscribe" do
29
+ it "should unsubscribe connection from all channels" do
30
+ subject[:awesome_channel].should_receive(:unsubscribe).with(:some_connection)
31
+ subject[:awesome_channel]
32
+ subject.unsubscribe(:some_connection)
33
+ end
34
+ end
35
+
28
36
  end
29
37
  end
@@ -21,6 +21,19 @@ module WebsocketRails
21
21
  end
22
22
  end
23
23
 
24
+ describe "#unsubscribe" do
25
+ it "should remove connection from subscriber pool" do
26
+ subject.subscribe connection
27
+ subject.unsubscribe connection
28
+ subject.subscribers.include?(connection).should be_false
29
+ end
30
+
31
+ it "should do nothing if connection is not subscribed to channel" do
32
+ subject.unsubscribe connection
33
+ subject.subscribers.include?(connection).should be_false
34
+ end
35
+ end
36
+
24
37
  describe "#trigger" do
25
38
  it "should create a new event and trigger it on all subscribers" do
26
39
  event = double('event').as_null_object
@@ -37,8 +50,9 @@ module WebsocketRails
37
50
 
38
51
  describe "#trigger_event" do
39
52
  it "should forward the event to the subscribers" do
40
- subject.should_receive(:send_data).with('event')
41
- subject.trigger_event 'event'
53
+ event = double('event').as_null_object
54
+ subject.should_receive(:send_data).with(event)
55
+ subject.trigger_event event
42
56
  end
43
57
  end
44
58
 
@@ -1,31 +1,31 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  module WebsocketRails
4
-
4
+
5
5
  class ConnectionAdapters::Test < ConnectionAdapters::Base
6
6
  def self.accepts?(env)
7
7
  true
8
8
  end
9
9
  end
10
-
10
+
11
11
  describe ConnectionAdapters do
12
-
12
+
13
13
  context ".register" do
14
14
  it "should store a reference to the adapter in the adapters array" do
15
15
  ConnectionAdapters.register( ConnectionAdapters::Test )
16
16
  ConnectionAdapters.adapters.include?( ConnectionAdapters::Test ).should be_true
17
17
  end
18
18
  end
19
-
19
+
20
20
  context ".establish_connection" do
21
21
  it "should return the correct connection adapter instance" do
22
22
  adapter = ConnectionAdapters.establish_connection( mock_request, double('Dispatcher').as_null_object )
23
23
  adapter.class.should == ConnectionAdapters::Test
24
24
  end
25
25
  end
26
-
26
+
27
27
  end
28
-
28
+
29
29
  module ConnectionAdapters
30
30
  describe Base do
31
31
  let(:dispatcher) { double('Dispatcher').as_null_object }
@@ -33,12 +33,16 @@ module WebsocketRails
33
33
  let(:event) { double('Event').as_null_object }
34
34
  before { Event.stub(:new_from_json).and_return(event) }
35
35
  subject { Base.new( mock_request, dispatcher ) }
36
-
37
- context "new adapters" do
38
- it "should register themselves in the adapters array when inherited" do
36
+
37
+ context "new adapter" do
38
+ it "should register itself in the adapters array when inherited" do
39
39
  adapter = Class.new( ConnectionAdapters::Base )
40
40
  ConnectionAdapters.adapters.include?( adapter ).should be_true
41
41
  end
42
+
43
+ it "should create a new DataStore::Connection instance" do
44
+ subject.data_store.should be_a DataStore::Connection
45
+ end
42
46
  end
43
47
 
44
48
  describe "#on_open" do
@@ -82,13 +86,13 @@ module WebsocketRails
82
86
  subject.on_error("test_data")
83
87
  end
84
88
  end
85
-
89
+
86
90
  describe "#send" do
87
91
  it "should raise a NotImplementedError exception" do
88
92
  expect { subject.send :message }.to raise_exception( NotImplementedError )
89
93
  end
90
94
  end
91
-
95
+
92
96
  describe "#enqueue" do
93
97
  it "should add the event to the queue" do
94
98
  subject.enqueue 'event'
@@ -125,6 +129,23 @@ module WebsocketRails
125
129
  subject.flush
126
130
  end
127
131
  end
132
+
133
+ describe "#close_connection" do
134
+ before do
135
+ @connection_manager = double('connection_manager').as_null_object
136
+ subject.stub_chain(:dispatcher, :connection_manager).and_return(@connection_manager)
137
+ end
138
+
139
+ it "calls delegates to the conection manager" do
140
+ @connection_manager.should_receive(:close_connection).with(subject)
141
+ subject.__send__(:close_connection)
142
+ end
143
+
144
+ it "deletes it's data_store" do
145
+ subject.data_store.should_receive(:destroy!)
146
+ subject.__send__(:close_connection)
147
+ end
148
+ end
128
149
  end
129
150
  end
130
151
  end
@@ -0,0 +1,63 @@
1
+ require "spec_helper"
2
+
3
+ module WebsocketRails
4
+ describe ControllerFactory do
5
+
6
+ class TestController < BaseController
7
+ attr_reader :_dispatcher, :_event
8
+
9
+ def initialize_session
10
+ true
11
+ end
12
+ end
13
+
14
+ let(:dispatcher) { double('dispatcher') }
15
+ let(:connection) { double('connection') }
16
+ let(:event) { double('event') }
17
+
18
+ subject { ControllerFactory.new(dispatcher) }
19
+
20
+ before do
21
+ connection.stub(:id).and_return(1)
22
+ event.stub(:connection).and_return(connection)
23
+ end
24
+
25
+ it "stores a reference to the dispatcher" do
26
+ subject.dispatcher.should == dispatcher
27
+ end
28
+
29
+ it "maintains a hash of controller data stores" do
30
+ subject.controller_stores.should be_a Hash
31
+ end
32
+
33
+ describe "#new_for_event" do
34
+ it "creates and returns a new controller instance" do
35
+ controller = subject.new_for_event(event, TestController)
36
+ controller.class.should == TestController
37
+ end
38
+
39
+ it "initializes the controller with the correct data_store" do
40
+ store = double('data_store')
41
+ subject.controller_stores[TestController] = store
42
+ controller = subject.new_for_event(event, TestController)
43
+ controller.controller_store.should == store
44
+ end
45
+
46
+ it "initializes the controller with the correct event" do
47
+ controller = subject.new_for_event(event, TestController)
48
+ controller.event.should == event
49
+ end
50
+
51
+ it "initializes the controller with the correct dispatcher" do
52
+ controller = subject.new_for_event(event, TestController)
53
+ controller._dispatcher.should == dispatcher
54
+ end
55
+
56
+ it "calls #initialize_session on the controller only once" do
57
+ TestController.any_instance.should_receive(:initialize_session).once
58
+ 3.times { subject.new_for_event(event, TestController) }
59
+ end
60
+ end
61
+
62
+ end
63
+ end
@@ -1,39 +1,106 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  module WebsocketRails
4
- describe DataStore do
5
- let(:attribute) {"example_attribute"}
6
- let(:value) {1}
7
-
8
- before(:each) do
9
- @base = double('base_controller')
10
- @base.stub(:client_id).and_return(1)
11
- @data_store = DataStore.new(@base)
12
- end
13
-
14
- it "loads up" do
15
- @data_store.present?.should be_true
16
- end
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
17
25
 
18
- describe "#[]" do
19
- context "with an undefined attribute" do
20
- it "returns nil" do
21
- @data_store[attribute].should be_nil
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
22
36
  end
23
37
  end
24
38
 
25
- context "with a defined attribute" do
26
- it "returns its value" do
27
- @data_store[attribute] = value
28
- @data_store[attribute].should == value
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
29
76
  end
30
77
  end
31
78
  end
32
79
 
33
- describe "#[]=" do
34
- it "returns the value" do
35
- (@data_store[attribute]=value).should == value
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
36
102
  end
37
103
  end
38
104
  end
105
+
39
106
  end
@@ -4,7 +4,7 @@ require 'support/mock_web_socket'
4
4
  module WebsocketRails
5
5
 
6
6
  class EventTarget
7
- attr_reader :_event, :test_method
7
+ attr_reader :_event, :_dispatcher, :test_method
8
8
 
9
9
  def execute_observers(event_name)
10
10
  true
@@ -13,7 +13,7 @@ module WebsocketRails
13
13
 
14
14
  describe Dispatcher do
15
15
 
16
- let(:event) { double('Event') }
16
+ let(:event) { double('Event').as_null_object }
17
17
  let(:connection) { MockWebSocket.new }
18
18
  let(:connection_manager) { double('connection_manager').as_null_object }
19
19
  subject { Dispatcher.new(connection_manager) }
@@ -43,27 +43,22 @@ module WebsocketRails
43
43
  end
44
44
  end
45
45
 
46
- context "dispatching a message for an event" do
46
+ context "dispatching an event" do
47
47
  before do
48
- @target = EventTarget.new
49
- EventMap.any_instance.stub(:routes_for).with(any_args).and_yield( @target, :test_method )
48
+ EventMap.any_instance.stub(:routes_for).with(any_args).and_yield(EventTarget, :test_method)
50
49
  event.stub(:name).and_return(:test_method)
51
50
  event.stub(:data).and_return(:some_message)
52
51
  event.stub(:connection).and_return(connection)
53
52
  event.stub(:is_channel?).and_return(false)
54
53
  event.stub(:is_invalid?).and_return(false)
54
+ event.stub(:is_internal?).and_return(false)
55
55
  end
56
56
 
57
57
  it "should execute the correct method on the target class" do
58
- @target.should_receive(:test_method)
58
+ EventTarget.any_instance.should_receive(:test_method)
59
59
  subject.dispatch(event)
60
60
  end
61
61
 
62
- it "should set the _event instance variable on the target object" do
63
- subject.dispatch(event)
64
- @target._event.should == event
65
- end
66
-
67
62
  context "channel events" do
68
63
  it "should forward the data to the correct channel" do
69
64
  event = Event.new 'test', :data => 'data', :channel => :awesome_channel