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 +16 -113
- data/lib/promiscuous/amqp/rubyamqp.rb +7 -6
- data/lib/promiscuous/common/worker.rb +17 -0
- data/lib/promiscuous/common.rb +1 -0
- data/lib/promiscuous/publisher/amqp.rb +1 -1
- data/lib/promiscuous/publisher/error.rb +18 -0
- data/lib/promiscuous/publisher/model.rb +1 -1
- data/lib/promiscuous/publisher/mongoid/defer.rb +52 -0
- data/lib/promiscuous/publisher/mongoid.rb +6 -0
- data/lib/promiscuous/publisher/worker.rb +49 -0
- data/lib/promiscuous/publisher.rb +2 -0
- data/lib/promiscuous/railtie/replicate.rake +27 -7
- data/lib/promiscuous/subscriber/amqp.rb +1 -1
- data/lib/promiscuous/subscriber/mongoid/embedded.rb +1 -1
- data/lib/promiscuous/subscriber/polymorphic.rb +0 -1
- data/lib/promiscuous/subscriber/worker.rb +31 -0
- data/lib/promiscuous/subscriber.rb +1 -0
- data/lib/promiscuous/version.rb +1 -1
- data/lib/promiscuous/worker.rb +12 -48
- data/lib/promiscuous.rb +1 -0
- metadata +16 -11
    
        data/README.md
    CHANGED
    
    | @@ -3,119 +3,22 @@ Promiscuous | |
| 3 3 |  | 
| 4 4 | 
             
            [](https://secure.travis-ci.org/crowdtap/promiscuous)
         | 
| 5 5 |  | 
| 6 | 
            -
            Promiscuous  | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 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 | 
| 15 | 
            -
                        :port | 
| 14 | 
            +
                        :host   => uri.host,
         | 
| 15 | 
            +
                        :port   => uri.port,
         | 
| 16 16 | 
             
                        :scheme => uri.scheme,
         | 
| 17 | 
            -
                        :user | 
| 18 | 
            -
                        :pass | 
| 19 | 
            -
                        :vhost | 
| 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
         | 
    
        data/lib/promiscuous/common.rb
    CHANGED
    
    
| @@ -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). | 
| 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 | 
            -
               | 
| 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 | 
            -
                   | 
| 10 | 
            -
                   | 
| 11 | 
            -
             | 
| 12 | 
            -
                  Promiscuous:: | 
| 9 | 
            +
                  trap_signals
         | 
| 10 | 
            +
                  force_backend :rubyamqp
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  Promiscuous::Loader.load_descriptors if defined?(Rails)
         | 
| 13 13 |  | 
| 14 14 | 
             
                  Promiscuous::Worker.replicate
         | 
| 15 | 
            -
             | 
| 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=( | 
| 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
         | 
| @@ -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)
         | 
    
        data/lib/promiscuous/version.rb
    CHANGED
    
    
    
        data/lib/promiscuous/worker.rb
    CHANGED
    
    | @@ -1,51 +1,15 @@ | |
| 1 | 
            -
            module Promiscuous
         | 
| 2 | 
            -
               | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 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 | 
            -
             | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 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
    
    
    
        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. | 
| 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- | 
| 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
         |