action_subscriber 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +5 -0
  5. data/LICENSE +20 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +122 -0
  8. data/Rakefile +8 -0
  9. data/action_subscriber.gemspec +38 -0
  10. data/examples/at_least_once.rb +17 -0
  11. data/examples/at_most_once.rb +15 -0
  12. data/examples/basic_subscriber.rb +30 -0
  13. data/examples/message_acknowledgement.rb +19 -0
  14. data/lib/action_subscriber.rb +93 -0
  15. data/lib/action_subscriber/base.rb +83 -0
  16. data/lib/action_subscriber/bunny/subscriber.rb +57 -0
  17. data/lib/action_subscriber/configuration.rb +68 -0
  18. data/lib/action_subscriber/default_routing.rb +26 -0
  19. data/lib/action_subscriber/dsl.rb +83 -0
  20. data/lib/action_subscriber/march_hare/subscriber.rb +60 -0
  21. data/lib/action_subscriber/middleware.rb +18 -0
  22. data/lib/action_subscriber/middleware/active_record/connection_management.rb +17 -0
  23. data/lib/action_subscriber/middleware/active_record/query_cache.rb +29 -0
  24. data/lib/action_subscriber/middleware/decoder.rb +33 -0
  25. data/lib/action_subscriber/middleware/env.rb +65 -0
  26. data/lib/action_subscriber/middleware/error_handler.rb +16 -0
  27. data/lib/action_subscriber/middleware/router.rb +17 -0
  28. data/lib/action_subscriber/middleware/runner.rb +16 -0
  29. data/lib/action_subscriber/rabbit_connection.rb +40 -0
  30. data/lib/action_subscriber/railtie.rb +13 -0
  31. data/lib/action_subscriber/rspec.rb +91 -0
  32. data/lib/action_subscriber/subscribable.rb +118 -0
  33. data/lib/action_subscriber/threadpool.rb +29 -0
  34. data/lib/action_subscriber/version.rb +3 -0
  35. data/spec/integration/basic_subscriber_spec.rb +42 -0
  36. data/spec/lib/action_subscriber/base_spec.rb +18 -0
  37. data/spec/lib/action_subscriber/configuration_spec.rb +32 -0
  38. data/spec/lib/action_subscriber/dsl_spec.rb +143 -0
  39. data/spec/lib/action_subscriber/middleware/active_record/connection_management_spec.rb +17 -0
  40. data/spec/lib/action_subscriber/middleware/active_record/query_cache_spec.rb +49 -0
  41. data/spec/lib/action_subscriber/middleware/decoder_spec.rb +31 -0
  42. data/spec/lib/action_subscriber/middleware/env_spec.rb +60 -0
  43. data/spec/lib/action_subscriber/middleware/error_handler_spec.rb +35 -0
  44. data/spec/lib/action_subscriber/middleware/router_spec.rb +24 -0
  45. data/spec/lib/action_subscriber/middleware/runner_spec.rb +6 -0
  46. data/spec/lib/action_subscriber/subscribable_spec.rb +128 -0
  47. data/spec/lib/action_subscriber/threadpool_spec.rb +35 -0
  48. data/spec/spec_helper.rb +26 -0
  49. data/spec/support/user_subscriber.rb +6 -0
  50. metadata +257 -0
