multiple_man 0.2.6 → 0.2.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 27a4153d5c70fd0caf82e6a884969c3641ffc3a4
4
- data.tar.gz: ea850e1b573bbd9f1a92123b728e7b5a00b1682e
3
+ metadata.gz: 9ca5356191de47f8a4e08e52e9a568eeebdf9048
4
+ data.tar.gz: 79a7e0d2c6079170cc2a81541980c603f595a366
5
5
  SHA512:
6
- metadata.gz: a0644fa501b3480a09cab9ff7868286ba7199b20187e1c04cc0c9ca1fbd06e2632f842f32d42a275b1c1c43b111d0ba99832b3a44efd2d4ab9dce0351a532fd0
7
- data.tar.gz: bebf010a39b98ff1461cd26c357e189e74959efe07a31ad2a8fc310772f33af011979bd97e75aa96d905c7de0a94a4de480c6d777b4969e8ccc05d0076b88bb5
6
+ metadata.gz: 4064bbb0ab486a17f5315302a5edbbc79e191724bc65ad74298f4b7a1edbcaac992f5f7862979eaebf8f7a5b711e3b1da5173fc6ddcd285bcf4e5cdf86bc62e1
7
+ data.tar.gz: 56fcab0f9ab729ff37240d419bb030aad4e8275a3626cdee62e494d506b542bb6c9276bbb014077c4a712789f0ec35d8006d4f1fc24f04ffc3bfeb8f740cf432
data/README.md CHANGED
@@ -1,6 +1,19 @@
1
1
  # MultipleMan
2
2
 
