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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f395c0bc53fe2aab8ffde4426d0013cf9a0ee0ba
4
- data.tar.gz: ce5554a9a4e526be5216796f0b34cecf7d7a1fa0
3
+ metadata.gz: d8c65c40b50ae40908c59ef6c0cd66d724f461ea
4
+ data.tar.gz: 3403383d7f31f579dcc4b450a65f5f482ea7c5cb
5
5
  SHA512:
6
- metadata.gz: 163c9dad1a4ee94f7e74a442721b2be649e64af233a0105d4f885695effd21c58eb4933053f5a9abdb01a1570ad1fc33ca1318c6d709753012c363c6b2843cda
7
- data.tar.gz: 87c49def2141da2c11297facc6618c93984f31aceacbcbd40ab606ce951fcdc408d6db1e472a62fc09bcb4430dd6e954b54d46ceda053e670e69f49108381d35
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
  [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/influitive/multiple_man/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
205
-
@@ -8,7 +8,7 @@ module MultipleMan
8
8
  def self.connect
9
9
  channel = connection.create_channel
10
10
  yield new(channel) if block_given?
11
- rescue Bunny::ConnectionClosedError, Bunny::NetworkErrorWrapper
11
+ rescue Bunny::Exception
12
12
  reset!
13
13
  retry
14
14
  ensure
@@ -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
@@ -10,4 +10,4 @@ module MultipleMan
10
10
  end
11
11
  end
12
12
  end
13
- end
13
+ 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
- populate_field(field, data[field])
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
- # Attempt to populate source id if id is specified
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
- all_records(records) do |record|
15
- push_record(connection, record, operation)
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.name}"
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
- super(klass)
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
- klass.where(find_conditions(id)).first || klass.new
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
@@ -9,4 +9,4 @@ module MultipleMan::Subscribers
9
9
  end
10
10
  end
11
11
  end
12
- end
12
+ end
@@ -14,8 +14,9 @@ namespace :multiple_man do
14
14
 
15
15
  listener.start
16
16
 
17
- while(true)
18
- sleep 10
19
- end
17
+ Signal.trap("INT") { puts "received INT"; exit }
18
+ Signal.trap("QUIT") { puts "received QUIT"; exit }
19
+
20
+ sleep
20
21
  end
21
22
  end
@@ -1,3 +1,3 @@
1
1
  module MultipleMan
2
- VERSION = "0.5.21"
2
+ VERSION = "0.6.0"
3
3
  end
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
@@ -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 == "app.MockClass.#"
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 == "app.test.MockClass"
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.5.21
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-03-18 00:00:00.000000000 Z
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