@@ -0,0 +1,143 @@
1
+ require 'spec_helper'
2
+
3
+ describe ::ActionSubscriber::DSL do
4
+ let(:subscriber) { Object.new }
5
+ before { subscriber.extend(::ActionSubscriber::DSL) }
6
+
7
+ describe "acknowledging messages" do
8
+ context "when manual_acknowledgement! is set" do
9
+ before { subscriber.manual_acknowledgement! }
10
+
11
+ it "acknowledges messages" do
12
+ expect(subscriber.acknowledge_messages?).to eq(true)
13
+ end
14
+
15
+ it "returns expected queue subscription options" do
16
+ expect(subscriber.queue_subscription_options).to eq( :manual_ack => true )
17
+ end
18
+
19
+ it "does not acknowledge messages after processing them" do
20
+ expect(subscriber.acknowledge_messages_after_processing?).to eq(false)
21
+ end
22
+
23
+ it "does not acknowledge messages before processing them" do
24
+ expect(subscriber.acknowledge_messages_before_processing?).to eq(false)
25
+ end
26
+ end
27
+
28
+ context "when at_most_once! is set" do
29
+ before { subscriber.at_most_once! }
30
+
31
+ it "acknowledges messages" do
32
+ expect(subscriber.acknowledge_messages?).to eq(true)
33
+ end
34
+
35
+ it "acknowledges messages before processing them" do
36
+ expect(subscriber.acknowledge_messages_before_processing?).to eq(true)
37
+ end
38
+
39
+ it "does not acknowledge messages after processing them" do
40
+ expect(subscriber.acknowledge_messages_after_processing?).to eq(false)
41
+ end
42
+ end
43
+
44
+ context "when at_least_once! is set" do
45
+ before { subscriber.at_least_once! }
46
+
47
+ it "acknowledges messages" do
48
+ expect(subscriber.acknowledge_messages?).to eq(true)
49
+ end
50
+
51
+ it "does not acknowledge messages before processing them" do
52
+ expect(subscriber.acknowledge_messages_before_processing?).to eq(false)
53
+ end
54
+
55
+ it "acknowledges messages after processing them" do
56
+ expect(subscriber.acknowledge_messages_after_processing?).to eq(true)
57
+ end
58
+ end
59
+
60
+ context "when no_acknowledgement! is set" do
61
+ before { subscriber.no_acknowledgement! }
62
+
63
+ it "does not acknowledge messages" do
64
+ expect(subscriber.acknowledge_messages?).to eq(false)
65
+ end
66
+
67
+ it "does not acknowledge messages after processing them" do
68
+ expect(subscriber.acknowledge_messages_after_processing?).to eq(false)
69
+ end
70
+
71
+ it "does not acknowledge messages before processing them" do
72
+ expect(subscriber.acknowledge_messages_before_processing?).to eq(false)
73
+ end
74
+ end
75
+
76
+ context "default" do
77
+ it "does not acknowledge messages" do
78
+ expect(subscriber.acknowledge_messages?).to eq(false)
79
+ end
80
+
81
+ it "does not acknowledge messages after processing them" do
82
+ expect(subscriber.acknowledge_messages_after_processing?).to eq(false)
83
+ end
84
+
85
+ it "does not acknowledge messages before processing them" do
86
+ expect(subscriber.acknowledge_messages_before_processing?).to eq(false)
87
+ end
88
+ end
89
+ end
90
+
91
+ describe "exchange_names" do
92
+ context "when exchange names are set" do
93
+ before { subscriber.exchange_names :foo, :bar }
94
+
95
+ it "returns an array of exchange names" do
96
+ expect(subscriber.exchange_names).to eq(["foo", "bar"])
97
+ end
98
+ end
99
+
100
+ context "when exchange names are not set" do
101
+ before { subscriber.instance_variable_set(:@_exchange_names, nil) }
102
+
103
+ it "returns the default exchange" do
104
+ expect(subscriber.exchange_names).to eq(["events"])
105
+ end
106
+ end
107
+ end
108
+
109
+ describe "queue_for" do
110
+ before { subscriber.queue_for(:created, "my_app.app.user.created") }
111
+
112
+ it "adds the method and queue name to the queue names collection" do
113
+ expect(subscriber.queue_names).to eq({:created => "my_app.app.user.created" })
114
+ end
115
+ end
116
+
117
+ describe "remote_application_name" do
118
+ context "when remote appliation name is set" do
119
+ before { subscriber.remote_application_name "app" }
120
+
121
+ it "returns the remote application name" do
122
+ expect(subscriber.remote_application_name).to eq("app")
123
+ end
124
+ end
125
+
126
+ context "when remote application name is not set" do
127
+ before { subscriber.instance_variable_set(:@_remote_application_name, nil) }
128
+
129
+ it "returns nil" do
130
+ expect(subscriber.remote_application_name).to be_nil
131
+ end
132
+ end
133
+ end
134
+
135
+ describe "routing_key_for" do
136
+ before { subscriber.routing_key_for(:created, "app.user.created") }
137
+
138
+ it "adds the method name to the routing key names collection" do
139
+ expect(subscriber.routing_key_names).to eq({:created => "app.user.created"})
140
+ end
141
+ end
142
+
143
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+ require 'action_subscriber/middleware/active_record/connection_management'
3
+
4
+ describe ActionSubscriber::Middleware::ActiveRecord::ConnectionManagement do
5
+ include_context 'action subscriber middleware env'
6
+
7
+ before { allow(ActiveRecord::Base).to receive(:clear_active_connections!) }
8
+
9
+ subject { described_class.new(app) }
10
+
11
+ it_behaves_like 'an action subscriber middleware'
12
+
13
+ it "clears active connections" do
14
+ expect(ActiveRecord::Base).to receive(:clear_active_connections!)
15
+ subject.call(env)
16
+ end
17
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+ require 'action_subscriber/middleware/active_record/query_cache'
3
+
4
+ describe ActionSubscriber::Middleware::ActiveRecord::QueryCache do
5
+ include_context 'action subscriber middleware env'
6
+
7
+ let(:connection) { double(:connection, :query_cache_enabled => true) }
8
+
9
+ before do
10
+ allow(connection).to receive(:clear_query_cache)
11
+ allow(connection).to receive(:disable_query_cache!)
12
+ allow(connection).to receive(:enable_query_cache!)
13
+
14
+ allow(ActiveRecord::Base).to receive(:connection).and_return(connection)
15
+ allow(ActiveRecord::Base).to receive(:connection_id)
16
+ end
17
+
18
+ subject { described_class.new(app) }
19
+
20
+ it_behaves_like 'an action subscriber middleware'
21
+
22
+ it "enables the query cache" do
23
+ expect(connection).to receive(:enable_query_cache!)
24
+ subject.call(env)
25
+ end
26
+
27
+ it "clears the query cache" do
28
+ expect(connection).to receive(:clear_query_cache)
29
+ subject.call(env)
30
+ end
31
+
32
+ context "when the query cache is already enabled" do
33
+ before { allow(connection).to receive(:query_cache_enabled).and_return(true) }
34
+
35
+ it "does not disable the query cache" do
36
+ expect(connection).to_not receive(:disable_query_cache!)
37
+ subject.call(env)
38
+ end
39
+ end
40
+
41
+ context "when the query cache is not already enabled" do
42
+ before { allow(connection).to receive(:query_cache_enabled).and_return(false) }
43
+
44
+ it "does disable the query cache" do
45
+ expect(connection).to receive(:disable_query_cache!)
46
+ subject.call(env)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActionSubscriber::Middleware::Decoder do
4
+ include_context 'action subscriber middleware env'
5
+
6
+ subject { described_class.new(app) }
7
+
8
+ it_behaves_like 'an action subscriber middleware'
9
+
10
+ let(:env) { ActionSubscriber::Middleware::Env.new(UserSubscriber, encoded_payload, message_properties) }
11
+ let(:encoded_payload) { JSON.generate(payload) }
12
+ let(:payload) { {"ohai" => "GUYZ"} }
13
+
14
+ context "when the content type has an associated decoder" do
15
+ before { message_properties[:content_type] = "application/json"}
16
+
17
+ it "decodes the payload" do
18
+ subject.call(env)
19
+ expect(env.payload).to eq(payload)
20
+ end
21
+ end
22
+
23
+ context "when the content type does not have an associated decoder" do
24
+ before { message_properties[:content_type] = "application/foo"}
25
+
26
+ it "uses the payload as-is" do
27
+ subject.call(env)
28
+ expect(env.payload).to eq(encoded_payload)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActionSubscriber::Middleware::Env do
4
+ let(:channel) { double("channel") }
5
+ let(:encoded_payload) { 'encoded_payload' }
6
+ let(:properties){ {
7
+ :channel => channel,
8
+ :content_type => "application/json",
9
+ :delivery_tag => "XYZ",
10
+ :encoded_payload => encoded_payload,
11
+ :exchange => "events",
12
+ :message_id => "MSG-1234",
13
+ :routing_key => "amigo.user.created",
14
+ } }
15
+ let(:subscriber) { UserSubscriber }
16
+
17
+ subject { described_class.new(subscriber, encoded_payload, properties) }
18
+
19
+ specify { expect(subject.action).to eq("created") }
20
+ specify { expect(subject.content_type).to eq(properties[:content_type]) }
21
+ specify { expect(subject.exchange).to eq(properties[:exchange]) }
22
+ specify { expect(subject.message_id).to eq(properties[:message_id]) }
23
+ specify { expect(subject.routing_key).to eq(properties[:routing_key]) }
24
+
25
+ describe "#acknowledge" do
26
+ it "sends an acknowledgement to rabbitmq" do
27
+ expect(channel).to receive(:ack).with(properties[:delivery_tag], false)
28
+ subject.acknowledge
29
+ end
30
+ end
31
+
32
+ describe "#reject" do
33
+ it "sends an rejection to rabbitmq" do
34
+ expect(channel).to receive(:reject).with(properties[:delivery_tag], true)
35
+ subject.reject
36
+ end
37
+ end
38
+
39
+ describe "#to_hash" do
40
+ it "includes the action" do
41
+ expect(subject.to_hash).to have_key(:action)
42
+ end
43
+
44
+ it "includes the content_type" do
45
+ expect(subject.to_hash).to have_key(:content_type)
46
+ end
47
+
48
+ it "includes the exchange" do
49
+ expect(subject.to_hash).to have_key(:exchange)
50
+ end
51
+
52
+ it "includes the routing_key" do
53
+ expect(subject.to_hash).to have_key(:routing_key)
54
+ end
55
+
56
+ it "includes the payload" do
57
+ expect(subject.to_hash).to have_key(:payload)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+ require 'action_subscriber/middleware/error_handler'
3
+
4
+ describe ActionSubscriber::Middleware::ErrorHandler do
5
+ include_context 'action subscriber middleware env'
6
+
7
+ subject { described_class.new(app) }
8
+
9
+ it_behaves_like 'an action subscriber middleware'
10
+
11
+ let(:error) { ::RuntimeError.new("Boom!") }
12
+
13
+ context "when an exception occurs" do
14
+ before { allow(app).to receive(:call).and_raise(error) }
15
+
16
+ it "calls the exception handler" do
17
+ handler = ::ActionSubscriber.configuration.error_handler
18
+ expect(handler).to receive(:call).with(error, env.to_h)
19
+
20
+ subject.call(env)
21
+ end
22
+
23
+ context "when the subscriber was expecting to acknowledge the message" do
24
+ before { allow(env.subscriber).to receive(:acknowledge_messages_after_processing?).and_return(true) }
25
+
26
+ it "calls the exception handler and rejects the message" do
27
+ handler = ::ActionSubscriber.configuration.error_handler
28
+ expect(handler).to receive(:call).with(error, env.to_h)
29
+ expect(env).to receive(:reject).with(no_args)
30
+
31
+ subject.call(env)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActionSubscriber::Middleware::Router do
4
+ include_context 'action subscriber middleware env'
5
+
6
+ subject { described_class.new(app) }
7
+
8
+ it "routes the event to the proper action" do
9
+ allow_any_instance_of(env.subscriber).to receive(env.action)
10
+ subject.call(env)
11
+ end
12
+
13
+ it "acknowledges messages after processing if the subscriber flag is set" do
14
+ allow(env.subscriber).to receive(:acknowledge_messages_after_processing?).and_return(true)
15
+ expect(env).to receive(:acknowledge)
16
+ subject.call(env)
17
+ end
18
+
19
+ it "acknowledges messages before processing if the subscriber flag is set" do
20
+ allow(env.subscriber).to receive(:acknowledge_messages_before_processing?).and_return(true)
21
+ expect(env).to receive(:acknowledge)
22
+ subject.call(env)
23
+ end
24
+ end
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActionSubscriber::Middleware::Runner do
4
+ # TODO: Figure out at way to test this...
5
+ it "adds the router to the top of the stack"
6
+ end
@@ -0,0 +1,128 @@
1
+ require 'spec_helper'
2
+
3
+ TestSubscriber = Class.new(ActionSubscriber::Base) do
4
+ def updated
5
+ end
6
+
7
+ def updated_low
8
+ end
9
+ end
10
+ TestSubscriber.remote_application_name :bob
11
+
12
+ describe ActionSubscriber::Subscribable do
13
+ describe "allow_low_priority_methods?" do
14
+ after do
15
+ ::ActionSubscriber.configure { |config| config.allow_low_priority_methods = false }
16
+ end
17
+
18
+ it "when the configuration is false is is false" do
19
+ ::ActionSubscriber.configure { |config| config.allow_low_priority_methods = false }
20
+ expect(TestSubscriber.allow_low_priority_methods?).to eq(false)
21
+ end
22
+
23
+ it "when the configuration is true it is true" do
24
+ ::ActionSubscriber.configure { |config| config.allow_low_priority_methods = true }
25
+ expect(TestSubscriber.allow_low_priority_methods?).to eq(true)
26
+ end
27
+ end
28
+
29
+ describe "filter_low_priority_methods" do
30
+ context "when allow_low_priority_methods? is false" do
31
+ before { allow(TestSubscriber).to receive(:allow_low_priority_methods?).and_return(false) }
32
+
33
+ it "removes low priority methods" do
34
+ filtered_methods = TestSubscriber.filter_low_priority_methods([:updated, :updated_low])
35
+ expect(filtered_methods).to eq([:updated])
36
+ end
37
+ end
38
+
39
+ context "when allow_low_priority_methods? is true" do
40
+ before { allow(TestSubscriber).to receive(:allow_low_priority_methods?).and_return(true) }
41
+
42
+ it "allows low priority methods" do
43
+ filtered_methods = TestSubscriber.filter_low_priority_methods([:updated, :updated_low])
44
+ expect(filtered_methods).to eq([:updated, :updated_low])
45
+ end
46
+ end
47
+ end
48
+
49
+ describe "generate_queue_name" do
50
+ it "returns a queue name" do
51
+ queue_name = TestSubscriber.generate_queue_name(:created)
52
+ expect(queue_name).to eq("alice.bob.test.created")
53
+ end
54
+ end
55
+
56
+ describe "generate_routing_key_name" do
57
+ it "returns a routing key name" do
58
+ routing_key_name = TestSubscriber.generate_routing_key_name(:created)
59
+ expect(routing_key_name).to eq("bob.test.created")
60
+ end
61
+ end
62
+
63
+ describe "local_application_name" do
64
+ it "returns the local application name" do
65
+ expect(TestSubscriber.local_application_name).to eq("alice")
66
+ end
67
+ end
68
+
69
+ describe "queue_name_for_method" do
70
+ before { TestSubscriber.instance_variable_set(:@_queue_names, nil) }
71
+
72
+ context "when the queue is already registered" do
73
+ it "returns the registered queue" do
74
+ TestSubscriber.queue_for(:created, "foo.bar")
75
+ queue_name = TestSubscriber.queue_name_for_method(:created)
76
+ expect(queue_name).to eq("foo.bar")
77
+ end
78
+ end
79
+
80
+ context "when the queue is not registered" do
81
+ it "generates a queue name" do
82
+ queue_name = TestSubscriber.queue_name_for_method(:created)
83
+ expect(queue_name).to eq("alice.bob.test.created")
84
+ end
85
+
86
+ it "registers the generated queue" do
87
+ queue_name = TestSubscriber.queue_name_for_method(:created)
88
+ expect(TestSubscriber.queue_names).to eq({:created =>"alice.bob.test.created"})
89
+ end
90
+ end
91
+ end
92
+
93
+ describe "resource_name" do
94
+ it "returns the resource name" do
95
+ expect(TestSubscriber.resource_name).to eq("test")
96
+ end
97
+ end
98
+
99
+ describe "routing_key_name_for_method" do
100
+ before { TestSubscriber.instance_variable_set(:@_routing_key_names, nil) }
101
+
102
+ context "when the routing key is already registered" do
103
+ it "returns the registered routing key" do
104
+ TestSubscriber.routing_key_for(:created, "bar.foo")
105
+ routing_key_name = TestSubscriber.routing_key_name_for_method(:created)
106
+ expect(routing_key_name).to eq("bar.foo")
107
+ end
108
+ end
109
+
110
+ context "when the routing key is not registered" do
111
+ it "generates a routing key name" do
112
+ routing_key_name = TestSubscriber.routing_key_name_for_method(:created)
113
+ expect(routing_key_name).to eq("bob.test.created")
114
+ end
115
+
116
+ it "registers the generated routing key" do
117
+ routing_key_name = TestSubscriber.routing_key_name_for_method(:created)
118
+ expect(TestSubscriber.routing_key_names).to eq({:created => "bob.test.created"})
119
+ end
120
+ end
121
+ end
122
+
123
+ describe "subscribable methods" do
124
+ it "returns the subscribable methods" do
125
+ expect(TestSubscriber.subscribable_methods).to eq([:updated])
126
+ end
127
+ end
128
+ end