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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 14ee094f6885fec532698cf8eac89dca3f79978e
4
+ data.tar.gz: 54740c601d44855e1260483ebfd73e7e042eee93
5
+ SHA512:
6
+ metadata.gz: bdd0d984f012f90c8100fa5c079dd7dee1c02362f7a5e93c4c2686758057ca4be52e53d17c820c7ddb1f39b68782048955d071811068183f7ff71a516bcf1211
7
+ data.tar.gz: 1c5543030e5b226fb9f97655454f3f644778c375a9b44a8c08742ec7795c1514d4b99adeb9498275e98c5b7891e3427cab4062a600bef1d9cd52e5f7f4de58c3
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .ruby-*
6
+ coverage
7
+ InstalledFiles
8
+ lib/bundler/man
9
+ pkg
10
+ rdoc
11
+ spec/reports
12
+ test/tmp
13
+ test/version_tmp
14
+ tmp
15
+
16
+ Gemfile.lock
17
+
18
+ # YARD artifacts
19
+ .yardoc
20
+ _yardoc
21
+ doc/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --order rand
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+ source 'http://gems.moneydesktop.com'
3
+
4
+ # Specify your gem's dependencies in action_subscriber.gemspec
5
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Brian
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Brian Stien
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,122 @@
1
+ ActionSubscriber
2
+ =================
3
+ ActionSubscriber is a DSL for for easily intergrating your Rails app with a RabbitMQ messaging server.
4
+
5
+ Requirements
6
+ -----------------
7
+ I test on Ruby 2.1.1 and Jruby 1.7.x. Ruby 1.8 is not supported.
8
+
9
+ Supported Message Types
10
+ -----------------
11
+ ActionSubscriber support JSON and plain text out of the box, but you can easily
12
+ add support for any custom message type.
13
+
14
+ Example
15
+ -----------------
16
+ A subscriber is set up by creating a class that inherits from ActionSubscriber::Base.
17
+
18
+ ```ruby
19
+ class UserSubscriber < ::ActionSubscriber::Base
20
+ publisher :user_hq
21
+
22
+ def created
23
+ # do something when a user is created
24
+ end
25
+ end
26
+ ```
27
+
28
+ checkout the examples dir for more detailed examples.
29
+
30
+ Usage
31
+ -----------------
32
+ ActionSubscriber is inspired by rails observers, and if you are familiar with rails
33
+ observers the ActionSubscriber DSL should make you feel right at home!
34
+
35
+ First, create a subscriber the inherits from ActionSubscriber::Base
36
+
37
+ Then, when your app starts up, you will need to load your subscriber code and then do
38
+
39
+ ```ruby
40
+ ActionSubscriber.start_subscribers
41
+ while true
42
+ sleep 1.0
43
+ end
44
+ ```
45
+
46
+ or
47
+
48
+ ```ruby
49
+ ::ActionSubscriber.start_queues
50
+ while true
51
+ ::ActionSubscriber.auto_pop!
52
+ sleep 1.0
53
+ end
54
+ ```
55
+
56
+ Any public methods on your subscriber will be registered as queues with rabbit with
57
+ routing keys named intelligently.
58
+
59
+ Once ActionSubscriber receives a message, it will call the associated method and the
60
+ parameter you recieve will be a decoded message.
61
+
62
+ Configuration
63
+ -----------------
64
+ ActionSubscriber needs to know how to connect to your rabbit server to start getting messages.
65
+
66
+ In an initializer, you can set the host and the port like this :
67
+
68
+ ActionSubscriber::Configuration.configure do |config|
69
+ config.host = "my rabbit host"
70
+ config.port = 5672
71
+ end
72
+
73
+ Other configuration options include :
74
+
75
+ * config.allow_low_priority_methods - subscribe to queues for methods suffixed with "_low"
76
+ * config.default_exchange - set the default exchange that your queues will use, using the default RabbitMQ exchange is not recommended
77
+ * config.hosts - an array of hostnames in your cluster
78
+ * config.times_to_pop - when using RabbitMQ's pull API, the number of messages we will grab each time we pool the broker
79
+ * config.threadpool_size - set the number of threads availiable to action_subscriber
80
+ * config.error_handler - handle error like you want to handle them!
81
+ * config.add_decoder - add a custom decoder for a custom content type
82
+
83
+ Message Acknowledgment
84
+ ----------------------
85
+ ### no_acknolwedgement!
86
+
87
+ This mode is the default. Rabbit is told to not expect any message acknowledgements so messages will be lost if an error occurs.
88
+
89
+ ### manual_acknowledgement!
90
+
91
+ This mode leaves it up to the subscriber to handle acknowledging or rejecting messages. In your subscriber you can just call <code>acknowledge</code> or <code>reject</code>.
92
+
93
+ ### at_most_once!
94
+
95
+ Rabbit is told to expect message acknowledgements, but sending the acknowledgement is left up to ActionSubscriber. We send the acknowledgement right before calling your subscriber.
96
+
97
+ ### at_least_once!
98
+
99
+ Rabbit is told to expect message acknowledgements, but sending the acknowledgement is left up to ActionSubscriber. We send the acknowledgement right after calling your subscriber. If an error is raised by your subscriber we reject the message instead of acknowledging it. Rejected messages go back to rabbit and will be re-delivered.
100
+
101
+ Testing
102
+ -----------------
103
+ ActionSubscriber includes support for easy unit testing with RSpec.
104
+
105
+ In your spec_helper.rb:
106
+
107
+ ```
108
+ require 'action_subscriber/rspec'
109
+
110
+ RSpec.configure do |config|
111
+ config.include ::ActionSubscriber::Rspec
112
+ end
113
+ ```
114
+
115
+ In your_subscriber_spec.rb :
116
+ ``` subject { mock_subscriber }```
117
+
118
+ Your test subject will be an instance of your subscriber class, and you can
119
+ easily test your public methods without dependence on data from Rabbit. You can
120
+ optionally pass data for your mock subscriber to consume if you wish.
121
+
122
+ ``` subject { mock_subscriber(:header => "test_header", :payload => "payload") } ```
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ desc "Run specs"
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ desc "Run specs (default)"
8
+ task :default => :spec
@@ -0,0 +1,38 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'action_subscriber/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "action_subscriber"
8
+ spec.version = ActionSubscriber::VERSION
9
+ spec.authors = ["Brian Stien","Adam Hutchison","Brandon Dewitt","Devin Christensen","Michael Ries"]
10
+ spec.email = ["brianastien@gmail.com","liveh2o@gmail.com","brandonsdewitt@gmail.com","quixoten@gmail.com","michael@riesd.com"]
11
+ spec.description = %q{ActionSubscriber is a DSL that allows a rails app to consume messages from a RabbitMQ broker.}
12
+ spec.summary = %q{ActionSubscriber is a DSL that allows a rails app to consume messages from a RabbitMQ broker.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'activesupport', '>= 3.2'
22
+
23
+ if ENV['PLATFORM'] == "java" || ::RUBY_PLATFORM == 'java'
24
+ spec.platform = "java"
25
+ spec.add_dependency 'march_hare', '>= 2.7.0'
26
+ else
27
+ spec.add_dependency 'bunny', '>= 1.5.0'
28
+ end
29
+ spec.add_dependency 'lifeguard'
30
+ spec.add_dependency 'middleware'
31
+
32
+ spec.add_development_dependency "activerecord", ">= 3.2"
33
+ spec.add_development_dependency "bundler", ">= 1.6"
34
+ spec.add_development_dependency "pry-nav"
35
+ spec.add_development_dependency "rspec", "~> 3.0"
36
+ spec.add_development_dependency "rake"
37
+ spec.add_development_dependency "simplecov"
38
+ end
@@ -0,0 +1,17 @@
1
+ class UserSubscriber < ActionSubscriber::Base
2
+ publisher :bob
3
+ exchange :events
4
+
5
+ # By turning on the at_least_once! mode we will tell rabbit
6
+ # to expect message acknowledgements, but ActionSubscriber
7
+ # will handle sending those acknowledgements right after
8
+ # it calls your subscriber. If your subscriber raises an error
9
+ # we will reject the message which causes rabbit to try it again.
10
+ # This way if you have an intermittent error (like a failed databse connection)
11
+ # you can get the message again later.
12
+ at_least_once!
13
+
14
+ def created
15
+ UserProfile.create_for_user(payload)
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ class UserSubscriber < ActionSubscriber::Base
2
+ publisher :bob
3
+ exchange :events
4
+
5
+ # By turning on the at_most_once! mode we will tell rabbit
6
+ # to expect message acknowledgements, but ActionSubscriber
7
+ # will handle sending those acknowledgements right before
8
+ # it calls your subscriber. This way you don't have to worry
9
+ # about the same message being sent to you twice.
10
+ at_most_once!
11
+
12
+ def created
13
+ UserProfile.create_for_user(payload)
14
+ end
15
+ end
@@ -0,0 +1,30 @@
1
+ class UserSubscriber < ActionSubscriber::Base
2
+
3
+ # In this example, we are in an application called "alice"
4
+ # We want to listen to events in bob when bob creates a user.
5
+ # When bob creates a user, bob publishes to "bob.user.created",
6
+ # on an exchange called events.
7
+ #
8
+ # This subscriber listens to bob's events and executes the created
9
+ # method every time bob publishes to the created queue.
10
+
11
+ publisher :bob
12
+ exchange :events
13
+
14
+ # Will create the queue:
15
+ # alice.bob.user.created
16
+ # With the routing key:
17
+ # bob.user.created
18
+ #
19
+ def created
20
+ send_email(payload)
21
+ end
22
+
23
+ private
24
+
25
+ # This is a private method and will be invisible to ActionSubscriber
26
+ #
27
+ def send_email(user)
28
+ # MyMailer.send_welcome_email(user.email, user.name)
29
+ end
30
+ end
@@ -0,0 +1,19 @@
1
+ class UserSubscriber < ActionSubscriber::Base
2
+ publisher :users_hq
3
+ exchange :events
4
+
5
+
6
+ # When we turn on manual acknowledgements RabbitMQ will be expecting us
7
+ # to send back either and acknowledgement or a rejection for each message
8
+ # if we don't send anything back rabbit might stop sending us messages
9
+ manual_acknowledgement!
10
+
11
+ def created
12
+ user_profile = UserProfile.create_for_user(payload)
13
+ if user_profile.save
14
+ acknowledge
15
+ else
16
+ reject
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,93 @@
1
+ require "active_support"
2
+ require "active_support/core_ext"
3
+ if ::RUBY_PLATFORM == "java"
4
+ require 'march_hare'
5
+ else
6
+ require "bunny"
7
+ end
8
+ require "lifeguard"
9
+ require "middleware"
10
+ require "thread"
11
+
12
+ require "action_subscriber/version"
13
+
14
+ require "action_subscriber/default_routing"
15
+ require "action_subscriber/dsl"
16
+ require "action_subscriber/configuration"
17
+ require "action_subscriber/middleware"
18
+ require "action_subscriber/rabbit_connection"
19
+ require "action_subscriber/subscribable"
20
+ require "action_subscriber/bunny/subscriber"
21
+ require "action_subscriber/march_hare/subscriber"
22
+ require "action_subscriber/threadpool"
23
+ require "action_subscriber/base"
24
+
25
+ module ActionSubscriber
26
+ ##
27
+ # Public Class Methods
28
+ #
29
+
30
+ # Loop over all subscribers and pull messages if there are
31
+ # any waiting in the queue for us.
32
+ #
33
+ def self.auto_pop!
34
+ return if ::ActionSubscriber::Threadpool.busy?
35
+ ::ActionSubscriber::Base.inherited_classes.each do |klass|
36
+ klass.auto_pop!
37
+ end
38
+ end
39
+
40
+ # Loop over all subscribers and register each as
41
+ # a subscriber.
42
+ #
43
+ def self.auto_subscribe!
44
+ ::ActionSubscriber::Base.inherited_classes.each do |klass|
45
+ klass.auto_subscribe!
46
+ end
47
+ end
48
+
49
+ def self.configuration
50
+ @configuration ||= ::ActionSubscriber::Configuration.new
51
+ end
52
+
53
+ def self.configure
54
+ yield(configuration) if block_given?
55
+ end
56
+
57
+ def self.print_subscriptions
58
+ ::ActionSubscriber::Base.print_subscriptions
59
+ end
60
+
61
+ def self.setup_queues!
62
+ ::ActionSubscriber::Base.inherited_classes.each do |klass|
63
+ klass.setup_queues!
64
+ end
65
+ end
66
+
67
+ def self.start_queues
68
+ ::ActionSubscriber::RabbitConnection.connect!
69
+ setup_queues!
70
+ print_subscriptions
71
+ end
72
+
73
+ def self.start_subscribers
74
+ ::ActionSubscriber::RabbitConnection.connect!
75
+ setup_queues!
76
+ auto_subscribe!
77
+ print_subscriptions
78
+ end
79
+
80
+ ##
81
+ # Class aliases
82
+ #
83
+ class << self
84
+ alias_method :config, :configuration
85
+ end
86
+
87
+ # Initialize config object
88
+ config
89
+
90
+ ::ActiveSupport.run_load_hooks(:action_subscriber, Base)
91
+ end
92
+
93
+ require "action_subscriber/railtie" if defined?(Rails)
@@ -0,0 +1,83 @@
1
+ module ActionSubscriber
2
+ class Base
3
+ extend ::ActionSubscriber::DefaultRouting
4
+ extend ::ActionSubscriber::DSL
5
+ extend ::ActionSubscriber::Subscribable
6
+ if ::RUBY_PLATFORM == "java"
7
+ extend ::ActionSubscriber::MarchHare::Subscriber
8
+ else
9
+ extend ::ActionSubscriber::Bunny::Subscriber
10
+ end
11
+
12
+ ##
13
+ # Private Attributes
14
+ #
15
+ private
16
+
17
+ attr_reader :env, :payload, :raw_payload
18
+
19
+ public
20
+
21
+ ##
22
+ # Constructor
23
+ #
24
+ def initialize(env)
25
+ @env = env
26
+ @payload = env.payload
27
+ @raw_payload = env.encoded_payload
28
+ end
29
+
30
+ ##
31
+ # Class Methods
32
+ #
33
+
34
+ def self.connection
35
+ ::ActionSubscriber::RabbitConnection.connection
36
+ end
37
+
38
+ # Inherited callback, save a reference to our descendents
39
+ #
40
+ def self.inherited(klass)
41
+ super
42
+
43
+ inherited_classes << klass
44
+ end
45
+
46
+ # Storage for any classes that inherited from us
47
+ #
48
+ def self.inherited_classes
49
+ @_inherited_classes ||= []
50
+ end
51
+
52
+ def self.print_subscriptions
53
+ puts ::ActionSubscriber.configuration.inspect
54
+ puts ""
55
+
56
+ inherited_classes.each do |klass|
57
+ puts klass.inspect
58
+ end
59
+
60
+ nil
61
+ end
62
+
63
+ ##
64
+ # Class Aliases
65
+ #
66
+ class << self
67
+ alias_method :subscribers, :inherited_classes
68
+ end
69
+
70
+ ##
71
+ # Private Instance Methods
72
+ #
73
+ private
74
+
75
+ def acknowledge
76
+ env.acknowledge
77
+ end
78
+
79
+ def reject
80
+ env.reject
81
+ end
82
+ end
83
+ end