untied-publisher 0.0.1
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/.gitignore +20 -0
- data/.rvmrc +48 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +27 -0
- data/Rakefile +1 -0
- data/lib/untied-publisher.rb +13 -0
- data/lib/untied-publisher/config.rb +28 -0
- data/lib/untied-publisher/doorkeeper.rb +78 -0
- data/lib/untied-publisher/event.rb +27 -0
- data/lib/untied-publisher/observer.rb +50 -0
- data/lib/untied-publisher/producer.rb +58 -0
- data/lib/untied-publisher/railtie.rb +15 -0
- data/lib/untied-publisher/version.rb +5 -0
- data/spec/doorkeeper_spec.rb +64 -0
- data/spec/event_spec.rb +52 -0
- data/spec/producer_spec.rb +58 -0
- data/spec/publisher_observer_spec.rb +63 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/setup_ar_and_schema.rb +24 -0
- data/untied-publisher.gemspec +35 -0
- metadata +216 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# This is an RVM Project .rvmrc file, used to automatically load the ruby
|
4
|
+
# development environment upon cd'ing into the directory
|
5
|
+
|
6
|
+
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional,
|
7
|
+
# Only full ruby name is supported here, for short names use:
|
8
|
+
# echo "rvm use 1.8.7" > .rvmrc
|
9
|
+
environment_id="ruby-1.8.7-p370"
|
10
|
+
|
11
|
+
# Uncomment the following lines if you want to verify rvm version per project
|
12
|
+
# rvmrc_rvm_version="1.14.5 (master)" # 1.10.1 seams as a safe start
|
13
|
+
# eval "$(echo ${rvm_version}.${rvmrc_rvm_version} | awk -F. '{print "[[ "$1*65536+$2*256+$3" -ge "$4*65536+$5*256+$6" ]]"}' )" || {
|
14
|
+
# echo "This .rvmrc file requires at least RVM ${rvmrc_rvm_version}, aborting loading."
|
15
|
+
# return 1
|
16
|
+
# }
|
17
|
+
|
18
|
+
# First we attempt to load the desired environment directly from the environment
|
19
|
+
# file. This is very fast and efficient compared to running through the entire
|
20
|
+
# CLI and selector. If you want feedback on which environment was used then
|
21
|
+
# insert the word 'use' after --create as this triggers verbose mode.
|
22
|
+
if [[ -d "${rvm_path:-$HOME/.rvm}/environments"
|
23
|
+
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
|
24
|
+
then
|
25
|
+
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
|
26
|
+
[[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]] &&
|
27
|
+
\. "${rvm_path:-$HOME/.rvm}/hooks/after_use" || true
|
28
|
+
else
|
29
|
+
# If the environment file has not yet been created, use the RVM CLI to select.
|
30
|
+
rvm --create "$environment_id" || {
|
31
|
+
echo "Failed to create RVM environment '${environment_id}'."
|
32
|
+
return 1
|
33
|
+
}
|
34
|
+
fi
|
35
|
+
|
36
|
+
# If you use bundler, this might be useful to you:
|
37
|
+
# if [[ -s Gemfile ]] && {
|
38
|
+
# ! builtin command -v bundle >/dev/null ||
|
39
|
+
# builtin command -v bundle | GREP_OPTIONS= \grep $rvm_path/bin/bundle >/dev/null
|
40
|
+
# }
|
41
|
+
# then
|
42
|
+
# printf "%b" "The rubygem 'bundler' is not installed. Installing it now.\n"
|
43
|
+
# gem install bundler
|
44
|
+
# fi
|
45
|
+
# if [[ -s Gemfile ]] && builtin command -v bundle >/dev/null
|
46
|
+
# then
|
47
|
+
# bundle install | GREP_OPTIONS= \grep -vE '^Using|Your bundle is complete'
|
48
|
+
# fi
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Guilherme Cavalcanti
|
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,27 @@
|
|
1
|
+
# Untied::Publisher
|
2
|
+
|
3
|
+
This is the publisher part of Untied. Untied is an observer pattern implementation to distributed systems.
|
4
|
+
|
5
|
+
For usage information, please visit the [Untied](http://github.com.br/redu/untied) page.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'untied-publisher'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install untied-publisher
|
20
|
+
|
21
|
+
## Contributing
|
22
|
+
|
23
|
+
1. Fork it
|
24
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
25
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
26
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
27
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "untied-publisher/version"
|
2
|
+
|
3
|
+
module Untied
|
4
|
+
module Publisher
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'untied-publisher/event'
|
9
|
+
require 'untied-publisher/config'
|
10
|
+
require 'untied-publisher/doorkeeper'
|
11
|
+
require 'untied-publisher/observer'
|
12
|
+
require 'untied-publisher/producer'
|
13
|
+
require 'untied-publisher/railtie' if defined?(Rails)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'configurable'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module Untied
|
6
|
+
module Publisher
|
7
|
+
def self.configure(&block)
|
8
|
+
yield(config) if block_given?
|
9
|
+
if config.deliver_messages
|
10
|
+
Untied.start
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.config
|
15
|
+
@config ||= Config.new
|
16
|
+
end
|
17
|
+
|
18
|
+
class Config
|
19
|
+
include Configurable
|
20
|
+
|
21
|
+
config :logger, Logger.new(STDOUT)
|
22
|
+
config :deliver_messages, true
|
23
|
+
config :service_name
|
24
|
+
config :doorkeeper, nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Untied
|
3
|
+
module Publisher
|
4
|
+
module Doorkeeper
|
5
|
+
# The Doorkeeper defines which ActiveRecord models will be propagated to
|
6
|
+
# other services. The instance method #watch is available for this.
|
7
|
+
#
|
8
|
+
# The Doorkeeper a similar way of ActiveRecord::Observer. It register
|
9
|
+
# functions on the models which calls the method on Untied::Publisher::Observer
|
10
|
+
# when ActiveRecord::Callbacks are fired.
|
11
|
+
#
|
12
|
+
# The following publisher watches the User after_create event:
|
13
|
+
#
|
14
|
+
# class MyDoorkeeper
|
15
|
+
# include Untied::Doorkeeper
|
16
|
+
#
|
17
|
+
# def initialize
|
18
|
+
# watch User, :after_create
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
|
22
|
+
# List of observed classes and callbacks
|
23
|
+
def observed
|
24
|
+
@observed ||= []
|
25
|
+
end
|
26
|
+
|
27
|
+
# Watches ActiveRecord lifecycle callbacks for some Class
|
28
|
+
#
|
29
|
+
# class Doorkeeper
|
30
|
+
# include Untied::Doorkeeper
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# pub.new.watch(User, :after_create)
|
34
|
+
# User.create # sends the user into the wire
|
35
|
+
def watch(*args)
|
36
|
+
entity = args.shift
|
37
|
+
observed << [entity, args]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the list of classes watched
|
41
|
+
def observed_classes
|
42
|
+
observed.collect(&:first).collect(&:to_s).uniq.collect(&:constantize)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Defines the methods that are called when the registered callbacks fire.
|
46
|
+
# For example, if the publisher is defined as follows:
|
47
|
+
#
|
48
|
+
# class MyDoorkeeper
|
49
|
+
# include Untided::Doorkeeper
|
50
|
+
#
|
51
|
+
# def initialize
|
52
|
+
# watch User, :after_create
|
53
|
+
# end
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# After calling MyDoorkeeper#define_callbacks the method
|
57
|
+
# _notify_untied__publisher_observer_for_after_create is created on User's
|
58
|
+
# model. This method is called when the after_create callback is fired.
|
59
|
+
def define_callbacks
|
60
|
+
observer = Untied::Publisher::Observer
|
61
|
+
observer_name = observer.name.underscore.gsub('/', '__')
|
62
|
+
|
63
|
+
observed.each do |(klass, callbacks)|
|
64
|
+
ActiveRecord::Callbacks::CALLBACKS.each do |callback|
|
65
|
+
next unless callbacks.include?(callback)
|
66
|
+
callback_meth = :"_notify_#{observer_name}_for_#{callback}"
|
67
|
+
unless klass.respond_to?(callback_meth)
|
68
|
+
klass.send(:define_method, callback_meth) do
|
69
|
+
observer.instance.send(callback, self)
|
70
|
+
end
|
71
|
+
klass.send(callback, callback_meth)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'active_model'
|
3
|
+
|
4
|
+
module Untied
|
5
|
+
class Event
|
6
|
+
include ActiveModel::Serializers::JSON
|
7
|
+
attr_accessor :name, :payload, :origin
|
8
|
+
|
9
|
+
def initialize(attrs)
|
10
|
+
@config = {
|
11
|
+
:name => "after_create",
|
12
|
+
:payload => nil,
|
13
|
+
:origin => nil
|
14
|
+
}.merge(attrs)
|
15
|
+
|
16
|
+
raise "You should inform the origin service" unless @config[:origin]
|
17
|
+
|
18
|
+
@name = @config.delete(:name)
|
19
|
+
@payload = @config.delete(:payload)
|
20
|
+
@origin = @config.delete(:origin)
|
21
|
+
end
|
22
|
+
|
23
|
+
def attributes
|
24
|
+
{ "name" => @name, "origin" => @origin, "payload" => @payload }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'active_model'
|
3
|
+
require 'active_record/observer'
|
4
|
+
require 'active_record/callbacks'
|
5
|
+
|
6
|
+
module Untied
|
7
|
+
module Publisher
|
8
|
+
class Observer < ActiveRecord::Observer
|
9
|
+
def initialize
|
10
|
+
Publisher.config.logger.info "Untied: Initializing publisher observer"
|
11
|
+
|
12
|
+
publisher.define_callbacks
|
13
|
+
observed = publisher.observed_classes
|
14
|
+
|
15
|
+
self.class.send(:define_method, :observed_classes, Proc.new { observed })
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(name, model, *args, &block)
|
20
|
+
if ActiveRecord::Callbacks::CALLBACKS.include?(name)
|
21
|
+
produce_event(name, model)
|
22
|
+
else
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def produce_event(callback, model)
|
30
|
+
e = Event.new(:name => callback, :payload => model,
|
31
|
+
:origin => Publisher.config.service_name)
|
32
|
+
producer.publish(e)
|
33
|
+
end
|
34
|
+
|
35
|
+
def producer
|
36
|
+
Producer.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def publisher
|
40
|
+
return @publisher if defined?(@publisher)
|
41
|
+
|
42
|
+
unless Publisher.config.doorkeeper
|
43
|
+
raise NameError.new "You should define a class which includes " + \
|
44
|
+
"Untied::Publisher::Doorkeeper and set it name to Untied::Publisher.config.doorkeeper."
|
45
|
+
end
|
46
|
+
@publisher = Publisher.config.doorkeeper.new
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'amqp'
|
3
|
+
|
4
|
+
module Untied
|
5
|
+
module Publisher
|
6
|
+
class Producer
|
7
|
+
def initialize(opts={})
|
8
|
+
@opts = {
|
9
|
+
:service_name => Publisher.config.service_name,
|
10
|
+
:deliver_messages => Publisher.config.deliver_messages,
|
11
|
+
:channel => nil,
|
12
|
+
}.merge(opts)
|
13
|
+
|
14
|
+
@routing_key = "untied.#{@opts[:service_name]}"
|
15
|
+
|
16
|
+
if !@opts[:deliver_messages]
|
17
|
+
Publisher.config.logger.info \
|
18
|
+
"AMQP.channel was not setted up because message delivering is disabled."
|
19
|
+
return
|
20
|
+
end
|
21
|
+
|
22
|
+
check_em_reactor
|
23
|
+
|
24
|
+
if AMQP.channel || @opts[:channel]
|
25
|
+
Publisher.config.logger.info "Using defined AMQP.channel"
|
26
|
+
@channel = AMQP.channel || @opts[:channel]
|
27
|
+
@exchange = @channel.topic("untied", :auto_delete => true)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def publish(event)
|
32
|
+
safe_publish(event)
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def safe_publish(e)
|
38
|
+
if @opts[:deliver_messages]
|
39
|
+
@exchange.publish(e.to_json, :routing_key => @routing_key) do
|
40
|
+
Publisher.config.logger.info \
|
41
|
+
"Publishing event #{e.inspect} with routing key #{@routing_key}"
|
42
|
+
end
|
43
|
+
else
|
44
|
+
Publisher.config.logger.info \
|
45
|
+
"The event #{ e.inspect} was not delivered. Try to set " + \
|
46
|
+
"Untied::Publisher.config.deliver_messages to true"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def check_em_reactor
|
51
|
+
if !defined?(EventMachine) || !EM.reactor_running?
|
52
|
+
raise "In order to use the producer you must be running inside an " + \
|
53
|
+
"eventmachine loop"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Untied
|
3
|
+
module Publisher
|
4
|
+
class Railtie < Rails::Railtie
|
5
|
+
config.after_initialize do
|
6
|
+
#FIXME don't know why should I do this.
|
7
|
+
ActiveRecord::Base.observers ||= []
|
8
|
+
config.active_record.observers ||= []
|
9
|
+
ActiveRecord::Base.observers << Untied::Publisher::Observer
|
10
|
+
config.active_record.observers << Untied::Publisher::Observer
|
11
|
+
Untied::Publisher::Observer.instance
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
module Untied
|
5
|
+
module Publisher
|
6
|
+
describe Doorkeeper do
|
7
|
+
before do
|
8
|
+
class ::Doorkeeper
|
9
|
+
include Untied::Publisher::Doorkeeper
|
10
|
+
end
|
11
|
+
end
|
12
|
+
let(:doorkeeper) { ::Doorkeeper.new }
|
13
|
+
|
14
|
+
context "#watch" do
|
15
|
+
it "should add observed classes to observed list" do
|
16
|
+
doorkeeper.watch(User, :after_create)
|
17
|
+
doorkeeper.observed.should == [[User, [:after_create]]]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "#define_callbacks" do
|
22
|
+
before do
|
23
|
+
doorkeeper.watch(User, :after_create)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should add methods to observable" do
|
27
|
+
doorkeeper.define_callbacks
|
28
|
+
User.new.should \
|
29
|
+
respond_to(:_notify_untied__publisher__observer_for_after_create)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should accept multiple classes" do
|
33
|
+
doorkeeper.watch(Post, :after_create)
|
34
|
+
doorkeeper.define_callbacks
|
35
|
+
|
36
|
+
User.new.should \
|
37
|
+
respond_to(:_notify_untied__publisher__observer_for_after_create)
|
38
|
+
Post.new.should \
|
39
|
+
respond_to(:_notify_untied__publisher__observer_for_after_create)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should accept multiple callbacks" do
|
43
|
+
doorkeeper.watch(Post, :after_create, :after_update)
|
44
|
+
doorkeeper.define_callbacks
|
45
|
+
|
46
|
+
Post.new.should \
|
47
|
+
respond_to(:_notify_untied__publisher__observer_for_after_update)
|
48
|
+
Post.new.should \
|
49
|
+
respond_to(:_notify_untied__publisher__observer_for_after_create)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "#observed_classes" do
|
54
|
+
it "should return a list of observed classes" do
|
55
|
+
doorkeeper.watch(Post, :after_create)
|
56
|
+
doorkeeper.watch(Post, :after_update)
|
57
|
+
doorkeeper.watch(User, :after_update)
|
58
|
+
|
59
|
+
doorkeeper.observed_classes.should == [Post, User]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/spec/event_spec.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
module Untied
|
5
|
+
module Publisher
|
6
|
+
describe Event do
|
7
|
+
before do
|
8
|
+
class Person
|
9
|
+
include ActiveModel::Serializers::JSON
|
10
|
+
attr_accessor :name
|
11
|
+
|
12
|
+
def initialize(attrs)
|
13
|
+
@name = attrs[:name]
|
14
|
+
end
|
15
|
+
|
16
|
+
def attributes
|
17
|
+
{ :name => name }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
let(:person) { Person.new(:name => "Guila") }
|
22
|
+
|
23
|
+
context ".new" do
|
24
|
+
it "should accept an event name and a payload" do
|
25
|
+
Event.
|
26
|
+
new(:name => :after_create, :payload => double('User'), :origin => "core").
|
27
|
+
should be_a Event
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should include the origin service name" do
|
31
|
+
Event.new(:name => :after_create, :payload => person, :origin => "core").
|
32
|
+
origin.should == "core"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "#to_json" do
|
37
|
+
it "should generte the correct json representation" do
|
38
|
+
event = {
|
39
|
+
:event => {
|
40
|
+
:name => :after_create,
|
41
|
+
:payload => person,
|
42
|
+
:origin => :core
|
43
|
+
}
|
44
|
+
}
|
45
|
+
Event.new(:name => :after_create, :payload => person, :origin => :core).
|
46
|
+
to_json == event.to_json
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
module Untied
|
5
|
+
module Publisher
|
6
|
+
describe Producer do
|
7
|
+
context "configuration" do
|
8
|
+
it "should use service name configured" do
|
9
|
+
opts = Producer.new.instance_variable_get(:@opts)
|
10
|
+
opts[:service_name].should == 'core'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should initilize producer" do
|
15
|
+
Producer.new.should be_a Producer
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should raise RuntimeError when trying to run without connection" do
|
19
|
+
expect {
|
20
|
+
Producer.new(:deliver_messages => true)
|
21
|
+
}.to raise_error
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should raise RuntimeError if EM is not running" do
|
25
|
+
mock_reactor_and_amqp do |c|
|
26
|
+
EM.stub("reactor_running?" => false)
|
27
|
+
expect {
|
28
|
+
Producer.new(:channel => c, :deliver_messages => true)
|
29
|
+
}.to raise_error
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "#publish" do
|
34
|
+
it "should call Channel#publish" do
|
35
|
+
mock_reactor_and_amqp do |channel|
|
36
|
+
e = Event.new(:name => "create", :payload => { :foo => 'bar' }, :origin => :core)
|
37
|
+
channel.topic.should_receive(:publish)
|
38
|
+
producer = Producer.new(:channel => channel, :deliver_messages => true)
|
39
|
+
producer.publish(e)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def mock_reactor_and_amqp
|
45
|
+
# Do nothing when calling start
|
46
|
+
Untied.stub(:start).and_return(nil)
|
47
|
+
# Simulate reactor running
|
48
|
+
EM.stub(:reactor_running?).and_return(true)
|
49
|
+
|
50
|
+
exchange = double('Exchange')
|
51
|
+
channel = double('Channel')
|
52
|
+
channel.stub(:topic).and_return(exchange)
|
53
|
+
|
54
|
+
yield(channel)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
module Untied
|
5
|
+
module Publisher
|
6
|
+
describe Publisher do
|
7
|
+
before do
|
8
|
+
class MyDoorkeeper
|
9
|
+
include Untied::Publisher::Doorkeeper
|
10
|
+
def initialize
|
11
|
+
watch(User, :after_create)
|
12
|
+
watch(User, :after_update)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
Untied::Publisher.config.doorkeeper = MyDoorkeeper
|
16
|
+
end
|
17
|
+
after { Untied::Publisher.config.doorkeeper = MyDoorkeeper }
|
18
|
+
|
19
|
+
context ".instance" do
|
20
|
+
it "should raise a friendly error when no doorkeeper is defined" do
|
21
|
+
Untied::Publisher.config.doorkeeper = nil
|
22
|
+
klass = Class.new(Observer)
|
23
|
+
expect {
|
24
|
+
klass.instance
|
25
|
+
}.to raise_error(/should define a class which includes/)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "ActiveRecord::Callbacks" do
|
30
|
+
it "should call the observer when the callback is fired" do
|
31
|
+
Observer.instance.should_receive(:after_create)
|
32
|
+
User.create
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should accept multiple callbacks even in different #watch" do
|
36
|
+
Observer.instance.should_receive(:after_create)
|
37
|
+
Observer.instance.should_receive(:after_update)
|
38
|
+
|
39
|
+
user = User.create(:name => "yo")
|
40
|
+
user.update_attributes({:name => "Ops!"})
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "#producer" do
|
45
|
+
it "should return the Producer" do
|
46
|
+
Observer.instance.should respond_to(:producer)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "when callbacks are fired" do
|
51
|
+
let(:producer) { double('Untied::Producer') }
|
52
|
+
|
53
|
+
ActiveRecord::Callbacks::CALLBACKS.each do |callback|
|
54
|
+
it "should publish the event on #{callback}" do
|
55
|
+
Observer.instance.stub(:producer).and_return(producer)
|
56
|
+
producer.should_receive(:publish).with(an_instance_of(Event))
|
57
|
+
Observer.instance.send(callback, double('User'))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
3
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
4
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
5
|
+
# loaded once.
|
6
|
+
#
|
7
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
8
|
+
|
9
|
+
require 'untied-publisher'
|
10
|
+
require 'ruby-debug'
|
11
|
+
require 'support/setup_ar_and_schema'
|
12
|
+
|
13
|
+
Untied::Publisher.configure do |config|
|
14
|
+
config.service_name = "core"
|
15
|
+
config.deliver_messages = false
|
16
|
+
end
|
17
|
+
|
18
|
+
RSpec.configure do |config|
|
19
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
20
|
+
config.run_all_when_everything_filtered = true
|
21
|
+
config.filter_run :focus
|
22
|
+
|
23
|
+
# Run specs in random order to surface order dependencies. If you find an
|
24
|
+
# order dependency and want to debug it, you can fix the order by providing
|
25
|
+
# the seed, which is printed after each run.
|
26
|
+
# --seed 1234
|
27
|
+
config.order = 'random'
|
28
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
module SetupActiveRecord
|
5
|
+
# Connection
|
6
|
+
ar_config = { :test => { :adapter => 'sqlite3', :database => ":memory:" } }
|
7
|
+
ActiveRecord::Base.configurations = ar_config
|
8
|
+
ActiveRecord::Base.
|
9
|
+
establish_connection(ActiveRecord::Base.configurations[:test])
|
10
|
+
|
11
|
+
# Schema
|
12
|
+
ActiveRecord::Schema.define do
|
13
|
+
create_table :posts, :force => true do |t|
|
14
|
+
t.string :title
|
15
|
+
end
|
16
|
+
create_table :users, :force => true do |t|
|
17
|
+
t.string :name
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Models
|
22
|
+
class ::User < ActiveRecord::Base; end
|
23
|
+
class ::Post < ActiveRecord::Base; end
|
24
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'untied-publisher/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "untied-publisher"
|
8
|
+
gem.version = Untied::Publisher::VERSION
|
9
|
+
gem.authors = ["Guilherme Cavalcanti"]
|
10
|
+
gem.email = ["guiocavalcanti@gmail.com"]
|
11
|
+
gem.description = "Provides the Publisher part of the Untied gem."
|
12
|
+
gem.summary = "Untied is a Observer Pattern implementation for distributed systems. Think as a cross-application ActiveRecord::Observer. This gem handles the publishing of events"
|
13
|
+
gem.homepage = "http://github.com/redu/untied"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(spec)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_development_dependency "rspec"
|
21
|
+
gem.add_development_dependency "sqlite3"
|
22
|
+
gem.add_development_dependency "rake"
|
23
|
+
|
24
|
+
gem.add_runtime_dependency "activerecord"
|
25
|
+
gem.add_runtime_dependency "amqp"
|
26
|
+
gem.add_runtime_dependency "configurable"
|
27
|
+
gem.add_runtime_dependency "json"
|
28
|
+
|
29
|
+
if RUBY_VERSION < "1.9"
|
30
|
+
gem.add_runtime_dependency "system_timer"
|
31
|
+
gem.add_development_dependency "ruby-debug"
|
32
|
+
else
|
33
|
+
gem.add_development_dependency "debugger"
|
34
|
+
end
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,216 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: untied-publisher
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Guilherme Cavalcanti
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-10-20 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: sqlite3
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rake
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
type: :development
|
61
|
+
version_requirements: *id003
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: activerecord
|
64
|
+
prerelease: false
|
65
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 3
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
type: :runtime
|
75
|
+
version_requirements: *id004
|
76
|
+
- !ruby/object:Gem::Dependency
|
77
|
+
name: amqp
|
78
|
+
prerelease: false
|
79
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
hash: 3
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
version: "0"
|
88
|
+
type: :runtime
|
89
|
+
version_requirements: *id005
|
90
|
+
- !ruby/object:Gem::Dependency
|
91
|
+
name: configurable
|
92
|
+
prerelease: false
|
93
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
hash: 3
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
version: "0"
|
102
|
+
type: :runtime
|
103
|
+
version_requirements: *id006
|
104
|
+
- !ruby/object:Gem::Dependency
|
105
|
+
name: json
|
106
|
+
prerelease: false
|
107
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
hash: 3
|
113
|
+
segments:
|
114
|
+
- 0
|
115
|
+
version: "0"
|
116
|
+
type: :runtime
|
117
|
+
version_requirements: *id007
|
118
|
+
- !ruby/object:Gem::Dependency
|
119
|
+
name: system_timer
|
120
|
+
prerelease: false
|
121
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
hash: 3
|
127
|
+
segments:
|
128
|
+
- 0
|
129
|
+
version: "0"
|
130
|
+
type: :runtime
|
131
|
+
version_requirements: *id008
|
132
|
+
- !ruby/object:Gem::Dependency
|
133
|
+
name: ruby-debug
|
134
|
+
prerelease: false
|
135
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
136
|
+
none: false
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
hash: 3
|
141
|
+
segments:
|
142
|
+
- 0
|
143
|
+
version: "0"
|
144
|
+
type: :development
|
145
|
+
version_requirements: *id009
|
146
|
+
description: Provides the Publisher part of the Untied gem.
|
147
|
+
email:
|
148
|
+
- guiocavalcanti@gmail.com
|
149
|
+
executables: []
|
150
|
+
|
151
|
+
extensions: []
|
152
|
+
|
153
|
+
extra_rdoc_files: []
|
154
|
+
|
155
|
+
files:
|
156
|
+
- .gitignore
|
157
|
+
- .rvmrc
|
158
|
+
- Gemfile
|
159
|
+
- LICENSE.txt
|
160
|
+
- README.md
|
161
|
+
- Rakefile
|
162
|
+
- lib/untied-publisher.rb
|
163
|
+
- lib/untied-publisher/config.rb
|
164
|
+
- lib/untied-publisher/doorkeeper.rb
|
165
|
+
- lib/untied-publisher/event.rb
|
166
|
+
- lib/untied-publisher/observer.rb
|
167
|
+
- lib/untied-publisher/producer.rb
|
168
|
+
- lib/untied-publisher/railtie.rb
|
169
|
+
- lib/untied-publisher/version.rb
|
170
|
+
- spec/doorkeeper_spec.rb
|
171
|
+
- spec/event_spec.rb
|
172
|
+
- spec/producer_spec.rb
|
173
|
+
- spec/publisher_observer_spec.rb
|
174
|
+
- spec/spec_helper.rb
|
175
|
+
- spec/support/setup_ar_and_schema.rb
|
176
|
+
- untied-publisher.gemspec
|
177
|
+
homepage: http://github.com/redu/untied
|
178
|
+
licenses: []
|
179
|
+
|
180
|
+
post_install_message:
|
181
|
+
rdoc_options: []
|
182
|
+
|
183
|
+
require_paths:
|
184
|
+
- lib
|
185
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
186
|
+
none: false
|
187
|
+
requirements:
|
188
|
+
- - ">="
|
189
|
+
- !ruby/object:Gem::Version
|
190
|
+
hash: 3
|
191
|
+
segments:
|
192
|
+
- 0
|
193
|
+
version: "0"
|
194
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
195
|
+
none: false
|
196
|
+
requirements:
|
197
|
+
- - ">="
|
198
|
+
- !ruby/object:Gem::Version
|
199
|
+
hash: 3
|
200
|
+
segments:
|
201
|
+
- 0
|
202
|
+
version: "0"
|
203
|
+
requirements: []
|
204
|
+
|
205
|
+
rubyforge_project:
|
206
|
+
rubygems_version: 1.8.24
|
207
|
+
signing_key:
|
208
|
+
specification_version: 3
|
209
|
+
summary: Untied is a Observer Pattern implementation for distributed systems. Think as a cross-application ActiveRecord::Observer. This gem handles the publishing of events
|
210
|
+
test_files:
|
211
|
+
- spec/doorkeeper_spec.rb
|
212
|
+
- spec/event_spec.rb
|
213
|
+
- spec/producer_spec.rb
|
214
|
+
- spec/publisher_observer_spec.rb
|
215
|
+
- spec/spec_helper.rb
|
216
|
+
- spec/support/setup_ar_and_schema.rb
|