3
- TODO: Write a gem description
3
+ [![Code Climate](https://codeclimate.com/github/influitive/multiple_man.png)](https://codeclimate.com/github/influitive/multiple_man)
4
+
5
+ [![CircleCI](https://circleci.com/gh/influitive/multiple_man.png)](https://circleci.com/gh/influitive/multiple_man)
6
+
7
+ MultipleMan synchronizes your ActiveRecord models between Rails
8
+ apps, using RabbitMQ to send messages between your applications.
9
+ It's heavily inspired by Promiscuous, but differs in a few ways:
10
+
11
+ - MultipleMan makes a hard assumption that you're using
12
+ ActiveRecord for your models. This simplifies how models
13
+ are sychronized, which offers a few benefits like:
14
+ - Transactions are fully supported in MultipleMan. Records
15
+ which aren't committed fully to the database won't be sent
16
+ to your message queue.
4
17
 
5
18
  ## Installation
6
19
 
@@ -18,7 +31,65 @@ Or install it yourself as:
18
31
 
19
32
  ## Usage
20
33
 
21
- TODO: Write usage instructions here
34
+ ### Configuration
35
+
36
+ MultipleMan can be configured (ideally inside an initializer) by
37
+ calling MultipleMan.configure like so:
38
+
39
+ MultipleMan.configure do |config|
40
+ # A connection string to your local server. Defaults to localhost.
41
+ config.connection = "amqp://example.com"
42
+
43
+ # The topic name to push to. If you have multiple
44
+ # multiple man apps, this should be unique per application. Publishers
45
+ # and subscribers should always use the same topic.
46
+ config.topic_name = "multiple_man"
47
+
48
+ # The application name (used for subscribers) - defaults to your
49
+ # Rails application name if you're using rails
50
+ config.app_name = "MyApp"
51
+
52
+ # Where you want to log errors to. Should be an instance of Logger
53
+ # Defaults to the Rails logger (for Rails) or STDOUT otherwise.
54
+ config.logger = Logger.new(STDOUT)
55
+ end
56
+
57
+ ### Publishing models
58
+
59
+ To publish a model, include the following in your model definition:
60
+
61
+ class Widget < ActiveRecord::Base
62
+ include MultipleMan::Publisher
63
+ publish fields: [:id, :name, :type], identifier: :code
64
+ end
65
+
66
+ You can use the following options when publishing:
67
+
68
+ - `fields` - An array of all the fields you want to send to the message queue. These
69
+ can be either ActiveRecord attributes or methods on your model.
70
+ - `identifier` - Either a symbol or a proc used by MultipleMan to identify your model.
71
+ `id` is used by default and is generally fine, unless you're working in a multi-tenant
72
+ environment where ids may be shared between two different models of the same class.
73
+
74
+ ### Subscribing to models
75
+
76
+ You can subscribe to a model as follows (in a seperate consumer app):
77
+
78
+ class Widget < ActiveRecord::Base
79
+ include MultipleMan::Subscriber
80
+ end
81
+
82
+ Currently, there's a few assumptions made of subscriber models:
83
+
84
+ - They need to have all of the fields that are published.
85
+ - They need an additional field, called `multiple_man_identifier`, that is the
86
+ same type as what's passed from the publisher side (just an integer if you're using
87
+ id)
88
+
89
+ Once you've set up your subscribers, you'll need to run a background worker to manage
90
+ the subscription process. Just run the following:
91
+
92
+ rake multiple_man:worker
22
93
 
23
94
  ## Contributing
24
95
 
@@ -27,3 +98,27 @@ TODO: Write usage instructions here
27
98
  3. Commit your changes (`git commit -am 'Add some feature'`)
28
99
  4. Push to the branch (`git push origin my-new-feature`)
29
100
  5. Create new Pull Request
101
+
102
+ ## License
103
+
104
+ The MIT License (MIT)
105
+
106
+ Copyright (c) 2014 Influitive Corporation
107
+
108
+ Permission is hereby granted, free of charge, to any person obtaining a copy
109
+ of this software and associated documentation files (the "Software"), to deal
110
+ in the Software without restriction, including without limitation the rights
111
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
112
+ copies of the Software, and to permit persons to whom the Software is
113
+ furnished to do so, subject to the following conditions:
114
+
115
+ The above copyright notice and this permission notice shall be included in
116
+ all copies or substantial portions of the Software.
117
+
118
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
119
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
120
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
121
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
122
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
123
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
124
+ THE SOFTWARE.
@@ -8,7 +8,7 @@ module MultipleMan
8
8
 
9
9
  self.record = record
10
10
  self.fields = fields
11
- self.identifier = identifier
11
+ self.identifier = identifier || :id
12
12
  end
13
13
 
14
14
  def data
@@ -21,13 +21,10 @@ module MultipleMan
21
21
  end
22
22
 
23
23
  def identifier_for_record
24
- puts identifier.class
25
- if identifier.class == Symbol
26
- record.send(identifier)
27
- elsif identifier.class == Proc
24
+ if identifier.class == Proc
28
25
  identifier.call(record)
29
26
  else
30
- record.id
27
+ record.send(identifier)
31
28
  end
32
29
  end
33
30
 
@@ -5,13 +5,18 @@ module MultipleMan
5
5
  self.topic_name = "multiple_man"
6
6
  self.app_name = Rails.application.class.parent.to_s if defined?(Rails)
7
7
  self.enabled = true
8
+ self.channel_pool_size = 5
8
9
  end
9
10
 
10
11
  def logger
11
12
  @logger ||= defined?(Rails) ? Rails.logger : Logger.new(STDOUT)
12
13
  end
13
14
 
14
- attr_accessor :topic_name, :app_name, :connection, :enabled
15
+ def on_error(&block)
16
+ @error_handler = block
17
+ end
18
+
19
+ attr_accessor :topic_name, :app_name, :connection, :enabled, :channel_pool_size, :error_handler
15
20
  attr_writer :logger
16
21
  end
17
22
 
@@ -1,4 +1,5 @@
1
1
  require 'bunny'
2
+ require 'connection_pool'
2
3
 
3
4
  module MultipleMan
4
5
  class Connection
@@ -11,13 +12,14 @@ module MultipleMan
11
12
  end
12
13
  end
13
14
 
14
- def self.open_channel
15
- Thread.current[:multiple_man_channel] ||= connection.create_channel
15
+ def self.channel_pool
16
+ @channel_pool ||= ConnectionPool.new(size: 25, timeout: 5) { connection.create_channel }
16
17
  end
17
18
 
18
19
  def self.connect
19
- channel = open_channel
20
- yield new(channel) if block_given?
20
+ channel_pool.with do |channel|
21
+ yield new(channel) if block_given?
22
+ end
21
23
  end
22
24
 
23
25
  def initialize(channel)
@@ -36,6 +36,7 @@ module MultipleMan
36
36
  subscription.send(operation, JSON.parse(payload).with_indifferent_access)
37
37
  rescue Exception => ex
38
38
  MultipleMan.logger.error " Error - #{ex.message}"
39
+ MultipleMan.error(ex)
39
40
  else
40
41
  MultipleMan.logger.info " Successfully processed!"
41
42
  end
@@ -21,9 +21,15 @@ module MultipleMan
21
21
 
22
22
  def push_record(connection, record)
23
23
  data = record_data(record)
24
- routing_key = RoutingKey.new(record, operation).to_s
24
+ routing_key = RoutingKey.new(record_type(record), operation).to_s
25
25
  MultipleMan.logger.info(" Record Data: #{data} | Routing Key: #{routing_key}")
26
26
  connection.topic.publish(data, routing_key: routing_key)
27
+ rescue Exception => ex
28
+ MultipleMan.error(ex)
29
+ end
30
+
31
+ def record_type(record)
32
+ options[:as] || record.class.name
27
33
  end
28
34
 
29
35
  def record_data(record)
@@ -8,21 +8,17 @@ module MultipleMan
8
8
  end
9
9
 
10
10
  def to_s
11
- "#{topic_name}.#{klass.name}.#{operation}"
11
+ "#{topic_name}.#{klass}.#{operation}"
12
12
  end
13
13
 
14
14
  attr_reader :operation
15
- attr_reader :klass
15
+ attr_accessor :klass
16
16
 
17
17
  def operation=(value)
18
18
  raise "Operation #{value} is not recognized" unless ALLOWED_OPERATIONS.include?(value)
19
19
  @operation = value
20
20
  end
21
21
 
22
- def klass=(value)
23
- @klass = (value.is_a?(Class) ? value : value.class)
24
- end
25
-
26
22
  private
27
23
  def topic_name
28
24
  MultipleMan.configuration.topic_name
@@ -1,3 +1,3 @@
1
1
  module MultipleMan
2
- VERSION = "0.2.6"
2
+ VERSION = "0.2.7"
3
3
  end
data/lib/multiple_man.rb CHANGED
@@ -14,14 +14,22 @@ module MultipleMan
14
14
  require 'multiple_man/listener'
15
15
 
16
16
  def self.logger
17
- MultipleMan.configuration.logger
17
+ configuration.logger
18
18
  end
19
19
 
20
20
  def self.disable!
21
- MultipleMan.configuration.enabled = false
21
+ configuration.enabled = false
22
22
  end
23
23
 
24
24
  def self.enable!
25
- MultipleMan.configuration.enabled = true
25
+ configuration.enabled = true
26
+ end
27
+
28
+ def self.error(ex)
29
+ if configuration.error_handler
30
+ configuration.error_handler.call(ex)
31
+ else
32
+ raise ex
33
+ end
26
34
  end
27
35
  end
data/multiple_man.gemspec CHANGED
@@ -19,8 +19,9 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_development_dependency "bundler", "~> 1.3"
22
- spec.add_development_dependency "rake"
23
- spec.add_development_dependency "rspec"
24
- spec.add_runtime_dependency "bunny"
25
- spec.add_runtime_dependency "activesupport"
22
+ spec.add_development_dependency "rake", '~> 10.1.0'
23
+ spec.add_development_dependency "rspec", '~> 2.14.1'
24
+ spec.add_runtime_dependency "bunny", '>= 1.0'
25
+ spec.add_runtime_dependency "activesupport", '>= 3.0'
26
+ spec.add_runtime_dependency "connection_pool", ">= 1.2"
26
27
  end
@@ -9,7 +9,7 @@ describe MultipleMan::Connection do
9
9
  it "should open a connection and a channel" do
10
10
  mock_bunny.should_receive(:start)
11
11
  Bunny.should_receive(:new).and_return(mock_bunny)
12
- mock_bunny.should_receive(:create_channel).and_return(mock_channel)
12
+ mock_bunny.should_receive(:create_channel).exactly(25).times
13
13
 
14
14
  described_class.connect
15
15
  end
@@ -1,15 +1,14 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe MultipleMan::ModelPublisher do
4
- let(:channel_stub) { double(Bunny::Channel, topic: topic_stub, close: nil)}
4
+ let(:channel_stub) { double(Bunny::Channel, topic: topic_stub)}
5
5
  let(:topic_stub) { double(Bunny::Exchange, publish: nil) }
6
6
 
7
7
  before {
8
+ MultipleMan::Connection.stub(:connect).and_yield(channel_stub)
8
9
  MultipleMan.configure do |config|
9
10
  config.topic_name = "app"
10
11
  end
11
-
12
- MultipleMan::Connection.stub(:open_channel).and_return(channel_stub)
13
12
  }
14
13
 
15
14
  class MockObject
@@ -29,15 +28,18 @@ describe MultipleMan::ModelPublisher do
29
28
  subject { described_class.new(:create, fields: [:foo]) }
30
29
 
31
30
  describe "after_commit" do
32
- it "should queue the update in the correct topic" do
33
- channel_stub.should_receive(:topic).with("app")
34
- described_class.new(:create, fields: [:foo]).after_commit(MockObject.new)
35
- end
36
31
  it "should send the jsonified version of the model to the correct routing key" do
37
32
  MultipleMan::AttributeExtractor.any_instance.should_receive(:to_json).and_return('{"id":10,"data":{"foo": "bar"}}')
38
33
  topic_stub.should_receive(:publish).with('{"id":10,"data":{"foo": "bar"}}', routing_key: "app.MockObject.create")
39
34
  described_class.new(:create, fields: [:foo]).after_commit(MockObject.new)
40
35
  end
36
+
37
+ it "should call the error handler on error" do
38
+ ex = Exception.new("Bad stuff happened")
39
+ topic_stub.stub(:publish) { raise ex }
40
+ MultipleMan.should_receive(:error).with(ex)
41
+ described_class.new(:create, fields: [:foo]).after_commit(MockObject.new)
42
+ end
41
43
  end
42
44
 
43
45
  end
@@ -11,7 +11,7 @@ describe MultipleMan::RoutingKey do
11
11
  end
12
12
 
13
13
  describe "to_s" do
14
- subject { described_class.new(MockObject.new, operation).to_s }
14
+ subject { described_class.new("MockObject", operation).to_s }
15
15
 
16
16
  context "creating" do
17
17
  let(:operation) { :create }
@@ -29,7 +29,7 @@ describe MultipleMan::RoutingKey do
29
29
  end
30
30
 
31
31
  context "not specified" do
32
- subject { described_class.new(MockObject.new).to_s }
32
+ subject { described_class.new("MockObject").to_s }
33
33
  it { should == "app.MockObject.#" }
34
34
  end
35
35
  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.2.6
4
+ version: 0.2.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Brunner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-03 00:00:00.000000000 Z
11
+ date: 2014-01-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -28,58 +28,72 @@ dependencies:
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ~>
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: 10.1.0
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ~>
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: 10.1.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ~>
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: 2.14.1
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ~>
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: 2.14.1
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: bunny
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - '>='
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: '1.0'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - '>='
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: '1.0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: activesupport
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - '>='
74
74
  - !ruby/object:Gem::Version
75
- version: '0'
75
+ version: '3.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: connection_pool
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '1.2'
76
90
  type: :runtime
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
94
  - - '>='
81
95
  - !ruby/object:Gem::Version
82
- version: '0'
96
+ version: '1.2'
83
97
  description: MultipleMan syncs changes to ActiveRecord models via AMQP
84
98
  email:
85
99
  - ryan@influitive.com