active_pubsub 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7ea43e949553e6313e96caa956b829708b0aecf7
4
+ data.tar.gz: 852017f1bc06dc74f14b4b828fa7f9bf23f73c84
5
+ SHA512:
6
+ metadata.gz: b179fd78488d123798f1db4cb90cbf9e6661628b3b6a91f38cdb37b38be04cbe9800c41bb44b925c70f974754d55fe09ef0dcd032b5c667cf118e139c16d6d0d
7
+ data.tar.gz: 2fe5a53c4d20a69292d9dbe4993aaeff5df5802c8d3a0faa469615286fb89269a0b3923a313b3caa67b17fcc6dd0b7e3921f6ef3c42faea522eff877e1a3efe1
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in active_pubsub.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,38 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :bundler do
5
+ watch('Gemfile')
6
+ # Uncomment next line if your Gemfile contains the `gemspec' command.
7
+ # watch(/^.+\.gemspec/)
8
+ end
9
+
10
+ # Note: The cmd option is now required due to the increasing number of ways
11
+ # rspec may be run, below are examples of the most common uses.
12
+ # * bundler: 'bundle exec rspec'
13
+ # * bundler binstubs: 'bin/rspec'
14
+ # * spring: 'bin/rsspec' (This will use spring if running and you have
15
+ # installed the spring binstubs per the docs)
16
+ # * zeus: 'zeus rspec' (requires the server to be started separetly)
17
+ # * 'just' rspec: 'rspec'
18
+ guard :rspec, cmd: 'bundle exec rspec' do
19
+ watch(%r{^spec/.+_spec\.rb$})
20
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
21
+ watch('spec/spec_helper.rb') { "spec" }
22
+
23
+ # Rails example
24
+ watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
25
+ watch(%r{^app/(.*)(\.erb|\.haml|\.slim)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
26
+ watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
27
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
28
+ watch('config/routes.rb') { "spec/routing" }
29
+ watch('app/controllers/application_controller.rb') { "spec/controllers" }
30
+ watch('spec/rails_helper.rb') { "spec" }
31
+
32
+ # Capybara features specs
33
+ watch(%r{^app/views/(.+)/.*\.(erb|haml|slim)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
34
+
35
+ # Turnip features and steps
36
+ watch(%r{^spec/acceptance/(.+)\.feature$})
37
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
38
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Jason Ayre
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # ActivePubsub
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'active_pubsub'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install active_pubsub
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( https://github.com/[my-github-username]/active_pubsub/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,42 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'active_pubsub/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "active_pubsub"
8
+ spec.version = ActivePubsub::VERSION
9
+ spec.authors = ["Jason Ayre"]
10
+ spec.email = ["jasonayre@gmail.com"]
11
+ spec.summary = %q{Pubsub using RabbitMQ and ActiveRecord, observe model events from different services.}
12
+ spec.description = %q{Uses RabbitMQ and ActiveRecord for publishing and consuming model events from any service}
13
+ spec.homepage = "https://github.com/jasonayre/active_pubsub"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "active_attr"
22
+ spec.add_dependency "celluloid-io"
23
+ spec.add_dependency "json"
24
+ spec.add_dependency "bunny"
25
+
26
+ spec.add_development_dependency 'activerecord'
27
+ spec.add_development_dependency 'sqlite3'
28
+ spec.add_development_dependency "bundler", "~> 1.6"
29
+ spec.add_development_dependency "rake"
30
+ spec.add_development_dependency "rspec"
31
+ spec.add_development_dependency "rspec-pride"
32
+ spec.add_development_dependency "pry-nav"
33
+ spec.add_development_dependency "simplecov"
34
+ spec.add_development_dependency 'rspec-its', '~> 1'
35
+ spec.add_development_dependency 'rspec-collection_matchers', '~> 1'
36
+ spec.add_development_dependency 'guard', '~> 2'
37
+ spec.add_development_dependency 'guard-rspec', '~> 4'
38
+ spec.add_development_dependency 'guard-bundler', '~> 2'
39
+ spec.add_development_dependency 'rb-fsevent'
40
+ spec.add_development_dependency 'terminal-notifier-guard'
41
+
42
+ end
data/bin/subscriber ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'thor'
4
+ require 'active_pubsub'
5
+ require './config/environment.rb'
6
+
7
+ class Subscriber < ::Thor
8
+ desc "start", "Start Rabbit Subscriber"
9
+ def start
10
+ puts "Starting Rabbit Subscriber SERVER"
11
+
12
+ ::ActivePubsub.load_subscribers
13
+
14
+ ::ActivePubsub.start_subscribers
15
+
16
+ puts "Subscribers Started"
17
+ end
18
+ end
19
+
20
+ ::Subscriber.start
21
+
22
+ sleep
@@ -0,0 +1,7 @@
1
+ class Post < ::ActiveRecord::Base
2
+ include ::ActivePubsub::Publishable
3
+
4
+ # This is the namespace for the local service
5
+ # The following Will set up a cms.post rabbit exchange
6
+ publish_as "cms"
7
+ end
@@ -0,0 +1,19 @@
1
+ # this is an example of a service that wants to do something when posts are created,
2
+ # updated, or destroyed, in the publishing service
3
+
4
+ class PostSubscriber < ::ActivePubsub::Subscriber
5
+ observes "cms"
6
+ as "aggregator"
7
+
8
+ on :created do |record|
9
+ puts record.inspect
10
+ end
11
+
12
+ on :destroyed do |record|
13
+ puts record.inspect
14
+ end
15
+
16
+ on :updated do |record|
17
+ puts record.inspect
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ require 'active_support/ordered_options'
2
+
3
+ module ActivePubsub
4
+ ### IMPORTANT ###
5
+ # Set service namespace if your subscriber has namespace set or it wont get events"
6
+ class Config < ::ActiveSupport::OrderedOptions
7
+ def initialize(options = {})
8
+ options[:address] = ENV['RABBITMQ_URL']
9
+ options[:publish_as] = nil
10
+ options[:service_namespace] = nil
11
+ super
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ require "bunny"
2
+
3
+ module ActivePubsub
4
+ class Connection < Delegator
5
+ attr_accessor :connection, :channel
6
+
7
+ def initialize(options = {})
8
+ @connection = ::Bunny.new(::ActivePubsub.config.try(:address) || "amqp://guest:guest@localhost:5672")
9
+ @connection.start
10
+ @channel = connection.create_channel
11
+ end
12
+
13
+ def __getobj__
14
+ @connection
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,27 @@
1
+ require 'active_model'
2
+
3
+ module ActivePubsub
4
+ class Event
5
+ include ::ActiveAttr::Model
6
+ include ::ActiveModel::AttributeMethods
7
+
8
+ attribute :id
9
+ attribute :exchange
10
+ attribute :name
11
+ attribute :occured_at
12
+ attribute :record
13
+ attribute :record_type
14
+ attribute :routing_key
15
+
16
+ #attributes have to be set for purposes of marshaling
17
+ def initialize(exchange, name, record)
18
+ self[:exchange] = exchange
19
+ self[:name] = name
20
+ self[:record] = record
21
+ self[:id] = ::SecureRandom.hex
22
+ self[:record_type] = record.class.name
23
+ self[:occured_at] ||= ::DateTime.now
24
+ self[:routing_key] ||= [exchange, name].join('.')
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,80 @@
1
+ module ActivePubsub
2
+ module Publishable
3
+ extend ActiveSupport::Concern
4
+
5
+ PUBLISHABLE_ACTIONS = ["updated", "created", "destroyed"]
6
+
7
+ included do
8
+ include ::ActiveModel::Dirty
9
+
10
+ after_create :publish_created_event
11
+ after_update :publish_updated_event
12
+ after_destroy :publish_destroyed_event
13
+ class_attribute :exchange_prefix
14
+
15
+ self.publishable_actions ||= []
16
+
17
+ ::ActivePubsub::Publisher.increment_publishable_model_count!
18
+ end
19
+
20
+ def attributes_hash
21
+ attributes.merge!("changes" => changes)
22
+ end
23
+
24
+ private
25
+
26
+ def publish_updated_event
27
+ record_updated_event = ::ActivePubsub::Event.new(self.class.exchange_key, "updated", serialized_resource)
28
+
29
+ ::ActivePubsub.publish_event(record_updated_event)
30
+ end
31
+
32
+ def publish_created_event
33
+ record_created_event = ::ActivePubsub::Event.new(self.class.exchange_key, "created", serialized_resource)
34
+
35
+ ::ActivePubsub.publish_event(record_created_event)
36
+ end
37
+
38
+ def publish_destroyed_event
39
+ record_destroyed_event = ::ActivePubsub::Event.new(self.class.exchange_key, "destroyed", serialized_resource)
40
+
41
+ ::ActivePubsub.publish_event(record_destroyed_event)
42
+ end
43
+
44
+ def serialized_resource
45
+ Marshal.dump(attributes_hash)
46
+ end
47
+
48
+ module ClassMethods
49
+ def exchange_key
50
+ [
51
+ try(:exchange_prefix) { ::ActivePubsub.config.try(:publish_as) },
52
+ name.demodulize.underscore
53
+ ].flatten.compact.join(".")
54
+ end
55
+
56
+ #this is the publishing service namespace which will be used to build exchange name
57
+
58
+ def publish_as(prefix)
59
+ self.exchange_prefix = prefix
60
+
61
+ ::ActivePubsub::Publisher.start unless ::ActivePubsub::Publisher.started
62
+
63
+ ::ActivePubsub.publisher.register_exchange(exchange_key)
64
+ end
65
+
66
+ #todo: make publishable actions filterable/appendable
67
+ def publishable_actions(*actions)
68
+ @publishable_actions = actions
69
+ end
70
+
71
+ def routing_key
72
+ resource_routing_keys.join(".")
73
+ end
74
+
75
+ def resource_routing_keys
76
+ [try(:exchange_prefix), name.demodulize.underscore].flatten.compact
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,71 @@
1
+ 'celluloid/io'
2
+
3
+ module ActivePubsub
4
+ class Publisher
5
+ include ::Celluloid
6
+
7
+ attr_accessor :connection
8
+ finalizer :clear_connections!
9
+
10
+ ### Class Methods ###
11
+ class << self
12
+ attr_accessor :started
13
+ attr_accessor :publishable_model_count
14
+ end
15
+
16
+ @started = false
17
+ @publishable_model_count = 0
18
+
19
+ def self.increment_publishable_model_count!
20
+ self.publishable_model_count += 1
21
+ end
22
+
23
+ def self.start
24
+ supervise_as :rabbit_publisher
25
+
26
+ self.started = true
27
+ end
28
+
29
+ def self.started?
30
+ self.started
31
+ end
32
+
33
+ ### Instance Methods ###
34
+ def initialize
35
+ connection
36
+ end
37
+
38
+ def connection
39
+ @connection ||= ::ActivePubsub::Connection.new
40
+ end
41
+
42
+ def clear_connections!
43
+ channel.close
44
+ connection.close
45
+ end
46
+
47
+ def channel
48
+ connection.channel
49
+ end
50
+
51
+ def exchanges
52
+ @exchanges ||= {}
53
+ end
54
+
55
+ def publish_event(event)
56
+ ::ActiveRecord::Base.connection_pool.with_connection do
57
+ puts event.inspect
58
+
59
+ exchanges[event.exchange].publish(serialize_event(event), :routing_key => event.routing_key)
60
+ end
61
+ end
62
+
63
+ def serialize_event(event)
64
+ ::Marshal.dump(event)
65
+ end
66
+
67
+ def register_exchange(exchange_name)
68
+ exchanges[exchange_name] ||= channel.topic(exchange_name, :auto_delete => true)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,28 @@
1
+ require 'rails/railtie'
2
+
3
+ module ActivePubsub
4
+ class Railtie < ::Rails::Railtie
5
+ # we only need publisher started if service has a publishable model
6
+ ::ActiveSupport.on_load(:active_record) do
7
+ puts "Active Record LOADED"
8
+
9
+ if(::ActivePubsub::Publisher.publishable_model_count > 0) && !::ActivePubsub::Publisher.started?
10
+ ::ActivePubsub::Publisher.start
11
+ end
12
+ end
13
+
14
+ #todo: make redis configurable
15
+ def self.load_config_yml
16
+ config_file = ::YAML.load_file(config_yml_filepath)
17
+ return unless config_file.is_a?(Hash)
18
+ end
19
+
20
+ def self.config_yml_exists?
21
+ ::File.exists? config_yml_filepath
22
+ end
23
+
24
+ def self.config_yml_filepath
25
+ ::Rails.root.join('config', 'rabbit.yml')
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,76 @@
1
+ require 'active_support/all'
2
+
3
+ module ActivePubsub
4
+ class Subscriber
5
+ attr_accessor :connection
6
+
7
+ class_attribute :events
8
+ class_attribute :exchange_name
9
+ class_attribute :connection
10
+ class_attribute :local_service_namespace
11
+
12
+ self.events = {}
13
+ self.connection = ::ActivePubsub::Connection.new
14
+
15
+ ### Class Methods ###
16
+ def self.channel
17
+ connection.channel
18
+ end
19
+
20
+ def self.exchange
21
+ channel.topic(exchange_name, :auto_delete => true)
22
+ end
23
+
24
+ def self.as(service_namespace)
25
+ self.local_service_namespace = service_namespace
26
+ end
27
+
28
+ def self.on(event_name, &block)
29
+ events[event_name] = block
30
+ end
31
+
32
+ def self.bind_subscriptions!
33
+ events.each_pair do |event_name, block|
34
+ channel.queue(queue_for_event(event_name.to_s))
35
+ .bind(exchange, :routing_key => routing_key_for_event(event_name))
36
+ .subscribe do |delivery_info, properties, payload|
37
+ event = deserialize_event(payload)
38
+ resource = deserialize_record(event[:record])
39
+
40
+ block.call(resource)
41
+ end
42
+ end
43
+ end
44
+
45
+ def self.deserialize_event(event)
46
+ @current_event = Marshal.load(event)
47
+ end
48
+
49
+ def self.deserialize_record(record)
50
+ @current_record = Marshal.load(record)
51
+ end
52
+
53
+ def self.observes(target_exchange)
54
+ self.exchange_name = target_exchange
55
+ end
56
+
57
+ def self.queue_for_event(event_name)
58
+ [local_service_namespace, exchange_name, event_name].compact.join('.')
59
+ end
60
+
61
+ def self.routing_key_for_event(event_name)
62
+ [exchange_name, event_name].join(".")
63
+ end
64
+
65
+ def self.print_subscriptions!
66
+ message = "Watching: \n"
67
+ events.each_pair do |event_name, block|
68
+ message << "Queue: #{queue_for_event(event_name.to_s)} \n" <<
69
+ "Routing Key: #{routing_key_for_event(event_name)} \n" <<
70
+ "\n"
71
+ end
72
+
73
+ puts message
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,3 @@
1
+ module ActivePubsub
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,59 @@
1
+ require "active_pubsub/version"
2
+
3
+ ENV['RABBITMQ_URL'] ||= "amqp://guest:guest@localhost:5672"
4
+
5
+ require "bunny"
6
+ require "celluloid"
7
+ require "active_support/all"
8
+ require "active_attr"
9
+
10
+ module ActivePubsub
11
+ class << self
12
+ attr_accessor :configuration
13
+ alias_method :config, :configuration
14
+
15
+ delegate :publish_event, :to => :publisher
16
+ end
17
+
18
+ def self.configure
19
+ self.configuration ||= ::ActivePubsub::Config.new
20
+
21
+ yield(configuration)
22
+
23
+ ::ActiveSupport.run_load_hooks(:active_pubsub, self)
24
+ end
25
+
26
+ def self.load_subscribers
27
+ ::Dir.glob(::Rails.root.join('app', 'subscribers', "*.rb")).each{ |file| load file }
28
+ end
29
+
30
+ def self.publisher
31
+ ::Celluloid::Actor[:rabbit_publisher]
32
+ end
33
+
34
+ def self.start_publisher
35
+ ::ActivePubsub::Publisher.start unless ::ActivePubsub::Publisher.started?
36
+ end
37
+
38
+ def self.start_subscribers
39
+ puts "Starting subscriber"
40
+ ::ActivePubsub::Subscriber.subclasses.each do |subscriber|
41
+ subscriber.bind_subscriptions!
42
+ subscriber.print_subscriptions!
43
+ end
44
+ end
45
+
46
+ def self.symbolize_routing_key(routing_key)
47
+ :"#{routing_key.split('.').join('_')}"
48
+ end
49
+
50
+ end
51
+
52
+ require "active_pubsub/connection"
53
+ require "active_pubsub/config"
54
+ require "active_pubsub/event"
55
+ require "active_pubsub/publisher"
56
+ require "active_pubsub/publishable"
57
+ require "active_pubsub/subscriber"
58
+
59
+ require 'active_pubsub/railtie' if defined?(Rails)
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ describe ::ActivePubsub::Connection do
4
+ subject { described_class.new }
5
+
6
+ its(:connection) { should be_an_instance_of(::Bunny::Session) }
7
+ its(:channel) { should be_a(::Bunny::Channel) }
8
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe ::ActivePubsub::Publishable do
4
+ subject { ::Fake::Blog::Post }
5
+ let(:instance_subject) { ::Fake::Blog::Post.new }
6
+ let(:publisher) { ::ActivePubsub.publisher }
7
+
8
+ context "when included" do
9
+ it "should increment publishable model count on publisher class" do
10
+ expect(::ActivePubsub::Publisher.publishable_model_count).to be > 0
11
+ end
12
+
13
+ context "Instance Methods" do
14
+ describe "#publish_updated_event" do
15
+ let(:created_record) { ::Fake::Blog::Post.create!(:title => "Post about nothing") }
16
+
17
+ it "should build updated action event when record is updated" do
18
+ ::ActivePubsub.should_receive(:publish_event).with(instance_of(::ActivePubsub::Event)).once
19
+ created_record
20
+ end
21
+ end
22
+
23
+ describe "#publish_created_event" do
24
+ let(:created_record) { ::Fake::Blog::Post.create!(:title => "Post about nothing") }
25
+
26
+ it "should publish when record is updated" do
27
+ ::ActivePubsub.should_receive(:publish_event).with(instance_of(::ActivePubsub::Event)).twice
28
+
29
+ created_record
30
+ created_record.title = "asdasd"
31
+ created_record.save
32
+ end
33
+ end
34
+
35
+ describe "#publish_destroyed_event" do
36
+ let(:created_record) { ::Fake::Blog::Post.create!(:title => "Post about nothing") }
37
+
38
+ it "should publish when record is updated" do
39
+ ::ActivePubsub.should_receive(:publish_event).with(instance_of(::ActivePubsub::Event)).twice
40
+
41
+ created_record.destroy
42
+ end
43
+ end
44
+ end
45
+
46
+ context "Class Methods" do
47
+ its(:exchange_key) { should eq "test.post" }
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe ::ActivePubsub::Publisher do
4
+ let(:fake_record) { ::Fake::Blog::Post.new }
5
+ let(:exchange_key) { "test.post" }
6
+ let(:event_name) { "test.post.created" }
7
+ let(:routing_key) { "test.post.created" }
8
+ let(:record_created_routing_key) { "test.post.created" }
9
+ let(:record_updated_routing_key) { "test.post.updated" }
10
+ let(:record_destroyed_routing_key) { "test.post.destroyed" }
11
+
12
+ let(:fake_event) {
13
+ ::ActivePubsub::Event.new(routing_key, event_name, fake_record)
14
+ }
15
+
16
+ subject {
17
+ ::Celluloid::Actor[:rabbit_publisher]
18
+ }
19
+
20
+ describe "#connection" do
21
+ it "should be active pubsub connection instance" do
22
+ subject.connection.should be_a(ActivePubsub::Connection)
23
+ end
24
+ end
25
+
26
+ describe "#clear_connections!" do
27
+ # todo: fix this test, it was causing strange network failure error
28
+ # it "should close channel" do
29
+ # subject.should_receive(:channel)
30
+ # subject.stub_chain(:channel, :close)
31
+ # subject.stub_chain(:connection, :close)
32
+ #
33
+ # subject.clear_connections!
34
+ # end
35
+ #
36
+ # it "should close connection" do
37
+ # subject.should_receive(:connection)
38
+ # subject.stub_chain(:channel, :close)
39
+ # subject.stub_chain(:connection, :close)
40
+ #
41
+ # subject.clear_connections!
42
+ # end
43
+ end
44
+
45
+ describe "#exchanges" do
46
+ its(:exchanges) { should include(exchange_key) }
47
+ end
48
+
49
+ describe "#publish_event" do
50
+ it "should receive publish event with instance of event when publishable record is saved" do
51
+ subject.should_receive(:publish_event).with(instance_of(::ActivePubsub::Event))
52
+
53
+ fake_record.save
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ require 'simplecov'
4
+ require 'pry'
5
+ require 'active_pubsub'
6
+
7
+ SimpleCov.start do
8
+ add_filter '/spec/'
9
+ end
10
+
11
+ RSpec.configure do |config|
12
+ end
13
+
14
+ Bundler.require(:default, :development, :test)
15
+
16
+ ::Dir["#{::File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f }
@@ -0,0 +1,21 @@
1
+ require 'active_record'
2
+
3
+ ActiveRecord::Base.establish_connection(
4
+ :adapter => "sqlite3",
5
+ :database => "spec/test.db"
6
+ )
7
+
8
+ ActiveRecord::Base.connection.tables.each do |table|
9
+ ActiveRecord::Base.connection.drop_table(table)
10
+ end
11
+
12
+ ActiveRecord::Schema.define(:version => 1) do
13
+ create_table :posts do |t|
14
+ t.string :body
15
+ t.string :title
16
+ t.string :slug
17
+ t.integer :user_id
18
+
19
+ t.timestamps
20
+ end
21
+ end
@@ -0,0 +1,10 @@
1
+ require 'active_record'
2
+ module Fake
3
+ module Blog
4
+ class Post < ::ActiveRecord::Base
5
+ include ::ActivePubsub::Publishable
6
+
7
+ publish_as "test"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1 @@
1
+ require 'support/models/post'
data/spec/test.db ADDED
Binary file
metadata ADDED
@@ -0,0 +1,349 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_pubsub
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jason Ayre
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: active_attr
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: celluloid-io
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bunny
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: activerecord
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sqlite3
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: bundler
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.6'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.6'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rspec-pride
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: pry-nav
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: simplecov
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: rspec-its
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '1'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '1'
195
+ - !ruby/object:Gem::Dependency
196
+ name: rspec-collection_matchers
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: '1'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: '1'
209
+ - !ruby/object:Gem::Dependency
210
+ name: guard
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - "~>"
214
+ - !ruby/object:Gem::Version
215
+ version: '2'
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - "~>"
221
+ - !ruby/object:Gem::Version
222
+ version: '2'
223
+ - !ruby/object:Gem::Dependency
224
+ name: guard-rspec
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - "~>"
228
+ - !ruby/object:Gem::Version
229
+ version: '4'
230
+ type: :development
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - "~>"
235
+ - !ruby/object:Gem::Version
236
+ version: '4'
237
+ - !ruby/object:Gem::Dependency
238
+ name: guard-bundler
239
+ requirement: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - "~>"
242
+ - !ruby/object:Gem::Version
243
+ version: '2'
244
+ type: :development
245
+ prerelease: false
246
+ version_requirements: !ruby/object:Gem::Requirement
247
+ requirements:
248
+ - - "~>"
249
+ - !ruby/object:Gem::Version
250
+ version: '2'
251
+ - !ruby/object:Gem::Dependency
252
+ name: rb-fsevent
253
+ requirement: !ruby/object:Gem::Requirement
254
+ requirements:
255
+ - - ">="
256
+ - !ruby/object:Gem::Version
257
+ version: '0'
258
+ type: :development
259
+ prerelease: false
260
+ version_requirements: !ruby/object:Gem::Requirement
261
+ requirements:
262
+ - - ">="
263
+ - !ruby/object:Gem::Version
264
+ version: '0'
265
+ - !ruby/object:Gem::Dependency
266
+ name: terminal-notifier-guard
267
+ requirement: !ruby/object:Gem::Requirement
268
+ requirements:
269
+ - - ">="
270
+ - !ruby/object:Gem::Version
271
+ version: '0'
272
+ type: :development
273
+ prerelease: false
274
+ version_requirements: !ruby/object:Gem::Requirement
275
+ requirements:
276
+ - - ">="
277
+ - !ruby/object:Gem::Version
278
+ version: '0'
279
+ description: Uses RabbitMQ and ActiveRecord for publishing and consuming model events
280
+ from any service
281
+ email:
282
+ - jasonayre@gmail.com
283
+ executables:
284
+ - subscriber
285
+ extensions: []
286
+ extra_rdoc_files: []
287
+ files:
288
+ - ".gitignore"
289
+ - ".rspec"
290
+ - Gemfile
291
+ - Guardfile
292
+ - LICENSE.txt
293
+ - README.md
294
+ - Rakefile
295
+ - active_pubsub.gemspec
296
+ - bin/subscriber
297
+ - examples/publishing_service_example.rb
298
+ - examples/subscribing_service_example.rb
299
+ - lib/active_pubsub.rb
300
+ - lib/active_pubsub/config.rb
301
+ - lib/active_pubsub/connection.rb
302
+ - lib/active_pubsub/event.rb
303
+ - lib/active_pubsub/publishable.rb
304
+ - lib/active_pubsub/publisher.rb
305
+ - lib/active_pubsub/railtie.rb
306
+ - lib/active_pubsub/subscriber.rb
307
+ - lib/active_pubsub/version.rb
308
+ - spec/active_pubsub/connection_spec.rb
309
+ - spec/active_pubsub/publishable_spec.rb
310
+ - spec/active_pubsub/publisher_spec.rb
311
+ - spec/spec_helper.rb
312
+ - spec/support/db/setup.rb
313
+ - spec/support/models.rb
314
+ - spec/support/models/post.rb
315
+ - spec/test.db
316
+ homepage: https://github.com/jasonayre/active_pubsub
317
+ licenses:
318
+ - MIT
319
+ metadata: {}
320
+ post_install_message:
321
+ rdoc_options: []
322
+ require_paths:
323
+ - lib
324
+ required_ruby_version: !ruby/object:Gem::Requirement
325
+ requirements:
326
+ - - ">="
327
+ - !ruby/object:Gem::Version
328
+ version: '0'
329
+ required_rubygems_version: !ruby/object:Gem::Requirement
330
+ requirements:
331
+ - - ">="
332
+ - !ruby/object:Gem::Version
333
+ version: '0'
334
+ requirements: []
335
+ rubyforge_project:
336
+ rubygems_version: 2.2.2
337
+ signing_key:
338
+ specification_version: 4
339
+ summary: Pubsub using RabbitMQ and ActiveRecord, observe model events from different
340
+ services.
341
+ test_files:
342
+ - spec/active_pubsub/connection_spec.rb
343
+ - spec/active_pubsub/publishable_spec.rb
344
+ - spec/active_pubsub/publisher_spec.rb
345
+ - spec/spec_helper.rb
346
+ - spec/support/db/setup.rb
347
+ - spec/support/models.rb
348
+ - spec/support/models/post.rb
349
+ - spec/test.db