promiscuous 0.9.3.1 → 0.10

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.
data/README.md CHANGED
@@ -3,119 +3,22 @@ Promiscuous
3
3
 
4
4
  [![Build Status](https://secure.travis-ci.org/crowdtap/promiscuous.png?branch=master)](https://secure.travis-ci.org/crowdtap/promiscuous)
5
5
 
6
- Promiscuous offers an automatic way of propagating your model data across one or
7
- more applications. It supports Mongoid2, Mongoid3 and ActiveRecord.
8
- It uses [RabbitMQ](http://www.rabbitmq.com/).
9
-
10
- Usage
11
- ------
12
-
13
- From a publisher side (app that owns the data), create a Publisher per model.
14
- as shown below.
15
-
16
- From your subscribers side (apps that receive updates from the publisher),
17
- create a Subscriber per model, as shown below.
18
-
19
- Example
20
- --------
21
-
22
- ### In your publisher app
23
-
24
- ```ruby
25
- # initializer
26
- Promiscuous::AMQP.configure(:app => 'crowdtap',
27
- :server_uri => 'amqp://user:password@host:port/vhost')
28
-
29
- # publisher
30
- class ModelPublisher < Promiscuous::Publisher::Mongoid
31
- publish :to => 'crowdtap/model',
32
- :class => :Model,
33
- :attributes => [:field_1, :field_2, :field_3]
34
- end
35
- ```
36
-
37
- ### In your subscriber app
38
-
39
- ```ruby
40
- # initializer
41
- Promiscuous::AMQP.configure(:app => 'sniper',
42
- :server_uri => 'amqp://user:password@host:port/vhost',
43
- :error_handler => some_proc)
44
-
45
- # subscriber
46
- class ModelSubscriber < Promiscuous::Subscriber::Mongoid
47
- subscribe :from => 'crowdtap/model',
48
- :attributes => [:field_1, :field_2, :field_3],
49
- :class => Model, # optional
50
- :foreign_key => :publisher_id # optional
51
- end
52
- ```
53
-
54
- ### Starting the subscriber worker
55
-
56
- rake promiscuous:replicate
57
-
58
- How does it work ?
59
- ------------------
60
-
61
- 1. On the publisher side, Promiscuous hooks into the after_create/update/destroy callbacks.
62
- 2. When a model changes, Promiscuous sends a message to RabbitMQ, to the
63
- 'promiscuous' [topic exchange](http://www.rabbitmq.com/tutorials/tutorial-five-python.html).
64
- 3. RabbitMQ routes the messages to each application through queues.
65
- We use one queue per application (TODO explain why we need one queue).
66
- 4. Subscribers apps are running the promiscuous worker, listening on their own queues,
67
- executing the create/update/destroy on their databases.
68
-
69
- Note that we use a single exchange to preserve the ordering of data updates
70
- across application so that subscribers always see a consistant state of the
71
- system.
72
-
73
- Synching databases
74
- -------------------
75
-
76
- Documents are created if not present when receiving an update on a non existing
77
- document.
78
-
79
- TODO: Explain how to sync databases.
80
-
81
- WARNING/TODO
82
- ------------
83
-
84
- Promiscuous does **not** handle:
85
- - ActiveRecord polymorphism.
86
- - Any of the Mongoid atomic operatiors, such as inc, or add_to_set.
87
- - Association magic. Example:
88
- ```ruby
89
- # This will NOT replicate particiation_ids:
90
- m = Member.first
91
- m.particiations = [Participation.first]
92
- m.save
93
-
94
- # On the other hand, this will:
95
- m = Member.first
96
- m.particiation_ids = [Participation.first.ids]
97
- m.save
98
- ```
99
-
100
- Furthermore, it can be racy. Consider this scenario with two interleaving
101
- requests A and B:
102
-
103
- 1. (A) Update mongo doc X.value = 1
104
- 2. (B) Update mongo doc X.value = 2
105
- 3. (B) Publish 'X.value = 2' to Rabbit
106
- 4. (A) Publish 'X.value = 1' to Rabbit
107
-
108
- At the end of the scenario, on the publisher side, the document X has value
109
- equal to 2, while on the subscriber side, the document has a value of 1. This
110
- will likely not occur in most scenarios BUT BEWARE. We have plans to fix this
111
- issue by using version numbers and mongo's amazing findandmodify.
112
-
113
- Backend: bunny / ruby-amqp
114
- --------------------------
115
-
116
- Your publisher app may not run an eventmachine loop, which is required for
117
- ruby-amqp. Bunny on the other hand allows a non-eventmachine based application
118
- to publish messages to rabbitmq.
6
+ Promiscuous is designed to facilitate designing a
7
+ [service-oriented architecture](http://en.wikipedia.org/wiki/Service-oriented_architecture)
8
+ in Ruby.
9
+
10
+ Promiscuous offers an automatic way of propagating your data across one or more
11
+ applications. It supports Mongoid2, Mongoid3 and ActiveRecord.
12
+ It relies on [RabbitMQ](http://www.rabbitmq.com/) to push data around.
13
+
14
+ Philosophy
15
+ ----------
16
+
17
+ In order for a service-oriented system to be successful, services *must* be
18
+ loosely coupled. The traditional Ruby way of tackling this problem is to
19
+ provide RESTful APIs.
20
+ Sadly, this come to a cost since one must write controllers, integration tests, etc.
21
+ Promiscuous to the rescue
119
22
 
120
23
  Compatibility
121
24
  -------------
@@ -11,12 +11,12 @@ module Promiscuous
11
11
  raise "Please use amqp://user:password@host:port/vhost" if uri.scheme != 'amqp'
12
12
 
13
13
  {
14
- :host => uri.host,
15
- :port => uri.port,
14
+ :host => uri.host,
15
+ :port => uri.port,
16
16
  :scheme => uri.scheme,
17
- :user => uri.user,
18
- :pass => uri.password,
19
- :vhost => uri.path.empty? ? "/" : uri.path,
17
+ :user => uri.user,
18
+ :pass => uri.password,
19
+ :vhost => uri.path.empty? ? "/" : uri.path,
20
20
  }
21
21
  end
22
22
 
@@ -26,7 +26,8 @@ module Promiscuous
26
26
  end
27
27
 
28
28
  def self.disconnect
29
- channel.close
29
+ self.channel.close if self.channel
30
+ self.channel = nil
30
31
  end
31
32
 
32
33
  def self.subscribe(options={}, &block)
@@ -0,0 +1,17 @@
1
+ module Promiscuous::Common::Worker
2
+ extend ActiveSupport::Concern
3
+
4
+ def initialize
5
+ self.stop = false
6
+ end
7
+
8
+ def unit_of_work
9
+ if defined?(Mongoid)
10
+ Mongoid.unit_of_work { yield }
11
+ else
12
+ yield
13
+ end
14
+ end
15
+
16
+ included { attr_accessor :stop }
17
+ end
@@ -2,4 +2,5 @@ module Promiscuous::Common
2
2
  autoload :Options, 'promiscuous/common/options'
3
3
  autoload :Lint, 'promiscuous/common/lint'
4
4
  autoload :ClassHelpers, 'promiscuous/common/class_helpers'
5
+ autoload :Worker, 'promiscuous/common/worker'
5
6
  end
@@ -2,7 +2,7 @@ module Promiscuous::Publisher::AMQP
2
2
  extend ActiveSupport::Concern
3
3
  include Promiscuous::Publisher::Envelope
4
4
 
5
- def amqp_publish
5
+ def publish
6
6
  Promiscuous::AMQP.publish(:key => to, :payload => payload.to_json)
7
7
  end
8
8
 
@@ -0,0 +1,18 @@
1
+ class Promiscuous::Publisher::Error < RuntimeError
2
+ attr_accessor :inner, :instance
3
+
4
+ def initialize(inner, instance)
5
+ super(inner)
6
+ set_backtrace(inner.backtrace)
7
+ self.inner = inner
8
+ self.instance = instance
9
+ end
10
+
11
+ def message
12
+ "#{inner.message} while processing #{instance}"
13
+ end
14
+
15
+ def to_s
16
+ message
17
+ end
18
+ end
@@ -33,7 +33,7 @@ module Promiscuous::Publisher::Model
33
33
  [:create, :update, :destroy].each do |operation|
34
34
  __send__("after_#{operation}", "promiscuous_publish_#{operation}".to_sym)
35
35
  define_method "promiscuous_publish_#{operation}" do
36
- self.class.promiscuous_publisher.new(:instance => self, :operation => operation).amqp_publish
36
+ self.class.promiscuous_publisher.new(:instance => self, :operation => operation).publish
37
37
  end
38
38
  end
39
39
  alias :promiscuous_sync :promiscuous_publish_update
@@ -0,0 +1,52 @@
1
+ module Promiscuous::Publisher::Mongoid::Defer
2
+ extend ActiveSupport::Concern
3
+
4
+ mattr_accessor :klasses
5
+ self.klasses = {}
6
+
7
+ def publish
8
+ super unless should_defer?
9
+ end
10
+
11
+ def should_defer?
12
+ if options.has_key?(:defer)
13
+ options[:defer]
14
+ else
15
+ operation == :update
16
+ end
17
+ end
18
+
19
+ def self.hook_mongoid
20
+ return if @mongoid_hooked
21
+ @mongoid_hooked = true
22
+
23
+ Moped::Query.class_eval do
24
+ alias_method :update_orig, :update
25
+ def update(change, flags = nil)
26
+ if klass = Promiscuous::Publisher::Mongoid::Defer.klasses[@collection.name]
27
+ psp_field = klass.aliased_fields["promiscous_sync_pending"]
28
+ change = change.dup
29
+ change['$set'] ||= {}
30
+ change['$set'].merge!(psp_field => true)
31
+ end
32
+ update_orig(change, flags)
33
+ end
34
+ end
35
+ end
36
+
37
+ included do
38
+ klass.class_eval do
39
+ cattr_accessor :publisher_defer_hooked
40
+ return if self.publisher_defer_hooked
41
+ self.publisher_defer_hooked = true
42
+
43
+ # TODO Make sure we are not overriding a field, although VERY unlikly
44
+ psp_field = :_psp
45
+ field psp_field, :as => :promiscous_sync_pending, :type => Boolean
46
+ index({psp_field => 1}, :background => true, :sparse => true)
47
+
48
+ Promiscuous::Publisher::Mongoid::Defer.hook_mongoid
49
+ Promiscuous::Publisher::Mongoid::Defer.klasses[collection.name] = self
50
+ end
51
+ end
52
+ end
@@ -1,5 +1,6 @@
1
1
  class Promiscuous::Publisher::Mongoid < Promiscuous::Publisher::Base
2
2
  autoload :Embedded, 'promiscuous/publisher/mongoid/embedded'
3
+ autoload :Defer, 'promiscuous/publisher/mongoid/defer'
3
4
 
4
5
  include Promiscuous::Publisher::Class
5
6
  include Promiscuous::Publisher::Attributes
@@ -13,6 +14,11 @@ class Promiscuous::Publisher::Mongoid < Promiscuous::Publisher::Base
13
14
  include Promiscuous::Publisher::Mongoid::Embedded
14
15
  else
15
16
  include Promiscuous::Publisher::Model
17
+ include Promiscuous::Publisher::Mongoid::Defer if mongoid3?
16
18
  end
17
19
  end
20
+
21
+ def self.mongoid3?
22
+ Gem.loaded_specs['mongoid'].version >= Gem::Version.new('3.0.0')
23
+ end
18
24
  end
@@ -0,0 +1,49 @@
1
+ class Promiscuous::Publisher::Worker
2
+ include Promiscuous::Common::Worker
3
+
4
+ def self.poll_delay
5
+ # TODO Configurable globally
6
+ # TODO Configurable per publisher
7
+ 1.second
8
+ end
9
+
10
+ def replicate
11
+ EM.defer proc { self.replicate_once },
12
+ proc { EM::Timer.new(self.class.poll_delay) { replicate } }
13
+ end
14
+
15
+ def replicate_once
16
+ return if self.stop
17
+ begin
18
+ self.unit_of_work do
19
+ Promiscuous::Publisher::Mongoid::Defer.klasses.values.each do |klass|
20
+ replicate_collection(klass)
21
+ end
22
+ end
23
+ rescue Exception => e
24
+ self.stop = true
25
+ unless e.is_a?(Promiscuous::Publisher::Error)
26
+ e = Promiscuous::Publisher::Error.new(e, nil)
27
+ end
28
+ Promiscuous.error "[publish] FATAL #{e}"
29
+ Promiscuous::Config.error_handler.try(:call, e)
30
+ end
31
+ end
32
+
33
+ def replicate_collection(klass)
34
+ return if self.stop
35
+ # TODO Check for indexes and if not there, bail out
36
+ psp_field = klass.aliased_fields["promiscous_sync_pending"]
37
+ while instance = klass.where(psp_field => true).find_and_modify({'$unset' => {psp_field => 1}})
38
+ replicate_instance(instance)
39
+ end
40
+ end
41
+
42
+ def replicate_instance(instance)
43
+ return if self.stop
44
+ instance.class.promiscuous_publisher.new(:instance => instance, :operation => :update, :defer => false).publish
45
+ rescue Exception => e
46
+ # TODO set back the psp field
47
+ raise Promiscuous::Publisher::Error.new(e, instance)
48
+ end
49
+ end
@@ -10,6 +10,8 @@ module Promiscuous::Publisher
10
10
  autoload :Model, 'promiscuous/publisher/model'
11
11
  autoload :Mongoid, 'promiscuous/publisher/mongoid'
12
12
  autoload :Polymorphic, 'promiscuous/publisher/polymorphic'
13
+ autoload :Worker, 'promiscuous/publisher/worker'
14
+ autoload :Error, 'promiscuous/publisher/error'
13
15
 
14
16
  def self.lint(*args)
15
17
  Lint.lint(*args)
@@ -1,18 +1,38 @@
1
1
  namespace :promiscuous do
2
- desc 'Run the subscribers worker'
2
+ # Note This rake task can be loaded without Rails
3
+ desc 'Run the workers'
3
4
  task :replicate => :environment do |t|
4
- require 'promiscuous/worker'
5
5
  require 'eventmachine'
6
6
  require 'em-synchrony'
7
7
 
8
8
  EM.synchrony do
9
- Promiscuous::Loader.load_descriptors :subscribers if defined?(Rails)
10
- Promiscuous::AMQP.disconnect
11
- Promiscuous::Config.backend = :rubyamqp
12
- Promiscuous::AMQP.connect
9
+ trap_signals
10
+ force_backend :rubyamqp
11
+
12
+ Promiscuous::Loader.load_descriptors if defined?(Rails)
13
13
 
14
14
  Promiscuous::Worker.replicate
15
- $stderr.puts "Replicating with #{Promiscuous::Subscriber::AMQP.subscribers.count} subscribers"
15
+
16
+ msg = "Replicating with #{Promiscuous::Subscriber::AMQP.subscribers.count} subscribers" +
17
+ " and #{Promiscuous::Publisher::Mongoid::Defer.klasses.count} publishers"
18
+ Promiscuous.info msg
19
+ $stderr.puts msg
16
20
  end
17
21
  end
22
+
23
+ def trap_signals
24
+ %w(SIGTERM SIGINT).each do |signal|
25
+ Signal.trap(signal) do
26
+ Promiscuous.info "Exiting..."
27
+ Promiscuous::Worker.stop
28
+ EM.stop
29
+ end
30
+ end
31
+ end
32
+
33
+ def force_backend(backend)
34
+ Promiscuous::AMQP.disconnect
35
+ Promiscuous::Config.backend = backend
36
+ Promiscuous::AMQP.connect
37
+ end
18
38
  end
@@ -16,7 +16,7 @@ module Promiscuous::Subscriber::AMQP
16
16
  included { use_option :from }
17
17
 
18
18
  module ClassMethods
19
- def from=(value)
19
+ def from=(_)
20
20
  super
21
21
  old_sub = Promiscuous::Subscriber::AMQP.subscribers[from]
22
22
  raise "The subscriber '#{old_sub}' already listen on '#{from}'" if old_sub
@@ -2,7 +2,7 @@ module Promiscuous::Subscriber::Mongoid::Embedded
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  def fetch
5
- old_value.nil? ? klass.new.tap { |m| m.id = id } : old_value
5
+ (old_value || klass.new).tap { |m| m.id = id }
6
6
  end
7
7
 
8
8
  def old_value
@@ -30,7 +30,6 @@ module Promiscuous::Subscriber::Polymorphic
30
30
 
31
31
  def polymorphic_subscriber_from(payload)
32
32
  type = payload.is_a?(Hash) ? payload['type'] : nil
33
- raise "The payload is missing the type information'" if type.nil?
34
33
  polymorphic_map[type] || self
35
34
  end
36
35
  end
@@ -0,0 +1,31 @@
1
+ class Promiscuous::Subscriber::Worker
2
+ include Promiscuous::Common::Worker
3
+
4
+ def replicate
5
+ Promiscuous::AMQP.subscribe(subscribe_options) do |metadata, payload|
6
+ # Note: This code always runs on the root Fiber,
7
+ # so ordering is always preserved
8
+ begin
9
+ unless self.stop
10
+ Promiscuous.info "[receive] #{payload}"
11
+ self.unit_of_work { Promiscuous::Subscriber.process(JSON.parse(payload)) }
12
+ metadata.ack
13
+ end
14
+ rescue Exception => e
15
+ e = Promiscuous::Subscriber::Error.new(e, payload)
16
+
17
+ # TODO Discuss with Arjun about having an error queue.
18
+ self.stop = true
19
+ Promiscuous::AMQP.disconnect
20
+ Promiscuous.error "[receive] FATAL #{e}"
21
+ Promiscuous::Config.error_handler.try(:call, e)
22
+ end
23
+ end
24
+ end
25
+
26
+ def subscribe_options
27
+ queue_name = "#{Promiscuous::Config.app}.promiscuous"
28
+ bindings = Promiscuous::Subscriber::AMQP.subscribers.keys
29
+ {:queue_name => queue_name, :bindings => bindings}
30
+ end
31
+ end
@@ -12,6 +12,7 @@ module Promiscuous::Subscriber
12
12
  autoload :Polymorphic, 'promiscuous/subscriber/polymorphic'
13
13
  autoload :Upsert, 'promiscuous/subscriber/upsert'
14
14
  autoload :Observer, 'promiscuous/subscriber/observer'
15
+ autoload :Worker, 'promiscuous/subscriber/worker'
15
16
 
16
17
  def self.lint(*args)
17
18
  Lint.lint(*args)
@@ -1,3 +1,3 @@
1
1
  module Promiscuous
2
- VERSION = '0.9.3.1'
2
+ VERSION = '0.10'
3
3
  end
@@ -1,51 +1,15 @@
1
- module Promiscuous
2
- module Worker
3
- mattr_accessor :stop
4
-
5
- def self.replicate
6
- self.stop = false
7
- self.trap_signals unless ENV['TEST_ENV']
8
-
9
- Promiscuous::AMQP.subscribe(subscribe_options) do |metadata, payload|
10
- begin
11
- unless self.stop
12
- Promiscuous.info "[receive] #{payload}"
13
- self.mongoid_wrapper { Promiscuous::Subscriber.process(JSON.parse(payload)) }
14
- metadata.ack
15
- end
16
- rescue Exception => e
17
- e = Promiscuous::Subscriber::Error.new(e, payload)
18
-
19
- self.stop = true
20
- Promiscuous::AMQP.disconnect
21
- Promiscuous.error "[receive] FATAL #{e}"
22
- Promiscuous::Config.error_handler.call(e) if Promiscuous::Config.error_handler
23
- end
24
- end
25
- end
26
-
27
- def self.mongoid_wrapper
28
- if defined?(Mongoid)
29
- Mongoid.unit_of_work { yield }
30
- else
31
- yield
32
- end
33
- end
34
-
35
- def self.trap_signals
36
- %w(SIGTERM SIGINT).each do |signal|
37
- Signal.trap(signal) do
38
- self.stop = true
39
- EM.stop
40
- Promiscuous.info "exiting gracefully"
41
- end
42
- end
43
- end
1
+ module Promiscuous::Worker
2
+ mattr_accessor :workers
3
+ self.workers = []
4
+
5
+ def self.replicate
6
+ self.workers << Promiscuous::Publisher::Worker.new
7
+ self.workers << Promiscuous::Subscriber::Worker.new
8
+ self.workers.each { |w| w.replicate }
9
+ end
44
10
 
45
- def self.subscribe_options
46
- queue_name = "#{Promiscuous::Config.app}.promiscuous"
47
- bindings = Promiscuous::Subscriber::AMQP.subscribers.keys
48
- {:queue_name => queue_name, :bindings => bindings}
49
- end
11
+ def self.stop
12
+ self.workers.each { |w| w.stop = true }
13
+ self.workers.clear
50
14
  end
51
15
  end
data/lib/promiscuous.rb CHANGED
@@ -9,6 +9,7 @@ module Promiscuous
9
9
  autoload :Publisher, 'promiscuous/publisher'
10
10
  autoload :Subscriber, 'promiscuous/subscriber'
11
11
  autoload :Observer, 'promiscuous/observer'
12
+ autoload :Worker, 'promiscuous/worker'
12
13
 
13
14
  class << self
14
15
  def configure(&block)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: promiscuous
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.3.1
4
+ version: '0.10'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-10-03 00:00:00.000000000 Z
13
+ date: 2012-10-10 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -105,8 +105,8 @@ files:
105
105
  - lib/promiscuous/amqp/rubyamqp.rb
106
106
  - lib/promiscuous/publisher/envelope.rb
107
107
  - lib/promiscuous/publisher/mongoid/embedded.rb
108
+ - lib/promiscuous/publisher/mongoid/defer.rb
108
109
  - lib/promiscuous/publisher/active_record.rb
109
- - lib/promiscuous/publisher/amqp.rb
110
110
  - lib/promiscuous/publisher/attributes.rb
111
111
  - lib/promiscuous/publisher/base.rb
112
112
  - lib/promiscuous/publisher/lint.rb
@@ -115,17 +115,19 @@ files:
115
115
  - lib/promiscuous/publisher/lint/base.rb
116
116
  - lib/promiscuous/publisher/lint/class.rb
117
117
  - lib/promiscuous/publisher/lint/polymorphic.rb
118
- - lib/promiscuous/publisher/mongoid.rb
119
118
  - lib/promiscuous/publisher/polymorphic.rb
120
- - lib/promiscuous/publisher/model.rb
121
119
  - lib/promiscuous/publisher/class.rb
122
120
  - lib/promiscuous/publisher/mock.rb
121
+ - lib/promiscuous/publisher/amqp.rb
122
+ - lib/promiscuous/publisher/error.rb
123
+ - lib/promiscuous/publisher/model.rb
124
+ - lib/promiscuous/publisher/mongoid.rb
125
+ - lib/promiscuous/publisher/worker.rb
123
126
  - lib/promiscuous/railtie/replicate.rake
124
127
  - lib/promiscuous/subscriber/envelope.rb
125
128
  - lib/promiscuous/subscriber/error.rb
126
129
  - lib/promiscuous/subscriber/mongoid/embedded.rb
127
130
  - lib/promiscuous/subscriber/active_record.rb
128
- - lib/promiscuous/subscriber/amqp.rb
129
131
  - lib/promiscuous/subscriber/lint.rb
130
132
  - lib/promiscuous/subscriber/lint/amqp.rb
131
133
  - lib/promiscuous/subscriber/lint/base.rb
@@ -137,23 +139,26 @@ files:
137
139
  - lib/promiscuous/subscriber/upsert.rb
138
140
  - lib/promiscuous/subscriber/model.rb
139
141
  - lib/promiscuous/subscriber/class.rb
140
- - lib/promiscuous/subscriber/polymorphic.rb
141
- - lib/promiscuous/subscriber/attributes.rb
142
142
  - lib/promiscuous/subscriber/observer.rb
143
+ - lib/promiscuous/subscriber/attributes.rb
144
+ - lib/promiscuous/subscriber/amqp.rb
145
+ - lib/promiscuous/subscriber/polymorphic.rb
146
+ - lib/promiscuous/subscriber/worker.rb
143
147
  - lib/promiscuous/config.rb
144
148
  - lib/promiscuous/amqp.rb
145
149
  - lib/promiscuous/common/lint.rb
146
150
  - lib/promiscuous/common/lint/base.rb
147
151
  - lib/promiscuous/common/options.rb
148
152
  - lib/promiscuous/common/class_helpers.rb
153
+ - lib/promiscuous/common/worker.rb
149
154
  - lib/promiscuous/loader.rb
150
- - lib/promiscuous/publisher.rb
151
155
  - lib/promiscuous/railtie.rb
152
- - lib/promiscuous/worker.rb
153
- - lib/promiscuous/common.rb
154
156
  - lib/promiscuous/observer.rb
157
+ - lib/promiscuous/common.rb
158
+ - lib/promiscuous/publisher.rb
155
159
  - lib/promiscuous/subscriber.rb
156
160
  - lib/promiscuous/version.rb
161
+ - lib/promiscuous/worker.rb
157
162
  - lib/promiscuous.rb
158
163
  - README.md
159
164
  homepage: http://github.com/crowdtap/promiscuous