promiscuous 0.1 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -23,8 +23,8 @@ Example
23
23
 
24
24
  ```ruby
25
25
  # initializer
26
- Promiscuous::AMQP.configure(:backend => :bunny, :app => 'crowdtap', :logger => Rails.logger,
27
- :server_uri => 'amqp://user:password@host:port/vhost')
26
+ Promiscuous::AMQP.configure(:app => 'crowdtap',
27
+ :server_uri => 'amqp://user:password@host:port/vhost')
28
28
 
29
29
  # publisher
30
30
  class ModelPublisher < Promiscuous::Publisher::Mongoid
@@ -38,10 +38,9 @@ end
38
38
 
39
39
  ```ruby
40
40
  # initializer
41
- Promiscuous::AMQP.configure(:backend => :rubyamqp, :app => 'sniper', :logger => Rails.logger,
42
- :server_uri => 'amqp://user:password@host:port/vhost',
43
- :queue_options => {:durable => true, :arguments => {'x-ha-policy' => 'all'}},
44
- :error_handler => some_proc)
41
+ Promiscuous::AMQP.configure(:app => 'sniper',
42
+ :server_uri => 'amqp://user:password@host:port/vhost',
43
+ :error_handler => some_proc)
45
44
 
46
45
  # subscriber
47
46
  class ModelSubscriber < Promiscuous::Subscriber::Mongoid
@@ -53,7 +52,7 @@ end
53
52
 
54
53
  ### Starting the subscriber worker
55
54
 
56
- rake promiscuous:run[./path/to/promiscuous_initializer.rb]
55
+ rake promiscuous:replicate
57
56
 
58
57
  How does it work ?
59
58
  ------------------
@@ -3,20 +3,24 @@ module Promiscuous
3
3
  module Bunny
4
4
  mattr_accessor :connection
5
5
 
6
- def self.configure(options)
6
+ def self.connect
7
7
  require 'bunny'
8
- self.connection = ::Bunny.new(options[:server_uri])
8
+ self.connection = ::Bunny.new(Promiscuous::Config.server_uri)
9
9
  self.connection.start
10
10
  end
11
11
 
12
+ def self.disconnect
13
+ self.connection.stop
14
+ end
15
+
12
16
  def self.publish(msg)
13
- AMQP.info "[publish] #{msg[:key]} -> #{msg[:payload]}"
17
+ Promiscuous.info "[publish] #{msg[:key]} -> #{msg[:payload]}"
14
18
  exchange = connection.exchange('promiscuous', :type => :topic, :durable => true)
15
19
  exchange.publish(msg[:payload], :key => msg[:key], :persistent => true)
16
20
  end
17
21
 
18
- def self.close
19
- self.connection.stop
22
+ def self.subscribe(options={}, &block)
23
+ raise "Cannot subscribe with bunny"
20
24
  end
21
25
  end
22
26
  end
@@ -1,19 +1,16 @@
1
1
  module Promiscuous
2
2
  module AMQP
3
3
  module Null
4
- def self.configure(options)
4
+ def self.connect
5
5
  end
6
6
 
7
- def self.publish(msg)
8
- end
9
-
10
- def self.subscribe(options={}, &block)
7
+ def self.disconnect
11
8
  end
12
9
 
13
- def self.clear
10
+ def self.publish(msg)
14
11
  end
15
12
 
16
- def self.close
13
+ def self.subscribe(options={}, &block)
17
14
  end
18
15
  end
19
16
  end
@@ -3,16 +3,11 @@ module Promiscuous
3
3
  module RubyAMQP
4
4
  mattr_accessor :channel, :queue_options
5
5
 
6
- def self.configure(options)
6
+ def self.connect
7
7
  require 'amqp'
8
- connection = ::AMQP.connect(build_connection_options(options))
9
- self.channel = ::AMQP::Channel.new(connection)
10
- self.queue_options = options[:queue_options] || {}
11
- end
12
8
 
13
- def self.build_connection_options(options)
14
- if options[:server_uri]
15
- uri = URI.parse(options[:server_uri])
9
+ amqp_options = if Promiscuous::Config.server_uri
10
+ uri = URI.parse(Promiscuous::Config.server_uri)
16
11
  raise "Please use amqp://user:password@host:port/vhost" if uri.scheme != 'amqp'
17
12
 
18
13
  {
@@ -24,6 +19,14 @@ module Promiscuous
24
19
  :vhost => uri.path.empty? ? "/" : uri.path,
25
20
  }
26
21
  end
