multiple_man 0.5.21 → 0.6.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.
- checksums.yaml +4 -4
- data/README.md +29 -14
- data/lib/multiple_man/connection.rb +1 -1
- data/lib/multiple_man/listeners/listener.rb +1 -2
- data/lib/multiple_man/mixins/listener.rb +43 -0
- data/lib/multiple_man/mixins/subscriber.rb +1 -1
- data/lib/multiple_man/model_populator.rb +5 -4
- data/lib/multiple_man/model_publisher.rb +8 -4
- data/lib/multiple_man/subscribers/base.rb +1 -1
- data/lib/multiple_man/subscribers/model_subscriber.rb +4 -2
- data/lib/multiple_man/subscribers/registry.rb +1 -1
- data/lib/multiple_man/tasks/worker.rake +4 -3
- data/lib/multiple_man/version.rb +1 -1
- data/lib/multiple_man.rb +1 -0
- data/spec/model_populator_spec.rb +9 -2
- data/spec/subscriber_spec.rb +3 -3
- data/spec/subscribers/base_spec.rb +6 -2
- data/spec/subscribers/model_subscriber_spec.rb +8 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d8c65c40b50ae40908c59ef6c0cd66d724f461ea
|
4
|
+
data.tar.gz: 3403383d7f31f579dcc4b450a65f5f482ea7c5cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b487f16fa2d71cf90898bd0024667b41c1d46fd84d53b9c650ecbc98a0d582ed2793bd68b2dab1de491473763d1fb5c82f6558f058e30a58f8daa3cc731a9e5f
|
7
|
+
data.tar.gz: fb7be9925bfa4d7b26747029bf0b313b236af14b2b03e71a37ca548e860d62fe21eee772601fb5922b600c29d8f4c8bd59366050c6f6001b522332bd3c81e80c
|
data/README.md
CHANGED
@@ -8,7 +8,7 @@ MultipleMan synchronizes your ActiveRecord models between Rails
|
|
8
8
|
apps, using RabbitMQ to send messages between your applications.
|
9
9
|
It's heavily inspired by Promiscuous, but differs in a few ways:
|
10
10
|
|
11
|
-
- MultipleMan makes a hard assumption that you're using
|
11
|
+
- MultipleMan makes a hard assumption that you're using
|
12
12
|
ActiveRecord for your models. This simplifies how models
|
13
13
|
are sychronized, which offers a few benefits like:
|
14
14
|
- Transactions are fully supported in MultipleMan. Records
|
@@ -38,9 +38,9 @@ calling MultipleMan.configure like so:
|
|
38
38
|
|
39
39
|
MultipleMan.configure do |config|
|
40
40
|
# A connection string to your local server. Defaults to localhost.
|
41
|
-
config.connection = "amqp://example.com"
|
41
|
+
config.connection = "amqp://example.com"
|
42
42
|
|
43
|
-
# The topic name to push to. If you have multiple
|
43
|
+
# The topic name to push to. If you have multiple
|
44
44
|
# multiple man apps, this should be unique per application. Publishers
|
45
45
|
# and subscribers should always use the same topic.
|
46
46
|
config.topic_name = "multiple_man"
|
@@ -50,10 +50,10 @@ calling MultipleMan.configure like so:
|
|
50
50
|
config.app_name = "MyApp"
|
51
51
|
|
52
52
|
# Specify what should happen when MultipleMan
|
53
|
-
# encounters an exception.
|
53
|
+
# encounters an exception.
|
54
54
|
config.on_error do |exception|
|
55
55
|
ErrorLogger.log(exception)
|
56
|
-
end
|
56
|
+
end
|
57
57
|
|
58
58
|
# Where you want to log errors to. Should be an instance of Logger
|
59
59
|
# Defaults to the Rails logger (for Rails) or STDOUT otherwise.
|
@@ -95,16 +95,16 @@ You can use the following options when publishing:
|
|
95
95
|
an ActiveRecord serializer (or anything that takes your
|
96
96
|
record in the constructor and has an `as_json` method) to
|
97
97
|
serialize models instead.
|
98
|
-
- `as` - If you want the name of the model from the
|
98
|
+
- `as` - If you want the name of the model from the
|
99
99
|
perspective of MultipleMan to be different than the model
|
100
|
-
name in Rails, specify `as` with the name you want to use.
|
100
|
+
name in Rails, specify `as` with the name you want to use.
|
101
101
|
Useful for STI.
|
102
102
|
- `identify_by` - Specify an array of fields that MultipleMan
|
103
103
|
should use to identify your record on the subscriber end.
|
104
|
-
`id` is used by default and is generally fine, unless you're working in a multi-tenant environment where ids may
|
104
|
+
`id` is used by default and is generally fine, unless you're working in a multi-tenant environment where ids may
|
105
105
|
be shared between two different models of the same class.
|
106
106
|
- (DEPRECATED) `identifier` - Either a symbol or a proc used by MultipleMan to identify your model.
|
107
|
-
|
107
|
+
|
108
108
|
### Publishing
|
109
109
|
|
110
110
|
By default, MultipleMan will publish all of your models whenever you save a model (in an `after_commit` hook). If you need to manually publish models, you can do so with the `multiple_man_publish` method, which acts like a scope on your models, like so:
|
@@ -120,7 +120,7 @@ Widget.where(published: true).multiple_man_publish
|
|
120
120
|
Widget.first.multiple_man_publish
|
121
121
|
```
|
122
122
|
|
123
|
-
If you're publishing multiple models, it's best to use the
|
123
|
+
If you're publishing multiple models, it's best to use the
|
124
124
|
version of multiple_man_publish that operates on a collection. By calling the individual version, a channel is opened and closed for each model, which can impact the thoroughput of MultipleMan.
|
125
125
|
|
126
126
|
### Subscribing to models
|
@@ -143,6 +143,22 @@ By default, MultipleMan will attempt to identify which model on the subscriber m
|
|
143
143
|
|
144
144
|
If your publisher specifies an `identifier` option, you *must* include a column on the subscriber side called `multiple_man_identifier`. MultipleMan will attempt to locate models on the subscriber side by this column.
|
145
145
|
|
146
|
+
## Listening for model changes
|
147
|
+
|
148
|
+
If you want to do something other than populate a model on MultipleMan messages,
|
149
|
+
you can listen for messages and process them however you'd like:
|
150
|
+
|
151
|
+
```
|
152
|
+
def ListenerClass
|
153
|
+
include MultipleMan::Listener
|
154
|
+
listen_to 'ModelName'
|
155
|
+
|
156
|
+
def create(payload)
|
157
|
+
# do something when a model is created
|
158
|
+
end
|
159
|
+
end
|
160
|
+
``
|
161
|
+
|
146
162
|
## Listening for subscriptions
|
147
163
|
|
148
164
|
Once you've set up your subscribers, you'll need to run a background worker to manage
|
@@ -152,9 +168,9 @@ the subscription process. Just run the following:
|
|
152
168
|
|
153
169
|
## Seeding
|
154
170
|
|
155
|
-
One common problem when using MultipleMan on an existing project is that there's already a lot of data that you want to process using your listeners. MultipleMan provides a mechanism called "seeding" to accomplish this.
|
171
|
+
One common problem when using MultipleMan on an existing project is that there's already a lot of data that you want to process using your listeners. MultipleMan provides a mechanism called "seeding" to accomplish this.
|
156
172
|
|
157
|
-
1. On the subscriber side, start listening for seed requests with the following rake task:
|
173
|
+
1. On the subscriber side, start listening for seed requests with the following rake task:
|
158
174
|
|
159
175
|
```
|
160
176
|
rake multiple_man:seed
|
@@ -166,7 +182,7 @@ rake multiple_man:seed
|
|
166
182
|
MyModel.multiple_man_publish(:seed)
|
167
183
|
```
|
168
184
|
|
169
|
-
3. Stop the seeder rake task when all of your messages have been processed. You can check your RabbitMQ server
|
185
|
+
3. Stop the seeder rake task when all of your messages have been processed. You can check your RabbitMQ server
|
170
186
|
|
171
187
|
## Contributing
|
172
188
|
|
@@ -202,4 +218,3 @@ THE SOFTWARE.
|
|
202
218
|
|
203
219
|
|
204
220
|
[](https://bitdeli.com/free "Bitdeli Badge")
|
205
|
-
|
@@ -7,7 +7,6 @@ module MultipleMan::Listeners
|
|
7
7
|
class << self
|
8
8
|
def start
|
9
9
|
MultipleMan.logger.debug "Starting listeners."
|
10
|
-
MultipleMan.logger.debug MultipleMan::Subscribers::Registry.subscriptions.to_json
|
11
10
|
|
12
11
|
MultipleMan::Subscribers::Registry.subscriptions.each do |subscription|
|
13
12
|
new(subscription).listen
|
@@ -33,7 +32,7 @@ module MultipleMan::Listeners
|
|
33
32
|
attr_accessor :subscription, :connection
|
34
33
|
|
35
34
|
def listen
|
36
|
-
|
35
|
+
|
37
36
|
MultipleMan.logger.info "Listening for #{subscription.klass} with routing key #{routing_key}."
|
38
37
|
queue.bind(connection.topic, routing_key: routing_key).subscribe(ack: true) do |delivery_info, meta_data, payload|
|
39
38
|
process_message(delivery_info, payload)
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module MultipleMan
|
2
|
+
module Listener
|
3
|
+
def Listener.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
def routing_key
|
8
|
+
MultipleMan::RoutingKey.new(klass, operation).to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_accessor :klass
|
12
|
+
attr_accessor :operation
|
13
|
+
|
14
|
+
def create
|
15
|
+
# noop
|
16
|
+
end
|
17
|
+
|
18
|
+
def update
|
19
|
+
# noop
|
20
|
+
end
|
21
|
+
|
22
|
+
def destroy
|
23
|
+
# noop
|
24
|
+
end
|
25
|
+
|
26
|
+
def seed
|
27
|
+
# noop
|
28
|
+
end
|
29
|
+
|
30
|
+
def queue_name
|
31
|
+
"#{MultipleMan.configuration.topic_name}.#{MultipleMan.configuration.app_name}.#{klass}"
|
32
|
+
end
|
33
|
+
|
34
|
+
module ClassMethods
|
35
|
+
def listen_to(model, operation: '#')
|
36
|
+
listener = self.new
|
37
|
+
listener.klass = model
|
38
|
+
listener.operation = operation
|
39
|
+
Subscribers::Registry.register(listener)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -9,12 +9,13 @@ module MultipleMan
|
|
9
9
|
def populate(payload)
|
10
10
|
data = payload[:id].merge(payload[:data])
|
11
11
|
fields_for(data).each do |field|
|
12
|
-
|
12
|
+
source, dest = field.is_a?(Array) ? field : [field, field]
|
13
|
+
populate_field(dest, data[source])
|
13
14
|
end
|
14
15
|
record
|
15
16
|
end
|
16
17
|
|
17
|
-
private
|
18
|
+
private
|
18
19
|
attr_accessor :record, :fields
|
19
20
|
|
20
21
|
# Raise an exception if explicit fields were provided.
|
@@ -23,7 +24,7 @@ module MultipleMan
|
|
23
24
|
end
|
24
25
|
|
25
26
|
def populate_field(field, value)
|
26
|
-
#
|
27
|
+
# Attempts to populate source id if id is specified
|
27
28
|
if field.to_s == 'id' && record.respond_to?('source_id')
|
28
29
|
field = 'source_id'
|
29
30
|
end
|
@@ -40,4 +41,4 @@ module MultipleMan
|
|
40
41
|
fields || data.keys
|
41
42
|
end
|
42
43
|
end
|
43
|
-
end
|
44
|
+
end
|
@@ -11,10 +11,16 @@ module MultipleMan
|
|
11
11
|
return unless MultipleMan.configuration.enabled
|
12
12
|
|
13
13
|
Connection.connect do |connection|
|
14
|
-
|
15
|
-
|
14
|
+
ActiveSupport::Notifications.instrument('multiple_man.publish_messages') do
|
15
|
+
all_records(records) do |record|
|
16
|
+
ActiveSupport::Notifications.instrument('multiple_man.publish_message') do
|
17
|
+
push_record(connection, record, operation)
|
18
|
+
end
|
19
|
+
end
|
16
20
|
end
|
17
21
|
end
|
22
|
+
rescue Exception => ex
|
23
|
+
MultipleMan.error(ex)
|
18
24
|
end
|
19
25
|
|
20
26
|
private
|
@@ -28,8 +34,6 @@ module MultipleMan
|
|
28
34
|
MultipleMan.logger.debug(" Record Data: #{data} | Routing Key: #{routing_key}")
|
29
35
|
|
30
36
|
connection.topic.publish(data.payload, routing_key: routing_key)
|
31
|
-
rescue Exception => ex
|
32
|
-
MultipleMan.error(ex)
|
33
37
|
end
|
34
38
|
|
35
39
|
def all_records(records, &block)
|
@@ -28,7 +28,7 @@ module MultipleMan::Subscribers
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def queue_name
|
31
|
-
"#{MultipleMan.configuration.topic_name}.#{MultipleMan.configuration.app_name}.#{klass
|
31
|
+
"#{MultipleMan.configuration.topic_name}.#{MultipleMan.configuration.app_name}.#{klass}"
|
32
32
|
end
|
33
33
|
|
34
34
|
private
|
@@ -2,7 +2,8 @@ module MultipleMan::Subscribers
|
|
2
2
|
class ModelSubscriber < Base
|
3
3
|
|
4
4
|
def initialize(klass, options)
|
5
|
-
|
5
|
+
self.model_class = klass
|
6
|
+
super(options[:to] || klass.name)
|
6
7
|
self.options = options
|
7
8
|
end
|
8
9
|
|
@@ -26,7 +27,7 @@ module MultipleMan::Subscribers
|
|
26
27
|
private
|
27
28
|
|
28
29
|
def find_model(id)
|
29
|
-
|
30
|
+
model_class.where(find_conditions(id)).first || model_class.new
|
30
31
|
end
|
31
32
|
|
32
33
|
def find_conditions(id)
|
@@ -43,6 +44,7 @@ module MultipleMan::Subscribers
|
|
43
44
|
end
|
44
45
|
|
45
46
|
attr_writer :klass
|
47
|
+
attr_accessor :model_class
|
46
48
|
|
47
49
|
end
|
48
50
|
end
|
data/lib/multiple_man/version.rb
CHANGED
data/lib/multiple_man.rb
CHANGED
@@ -6,6 +6,7 @@ module MultipleMan
|
|
6
6
|
|
7
7
|
require 'multiple_man/mixins/publisher'
|
8
8
|
require 'multiple_man/mixins/subscriber'
|
9
|
+
require 'multiple_man/mixins/listener'
|
9
10
|
require 'multiple_man/subscribers/base'
|
10
11
|
require 'multiple_man/subscribers/model_subscriber'
|
11
12
|
require 'multiple_man/subscribers/registry'
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe MultipleMan::ModelPopulator do
|
3
|
+
describe MultipleMan::ModelPopulator do
|
4
4
|
class MockModel
|
5
5
|
attr_accessor :a, :b, :multiple_man_identifier
|
6
6
|
end
|
@@ -27,6 +27,13 @@ describe MultipleMan::ModelPopulator do
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
30
|
+
|
31
|
+
context "with fields as a hash" do
|
32
|
+
let(:fields) { { b: :a } }
|
33
|
+
|
34
|
+
its(:b) { should == nil }
|
35
|
+
its(:a) { should == 2 }
|
36
|
+
end
|
30
37
|
context "record has source id" do
|
31
38
|
let(:model) { Class.new do
|
32
39
|
attr_accessor :source_id, :id
|
@@ -57,4 +64,4 @@ describe MultipleMan::ModelPopulator do
|
|
57
64
|
end
|
58
65
|
end
|
59
66
|
end
|
60
|
-
end
|
67
|
+
end
|
data/spec/subscriber_spec.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe MultipleMan::Subscriber do
|
3
|
+
describe MultipleMan::Subscriber do
|
4
4
|
class MockClass
|
5
5
|
include MultipleMan::Subscriber
|
6
6
|
end
|
@@ -9,6 +9,6 @@ describe MultipleMan::Subscriber do
|
|
9
9
|
it "should register itself" do
|
10
10
|
MultipleMan::Subscribers::Registry.should_receive(:register).with(instance_of(MultipleMan::Subscribers::ModelSubscriber))
|
11
11
|
MockClass.subscribe fields: [:foo, :bar]
|
12
|
-
end
|
12
|
+
end
|
13
13
|
end
|
14
|
-
end
|
14
|
+
end
|
@@ -5,13 +5,17 @@ describe MultipleMan::Subscribers::Base do
|
|
5
5
|
end
|
6
6
|
|
7
7
|
specify "routing_key should be the model name and a wildcard" do
|
8
|
-
described_class.new(MockClass).routing_key.should
|
8
|
+
described_class.new(MockClass).routing_key.should =~ /\.MockClass\.\#$/
|
9
9
|
end
|
10
10
|
|
11
11
|
specify "queue name should be the app name + class" do
|
12
12
|
MultipleMan.configure do |config|
|
13
13
|
config.app_name = "test"
|
14
14
|
end
|
15
|
-
described_class.new(MockClass).queue_name.should
|
15
|
+
described_class.new(MockClass).queue_name.should =~ /\.test\.MockClass$/
|
16
|
+
end
|
17
|
+
|
18
|
+
specify "it should be alright to use a string for a class name" do
|
19
|
+
described_class.new("MockClass").routing_key.should =~ /\.MockClass\.\#$/
|
16
20
|
end
|
17
21
|
end
|
@@ -5,6 +5,13 @@ describe MultipleMan::Subscribers::ModelSubscriber do
|
|
5
5
|
|
6
6
|
end
|
7
7
|
|
8
|
+
describe "initialize" do
|
9
|
+
it "should listen to the object passed in for to" do
|
10
|
+
subscriber = described_class.new(MockClass, to: 'PublishedClass')
|
11
|
+
expect(subscriber.klass).to eq("PublishedClass")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
8
15
|
describe "create" do
|
9
16
|
it "should create a new model" do
|
10
17
|
mock_object = MockClass.new
|
@@ -40,4 +47,4 @@ describe MultipleMan::Subscribers::ModelSubscriber do
|
|
40
47
|
described_class.new(MockClass, {}).destroy({id: 1})
|
41
48
|
end
|
42
49
|
end
|
43
|
-
end
|
50
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: multiple_man
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Brunner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-05-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -171,6 +171,7 @@ files:
|
|
171
171
|
- lib/multiple_man/identity.rb
|
172
172
|
- lib/multiple_man/listeners/listener.rb
|
173
173
|
- lib/multiple_man/listeners/seeder_listener.rb
|
174
|
+
- lib/multiple_man/mixins/listener.rb
|
174
175
|
- lib/multiple_man/mixins/publisher.rb
|
175
176
|
- lib/multiple_man/mixins/subscriber.rb
|
176
177
|
- lib/multiple_man/model_populator.rb
|