22
+
23
+ connection = ::AMQP.connect(amqp_options)
24
+ self.channel = ::AMQP::Channel.new(connection)
25
+ self.queue_options = Promiscuous::Config.queue_options
26
+ end
27
+
28
+ def self.disconnect
29
+ channel.close
27
30
  end
28
31
 
29
32
  def self.subscribe(options={}, &block)
@@ -34,20 +37,16 @@ module Promiscuous
34
37
  exchange = channel.topic('promiscuous', :durable => true)
35
38
  bindings.each do |binding|
36
39
  queue.bind(exchange, :routing_key => binding)
37
- AMQP.info "[bind] #{queue_name} -> #{binding}"
40
+ Promiscuous.info "[bind] #{queue_name} -> #{binding}"
38
41
  end
39
42
  queue.subscribe(:ack => true, &block)
40
43
  end
41
44
 
42
45
  def self.publish(msg)
43
- AMQP.info "[publish] #{msg[:key]} -> #{msg[:payload]}"
46
+ Promiscuous.info "[publish] #{msg[:key]} -> #{msg[:payload]}"
44
47
  exchange = channel.topic('promiscuous', :durable => true)
45
48
  exchange.publish(msg[:payload], :routing_key => msg[:key], :persistent => true)
46
49
  end
47
-
48
- def self.close
49
- channel.close
50
- end
51
50
  end
52
51
  end
53
52
  end
@@ -1,34 +1,15 @@
1
1
  require 'promiscuous/amqp/bunny'
2
- require 'promiscuous/amqp/fake'
3
- require 'promiscuous/amqp/ruby-amqp'
2
+ require 'promiscuous/amqp/rubyamqp'
4
3
  require 'promiscuous/amqp/null'
5
4
 
6
5
  module Promiscuous
7
6
  module AMQP
8
- mattr_accessor :backend, :app, :logger, :error_handler
9
-
10
- def self.configure(options={}, &block)
11
- options.symbolize_keys!
12
-
13
- self.backend = "Promiscuous::AMQP::#{options[:backend].to_s.camelize.gsub(/amqp/, 'AMQP')}".constantize
14
- self.backend.configure(options, &block)
15
- self.app = options[:app]
16
- self.logger = options[:logger] || Logger.new(STDOUT).tap { |l| l.level = Logger::WARN }
17
- self.error_handler = options[:error_handler]
18
- self
19
- end
20
-
21
7
  class << self
22
- [:info, :error, :warn, :fatal].each do |level|
23
- define_method(level) do |msg|
24
- self.logger.__send__(level, "[AMQP] #{msg}")
25
- end
8
+ def backend
9
+ Promiscuous::Config.backend
26
10
  end
27
- end
28
11
 
29
- # TODO Evaluate the performance hit of method_missing
30
- def self.method_missing(method, *args, &block)
31
- self.backend.__send__(method, *args, &block)
12
+ delegate :connect, :disconnect, :publish, :subscribe, :to => :backend
32
13
  end
33
14
  end
34
15
  end
@@ -0,0 +1,20 @@
1
+ module Promiscuous
2
+ module Config
3
+ mattr_accessor :app, :logger, :error_handler, :backend, :server_uri, :queue_options
4
+
5
+ def self.backend=(value)
6
+ @@backend = "Promiscuous::AMQP::#{value.to_s.camelize.gsub(/amqp/, 'AMQP')}".constantize
7
+ end
8
+
9
+ def self.configure(&block)
10
+ class_variables.each { |var| class_variable_set(var, nil) }
11
+
12
+ block.call(self)
13
+ self.backend ||= defined?(EventMachine) && EventMachine.reactor_running? ? :rubyamqp : :bunny
14
+ self.logger ||= defined?(Rails) ? Rails.logger : Logger.new(STDERR).tap { |l| l.level = Logger::WARN }
15
+ self.queue_options ||= {:durable => true, :arguments => {'x-ha-policy' => 'all'}}
16
+
17
+ Promiscuous::AMQP.connect
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ module Promiscuous
2
+ module Loader
3
+ def self.load_descriptors(descriptors=[:publishers, :subscribers])
4
+ [descriptors].flatten.each do |descriptor|
5
+ dir, file_matcher = case descriptor
6
+ when :publishers
7
+ require 'promiscuous/publisher'
8
+ # TODO Cleanup publishers
9
+ %w(publishers **_publisher.rb)
10
+ when :subscribers
11
+ require 'promiscuous/subscriber'
12
+ Promiscuous::Subscriber.subscribers.clear
13
+ %w(subscribers **_subscriber.rb)
14
+ end
15
+
16
+ Dir[Rails.root.join('app', dir, file_matcher)].map do |file|
17
+ File.basename(file, ".rb").camelize.constantize
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,12 +1,18 @@
1
- namespace :replicable do
1
+ namespace :promiscuous do
2
2
  desc 'Run the subscribers worker'
3
- task :run, [:initializer] => :environment do |t, args|
4
- require 'replicable/worker'
3
+ task :replicate => :environment do |t|
4
+ require 'promiscuous/worker'
5
5
  require 'eventmachine'
6
6
  require 'em-synchrony'
7
+
7
8
  EM.synchrony do
8
- load args.initializer
9
- Replicable::Worker.run
9
+ Promiscuous::Loader.load_descriptors :subscribers
10
+ Promiscuous::AMQP.disconnect
11
+ Promiscuous::Config.backend = :rubyamqp
12
+ Promiscuous::AMQP.connect
13
+
14
+ Promiscuous::Worker.replicate
15
+ puts "Replicating with #{Promiscuous::Subscriber.subscribers.count} subscribers"
10
16
  end
11
17
  end
12
18
  end
@@ -3,24 +3,10 @@ module Promiscuous
3
3
  rake_tasks { load 'promiscuous/railtie/replicate.rake' }
4
4
 
5
5
  initializer 'load promiscuous' do
6
- # TODO clean that up
7
6
  config.after_initialize do
8
- Dir[Rails.root.join('app', 'publishers', '**_publisher.rb')].map do |file|
9
- file.split('/')[-1].split('.')[0].camelize.constantize
10
- end
11
- ActionDispatch::Reloader.to_prepare do
12
- Dir[Rails.root.join('app', 'publishers', '**_publisher.rb')].map do |file|
13
- file.split('/')[-1].split('.')[0].camelize.constantize
14
- end
15
- end
16
-
17
- Dir[Rails.root.join('app', 'subscribers', '**_subscriber.rb')].map do |file|
18
- file.split('/')[-1].split('.')[0].camelize.constantize
19
- end
7
+ Promiscuous::Loader.load_descriptors(:publishers)
20
8
  ActionDispatch::Reloader.to_prepare do
21
- Dir[Rails.root.join('app', 'subscribers', '**_subscriber.rb')].map do |file|
22
- file.split('/')[-1].split('.')[0].camelize.constantize
23
- end
9
+ Promiscuous::Loader.load_descriptors(:publishers)
24
10
  end
25
11
  end
26
12
  end
@@ -3,30 +3,14 @@ require 'promiscuous/subscriber/envelope'
3
3
  module Promiscuous::Subscriber::AMQP
4
4
  extend ActiveSupport::Concern
5
5
 
6
- mattr_accessor :subscribers
7
- self.subscribers = {}
8
-
9
- def self.subscriber_for(payload)
10
- origin = payload.is_a?(Hash) ? payload['__amqp__'] : nil
11
- if origin
12
- unless subscribers.has_key?(origin)
13
- raise "FATAL: Unknown binding: '#{origin}'"
14
- end
15
- subscribers[origin]
16
- end
6
+ def self.subscriber_key(payload)
7
+ payload.is_a?(Hash) ? payload['__amqp__'] : nil
17
8
  end
18
9
 
19
10
  module ClassMethods
20
11
  def subscribe(options)
21
12
  super
22
-
23
- subscribers = Promiscuous::Subscriber::AMQP.subscribers
24
- from = options[:from]
25
-
26
- if subscribers.has_key?(from)
27
- raise "The subscriber '#{subscribers[from]}' already listen on '#{from}'"
28
- end
29
- subscribers[from] = self
13
+ Promiscuous::Subscriber.bind(options[:from], self)
30
14
  end
31
15
  end
32
16
  end
@@ -5,7 +5,7 @@ module Promiscuous::Subscriber::Mongoid::Upsert
5
5
  begin
6
6
  super
7
7
  rescue Mongoid::Errors::DocumentNotFound
8
- Promiscuous::AMQP.warn "[receive] upserting #{payload}"
8
+ Promiscuous.warn "[receive] upserting #{payload}"
9
9
  klass.new.tap { |o| o.id = id }
10
10
  end
11
11
  end
@@ -3,12 +3,32 @@ module Promiscuous::Subscriber
3
3
  require 'promiscuous/subscriber/mongoid'
4
4
  require 'promiscuous/subscriber/amqp'
5
5
 
6
+ mattr_accessor :subscribers
7
+ self.subscribers = {}
8
+
9
+ def self.bind(key, subscriber)
10
+ if self.subscribers.has_key?(key)
11
+ raise "The subscriber '#{self.subscribers[key]}' already listen on '#{key}'"
12
+ end
13
+ self.subscribers[key] = subscriber
14
+ end
15
+
16
+ def self.get_subscriber(payload, options={})
17
+ key = Promiscuous::Subscriber::AMQP.subscriber_key(payload)
18
+
19
+ if key
20
+ raise "FATAL: Unknown binding: '#{key}'" unless self.subscribers.has_key?(key)
21
+ self.subscribers[key]
22
+ else
23
+ Promiscuous::Subscriber::Base
24
+ end
25
+ end
26
+
6
27
  def self.process(payload, options={})
7
- subscriber = Promiscuous::Subscriber::AMQP.subscriber_for(payload)
8
- return payload if subscriber.nil?
28
+ subscriber_klass = get_subscriber(payload)
9
29
 
10
- sub = subscriber.new(options.merge(:payload => payload))
11
- sub.process if sub.respond_to?(:process)
30
+ sub = subscriber_klass.new(options.merge(:payload => payload))
31
+ sub.process
12
32
  sub.instance
13
33
  end
14
34
  end
@@ -1,3 +1,3 @@
1
1
  module Promiscuous
2
- VERSION = '0.1'
2
+ VERSION = '0.5'
3
3
  end
@@ -1,25 +1,42 @@
1
1
  module Promiscuous
2
2
  module Worker
3
- def self.run
4
- queue_name = "#{Promiscuous::AMQP.app}.promiscuous"
5
-
3
+ def self.replicate
6
4
  stop = false
7
- Promiscuous::AMQP.subscribe(:queue_name => queue_name,
8
- :bindings => Promiscuous::Subscriber::AMQP.subscribers.keys) do |metadata, payload|
5
+ lock = Mutex.new
6
+
7
+ %w(SIGTERM SIGINT).each do |signal|
8
+ Signal.trap(signal) do
9
+ lock.synchronize do
10
+ stop = true
11
+ EM.stop
12
+ end
13
+ end
14
+ end
15
+
16
+ Promiscuous::AMQP.subscribe(subscribe_options) do |metadata, payload|
9
17
  begin
10
- unless stop
11
- Promiscuous::AMQP.info "[receive] #{payload}"
12
- Promiscuous::Subscriber.process(JSON.parse(payload))
13
- metadata.ack
18
+ lock.synchronize do
19
+ unless stop
20
+ Promiscuous.info "[receive] #{payload}"
21
+ Promiscuous::Subscriber.process(JSON.parse(payload))
22
+ metadata.ack
23
+ end
14
24
  end
15
25
  rescue Exception => e
16
26
  e = Promiscuous::Subscriber::Error.new(e, payload)
27
+
17
28
  stop = true
18
- Promiscuous::AMQP.close
19
- Promiscuous::AMQP.error "[receive] FATAL #{e}"
20
- Promiscuous::AMQP.error_handler.call(e) if Promiscuous::AMQP.error_handler
29
+ Promiscuous::AMQP.disconnect
30
+ Promiscuous.error "[receive] FATAL #{e}"
31
+ Promiscuous::Config.error_handler.call(e) if Promiscuous::Config.error_handler
21
32
  end
22
33
  end
23
34
  end
35
+
36
+ def self.subscribe_options
37
+ queue_name = "#{Promiscuous::Config.app}.promiscuous"
38
+ bindings = Promiscuous::Subscriber.subscribers.keys
39
+ {:queue_name => queue_name, :bindings => bindings}
40
+ end
24
41
  end
25
42
  end
data/lib/promiscuous.rb CHANGED
@@ -1,5 +1,19 @@
1
1
  require 'active_support/core_ext'
2
+ require 'promiscuous/config'
2
3
  require 'promiscuous/amqp'
3
- require 'promiscuous/publisher'
4
- require 'promiscuous/subscriber'
4
+ require 'promiscuous/loader'
5
5
  require 'promiscuous/railtie' if defined?(Rails)
6
+
7
+ module Promiscuous
8
+ class << self
9
+ def configure(&block)
10
+ Config.configure(&block)
11
+ end
12
+
13
+ [:info, :error, :warn, :fatal].each do |level|
14
+ define_method(level) do |msg|
15
+ Promiscuous::Config.logger.__send__(level, "[promiscuous] #{msg}")
16
+ end
17
+ end
18
+ end
19
+ end
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.1'
4
+ version: '0.5'
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-08-11 00:00:00.000000000 Z
13
+ date: 2012-08-12 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: mongoid
@@ -100,39 +100,40 @@ executables: []
100
100
  extensions: []
101
101
  extra_rdoc_files: []
102
102
  files:
103
- - lib/promiscuous/amqp/fake.rb
104
- - lib/promiscuous/amqp/null.rb
105
- - lib/promiscuous/amqp/bunny.rb
106
- - lib/promiscuous/amqp/ruby-amqp.rb
107
- - lib/promiscuous/railtie/replicate.rake
108
- - lib/promiscuous/publisher/mongoid/embedded.rb
109
- - lib/promiscuous/publisher/mongoid/root.rb
110
- - lib/promiscuous/publisher/base.rb
103
+ - lib/promiscuous/version.rb
111
104
  - lib/promiscuous/publisher/class_bind.rb
112
- - lib/promiscuous/publisher/envelope.rb
113
- - lib/promiscuous/publisher/mongoid.rb
114
- - lib/promiscuous/publisher/polymorphic.rb
115
105
  - lib/promiscuous/publisher/attributes.rb
106
+ - lib/promiscuous/publisher/mongoid.rb
116
107
  - lib/promiscuous/publisher/generic.rb
108
+ - lib/promiscuous/publisher/mongoid/root.rb
109
+ - lib/promiscuous/publisher/mongoid/embedded.rb
110
+ - lib/promiscuous/publisher/envelope.rb
117
111
  - lib/promiscuous/publisher/amqp.rb
112
+ - lib/promiscuous/publisher/base.rb
113
+ - lib/promiscuous/publisher/polymorphic.rb
114
+ - lib/promiscuous/amqp/null.rb
115
+ - lib/promiscuous/amqp/rubyamqp.rb
116
+ - lib/promiscuous/amqp/bunny.rb
117
+ - lib/promiscuous/publisher.rb
118
+ - lib/promiscuous/subscriber/attributes.rb
119
+ - lib/promiscuous/subscriber/mongoid.rb
120
+ - lib/promiscuous/subscriber/error.rb
121
+ - lib/promiscuous/subscriber/generic.rb
122
+ - lib/promiscuous/subscriber/mongoid/root.rb
118
123
  - lib/promiscuous/subscriber/mongoid/embedded.rb
119
124
  - lib/promiscuous/subscriber/mongoid/upsert.rb
120
- - lib/promiscuous/subscriber/mongoid/root.rb
121
- - lib/promiscuous/subscriber/attributes.rb
122
- - lib/promiscuous/subscriber/base.rb
123
- - lib/promiscuous/subscriber/custom_class.rb
124
125
  - lib/promiscuous/subscriber/envelope.rb
125
- - lib/promiscuous/subscriber/generic.rb
126
- - lib/promiscuous/subscriber/polymorphic.rb
127
- - lib/promiscuous/subscriber/mongoid.rb
128
126
  - lib/promiscuous/subscriber/amqp.rb
129
- - lib/promiscuous/subscriber/error.rb
130
- - lib/promiscuous/version.rb
131
- - lib/promiscuous/publisher.rb
127
+ - lib/promiscuous/subscriber/custom_class.rb
128
+ - lib/promiscuous/subscriber/base.rb
129
+ - lib/promiscuous/subscriber/polymorphic.rb
130
+ - lib/promiscuous/worker.rb
131
+ - lib/promiscuous/railtie/replicate.rake
132
132
  - lib/promiscuous/railtie.rb
133
+ - lib/promiscuous/loader.rb
133
134
  - lib/promiscuous/amqp.rb
135
+ - lib/promiscuous/config.rb
134
136
  - lib/promiscuous/subscriber.rb
135
- - lib/promiscuous/worker.rb
136
137
  - lib/promiscuous.rb
137
138
  - README.md
138
139
  homepage: http://github.com/crowdtap/promiscuous
@@ -155,7 +156,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
155
156
  version: '0'
156
157
  requirements: []
157
158
  rubyforge_project:
158
- rubygems_version: 1.8.24
159
+ rubygems_version: 1.8.22
159
160
  signing_key:
160
161
  specification_version: 3
161
162
  summary: Model replication over RabbitMQ
@@ -1,27 +0,0 @@
1
- module Promiscuous
2
- module AMQP
3
- module Fake
4
- mattr_accessor :messages, :subscribe_options
5
- self.messages = []
6
-
7
- def self.configure(options)
8
- end
9
-
10
- def self.publish(msg)
11
- self.messages << msg
12
- end
13
-
14
- def self.subscribe(options={}, &block)
15
- self.subscribe_options = options
16
- end
17
-
18
- def self.clear
19
- self.messages.clear
20
- self.subscribe_options = nil
21
- end
22
-
23
- def self.close
24
- end
25
- end
26
- end
27
- end