cony 1.0.0
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/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +43 -0
- data/README.md +96 -0
- data/Rakefile +5 -0
- data/VERSION +1 -0
- data/bin/cony-receive +18 -0
- data/cony.gemspec +25 -0
- data/lib/cony/active_record.rb +48 -0
- data/lib/cony/amqp_connection_handler.rb +24 -0
- data/lib/cony.rb +15 -0
- data/spec/cony/active_record_spec.rb +53 -0
- data/spec/cony/amqp_connection_handler_spec.rb +51 -0
- data/spec/spec_helper.rb +17 -0
- metadata +130 -0
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
cony
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.9.3-p484
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
cony (1.0.0)
|
5
|
+
activesupport (>= 3)
|
6
|
+
bunny (~> 1.1.7)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
activesupport (4.1.0)
|
12
|
+
i18n (~> 0.6, >= 0.6.9)
|
13
|
+
json (~> 1.7, >= 1.7.7)
|
14
|
+
minitest (~> 5.1)
|
15
|
+
thread_safe (~> 0.1)
|
16
|
+
tzinfo (~> 1.1)
|
17
|
+
amq-protocol (1.9.2)
|
18
|
+
bunny (1.1.9)
|
19
|
+
amq-protocol (>= 1.9.2)
|
20
|
+
diff-lcs (1.2.5)
|
21
|
+
i18n (0.6.9)
|
22
|
+
json (1.8.1)
|
23
|
+
minitest (5.3.3)
|
24
|
+
rake (10.3.0)
|
25
|
+
rspec (2.14.1)
|
26
|
+
rspec-core (~> 2.14.0)
|
27
|
+
rspec-expectations (~> 2.14.0)
|
28
|
+
rspec-mocks (~> 2.14.0)
|
29
|
+
rspec-core (2.14.8)
|
30
|
+
rspec-expectations (2.14.5)
|
31
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
32
|
+
rspec-mocks (2.14.6)
|
33
|
+
thread_safe (0.3.3)
|
34
|
+
tzinfo (1.1.0)
|
35
|
+
thread_safe (~> 0.1)
|
36
|
+
|
37
|
+
PLATFORMS
|
38
|
+
ruby
|
39
|
+
|
40
|
+
DEPENDENCIES
|
41
|
+
cony!
|
42
|
+
rake (~> 10.0)
|
43
|
+
rspec (~> 2.12)
|
data/README.md
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# :rabbit: Cony [](https://travis-ci.org/ninech/cony)
|
2
|
+
|
3
|
+
Cony sends notifications about the lifecycle of your models via AMQP.
|
4
|
+
|
5
|
+
|
6
|
+
## Setup
|
7
|
+
|
8
|
+
### Rails 3 & 4
|
9
|
+
|
10
|
+
In Rails 3 and 4, add this to your Gemfile and run the bundle command.
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem 'cony'
|
14
|
+
```
|
15
|
+
|
16
|
+
To configure the AMQP-Settings, use an initializer (e.g.
|
17
|
+
`config/initializers/cony.rb`) with the following content.
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
Cony.configure do |config|
|
21
|
+
config.amqp = {
|
22
|
+
host: 'localhost',
|
23
|
+
exchange: 'organization.application'
|
24
|
+
}
|
25
|
+
# config.durable = false
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
|
30
|
+
## Getting Started
|
31
|
+
|
32
|
+
To enable the notifications for a model, you just need to include the
|
33
|
+
corresponding class. For `ActiveRecord` use the following snippet.
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
class ExampleModel < ActiveRecord::Base
|
37
|
+
include Cony::ActiveRecord
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
## Message Format
|
42
|
+
|
43
|
+
The routing key for the messages have a format of
|
44
|
+
`model_name_with_underscore.mutation.action`.
|
45
|
+
|
46
|
+
It will append the id of the model and the detected changes to the payload of the message.
|
47
|
+
|
48
|
+
### Create
|
49
|
+
|
50
|
+
A create for a `Example::Model` model will have a routing key of
|
51
|
+
`example/model.mutation.create`.
|
52
|
+
|
53
|
+
The sent JSON structure will look like this:
|
54
|
+
|
55
|
+
```json
|
56
|
+
{
|
57
|
+
"id": 1337,
|
58
|
+
"changes": [
|
59
|
+
{ "name": { "old": null, "new": "value" } },
|
60
|
+
{ "description": { "old": null, "new": "value" } }
|
61
|
+
]
|
62
|
+
}
|
63
|
+
```
|
64
|
+
|
65
|
+
|
66
|
+
### Update
|
67
|
+
|
68
|
+
An update for a `Example::Model` model will have a routing key of
|
69
|
+
`example/model.mutation.update`.
|
70
|
+
|
71
|
+
The sent JSON structure will look like this:
|
72
|
+
|
73
|
+
```json
|
74
|
+
{
|
75
|
+
"id": 1337,
|
76
|
+
"changes": [
|
77
|
+
{ "name": { "old": "old-value", "new": "new-value" } }
|
78
|
+
]
|
79
|
+
}
|
80
|
+
```
|
81
|
+
|
82
|
+
|
83
|
+
### Destroy
|
84
|
+
|
85
|
+
A destroy event for a `Example::Model` model will have a routing key of
|
86
|
+
`example/model.mutation.destroy`.
|
87
|
+
|
88
|
+
The sent JSON structure will look like this:
|
89
|
+
|
90
|
+
```json
|
91
|
+
{
|
92
|
+
"id": 1337,
|
93
|
+
"changes": []
|
94
|
+
}
|
95
|
+
```
|
96
|
+
|
data/Rakefile
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/bin/cony-receive
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'amqp'
|
4
|
+
|
5
|
+
EventMachine.run do
|
6
|
+
AMQP.connect do |connection|
|
7
|
+
channel = AMQP::Channel.new(connection)
|
8
|
+
# topic exchange name can be any string
|
9
|
+
exchange = channel.topic("cony-test", durable: true)
|
10
|
+
|
11
|
+
# Subscribers.
|
12
|
+
channel.queue("", :exclusive => true) do |queue|
|
13
|
+
queue.bind(exchange, :routing_key => "#").subscribe do |headers, payload|
|
14
|
+
puts payload.inspect
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/cony.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
$:.push File.expand_path('../lib', __FILE__)
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'cony'
|
7
|
+
s.version = File.read(File.expand_path('../VERSION', __FILE__)).strip
|
8
|
+
s.authors = ['Raffael Schmid']
|
9
|
+
s.email = ['info@nine.ch']
|
10
|
+
s.homepage = 'http://github.com/ninech/'
|
11
|
+
s.license = 'MIT'
|
12
|
+
s.summary = 'Automatically sends notifications via AMQP when a model has been changed.'
|
13
|
+
s.description = 'Automatically sends notifications via AMQP when a model has been changed.'
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ['lib']
|
19
|
+
|
20
|
+
s.add_development_dependency 'rake', '~> 10.0'
|
21
|
+
s.add_development_dependency 'rspec', '~> 2.12'
|
22
|
+
|
23
|
+
s.add_runtime_dependency 'activesupport', '>= 3'
|
24
|
+
s.add_runtime_dependency 'bunny', '~> 1.1.7'
|
25
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'active_support/core_ext/string/inflections'
|
2
|
+
|
3
|
+
require 'cony'
|
4
|
+
require 'cony/amqp_connection_handler'
|
5
|
+
|
6
|
+
module Cony
|
7
|
+
module ActiveRecord
|
8
|
+
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
included do
|
12
|
+
after_create :cony_send_create_notify
|
13
|
+
after_update :cony_send_update_notify
|
14
|
+
after_destroy :cony_send_destroy_notify
|
15
|
+
end
|
16
|
+
|
17
|
+
def cony_send_create_notify
|
18
|
+
publish(:create)
|
19
|
+
end
|
20
|
+
|
21
|
+
def cony_send_update_notify
|
22
|
+
publish(:update)
|
23
|
+
end
|
24
|
+
|
25
|
+
def cony_send_destroy_notify
|
26
|
+
publish(:destroy)
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
private
|
31
|
+
def publish(type)
|
32
|
+
amqp_connection.publish(
|
33
|
+
{id: self.id, changes: cony_changes},
|
34
|
+
"#{self.class.name.underscore}.mutation.#{type}")
|
35
|
+
end
|
36
|
+
|
37
|
+
def amqp_connection
|
38
|
+
@amqp_connection ||= Cony::AMQPConnectionHandler.new(Cony.config.amqp)
|
39
|
+
end
|
40
|
+
|
41
|
+
def cony_changes
|
42
|
+
changes.map do |name, change|
|
43
|
+
{name => {old: change.first, new: change.last}}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'bunny'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Cony
|
5
|
+
class AMQPConnectionHandler
|
6
|
+
|
7
|
+
def initialize(config)
|
8
|
+
@config = config
|
9
|
+
end
|
10
|
+
|
11
|
+
def publish(message, routing_key)
|
12
|
+
Bunny.run(@config) do |connection|
|
13
|
+
exchange = connection.exchange(@config[:exchange], type: :topic, durable: Cony.config.durable)
|
14
|
+
exchange.publish(message.to_json,
|
15
|
+
key: routing_key,
|
16
|
+
mandatory: false,
|
17
|
+
immediate: false,
|
18
|
+
persistent: Cony.config.durable,
|
19
|
+
content_type: 'application/json')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
data/lib/cony.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'active_support/core_ext/object/deep_dup'
|
2
|
+
require 'active_support/configurable'
|
3
|
+
|
4
|
+
require 'cony/active_record'
|
5
|
+
|
6
|
+
module Cony
|
7
|
+
include ActiveSupport::Configurable
|
8
|
+
|
9
|
+
defaults = {
|
10
|
+
durable: false
|
11
|
+
}
|
12
|
+
|
13
|
+
self.config.merge! defaults.deep_dup
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'cony/active_record'
|
4
|
+
|
5
|
+
describe Cony::ActiveRecord do
|
6
|
+
|
7
|
+
let(:amqp_connection) { double('Cony::AMQPConnectionHandler') }
|
8
|
+
let(:id) { 1337 }
|
9
|
+
let(:active_record_changes) { {name: ['old', 'new']} }
|
10
|
+
let(:cony_changes) { [{name: {old: 'old', new: 'new'}}] }
|
11
|
+
|
12
|
+
let(:model) do
|
13
|
+
eval <<-EOF
|
14
|
+
Class.new do
|
15
|
+
def self.after_create(callback); end
|
16
|
+
def self.after_update(callback); end
|
17
|
+
def self.after_destroy(callback); end
|
18
|
+
def self.name; "Anonymaus::Klass"; end
|
19
|
+
def id; #{id}; end
|
20
|
+
def changes; #{active_record_changes}; end
|
21
|
+
|
22
|
+
include Cony::ActiveRecord
|
23
|
+
end
|
24
|
+
EOF
|
25
|
+
end
|
26
|
+
|
27
|
+
before do
|
28
|
+
Cony::AMQPConnectionHandler.stub(:new).and_return(amqp_connection)
|
29
|
+
end
|
30
|
+
|
31
|
+
subject { model.new }
|
32
|
+
|
33
|
+
describe '#cony_send_create_notify' do
|
34
|
+
it 'uses the amqp connection to send the notify' do
|
35
|
+
amqp_connection.should_receive(:publish).with({id: id, changes: cony_changes}, 'anonymaus/klass.mutation.create')
|
36
|
+
subject.cony_send_create_notify
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#cony_send_update_notify' do
|
41
|
+
it 'uses the amqp connection to send the notify' do
|
42
|
+
amqp_connection.should_receive(:publish).with({id: id, changes: cony_changes}, 'anonymaus/klass.mutation.update')
|
43
|
+
subject.cony_send_update_notify
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#cony_send_destroy_notify' do
|
48
|
+
it 'uses the amqp connection to send the notify' do
|
49
|
+
amqp_connection.should_receive(:publish).with({id: id, changes: cony_changes}, 'anonymaus/klass.mutation.destroy')
|
50
|
+
subject.cony_send_destroy_notify
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'cony/amqp_connection_handler'
|
4
|
+
|
5
|
+
describe Cony::AMQPConnectionHandler do
|
6
|
+
|
7
|
+
let(:config) { {exchange: 'bunny-tests'} }
|
8
|
+
let(:handler) { Cony::AMQPConnectionHandler.new(config) }
|
9
|
+
let(:message) { 'Bunnies are connies' }
|
10
|
+
let(:routing_key) { 'bunny.info' }
|
11
|
+
let(:exchange_double) do
|
12
|
+
double('Exchange double').tap do |exc|
|
13
|
+
exc.stub(:publish)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
let(:connection_double) do
|
17
|
+
double('Connection double').tap do |conn|
|
18
|
+
conn.stub(:exchange).and_return(exchange_double)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
subject { handler }
|
23
|
+
|
24
|
+
before do
|
25
|
+
Bunny.stub(:run).and_yield(connection_double)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'uses bunny to publish a message' do
|
29
|
+
Bunny.should_receive(:run)
|
30
|
+
subject.publish(message, routing_key)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'configures the exchange correctly' do
|
34
|
+
connection_double.should_receive(:exchange).with('bunny-tests', type: :topic, durable: false)
|
35
|
+
subject.publish(message, routing_key)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'publishes the message' do
|
39
|
+
publish_options = {
|
40
|
+
key: routing_key,
|
41
|
+
mandatory: false,
|
42
|
+
immediate: false,
|
43
|
+
persistent: false,
|
44
|
+
content_type: 'application/json',
|
45
|
+
}
|
46
|
+
exchange_double.should_receive(:publish)
|
47
|
+
.with('"Bunnies are connies"', publish_options)
|
48
|
+
subject.publish(message, routing_key)
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
9
|
+
config.run_all_when_everything_filtered = true
|
10
|
+
config.filter_run :focus
|
11
|
+
|
12
|
+
# Run specs in random order to surface order dependencies. If you find an
|
13
|
+
# order dependency and want to debug it, you can fix the order by providing
|
14
|
+
# the seed, which is printed after each run.
|
15
|
+
# --seed 1234
|
16
|
+
config.order = 'random'
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cony
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Raffael Schmid
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-04-16 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '10.0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '10.0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '2.12'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '2.12'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: activesupport
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '3'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: bunny
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.1.7
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.1.7
|
78
|
+
description: Automatically sends notifications via AMQP when a model has been changed.
|
79
|
+
email:
|
80
|
+
- info@nine.ch
|
81
|
+
executables:
|
82
|
+
- cony-receive
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files: []
|
85
|
+
files:
|
86
|
+
- .rspec
|
87
|
+
- .ruby-gemset
|
88
|
+
- .ruby-version
|
89
|
+
- Gemfile
|
90
|
+
- Gemfile.lock
|
91
|
+
- README.md
|
92
|
+
- Rakefile
|
93
|
+
- VERSION
|
94
|
+
- bin/cony-receive
|
95
|
+
- cony.gemspec
|
96
|
+
- lib/cony.rb
|
97
|
+
- lib/cony/active_record.rb
|
98
|
+
- lib/cony/amqp_connection_handler.rb
|
99
|
+
- spec/cony/active_record_spec.rb
|
100
|
+
- spec/cony/amqp_connection_handler_spec.rb
|
101
|
+
- spec/spec_helper.rb
|
102
|
+
homepage: http://github.com/ninech/
|
103
|
+
licenses:
|
104
|
+
- MIT
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
require_paths:
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
none: false
|
111
|
+
requirements:
|
112
|
+
- - ! '>='
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
117
|
+
requirements:
|
118
|
+
- - ! '>='
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
requirements: []
|
122
|
+
rubyforge_project:
|
123
|
+
rubygems_version: 1.8.23
|
124
|
+
signing_key:
|
125
|
+
specification_version: 3
|
126
|
+
summary: Automatically sends notifications via AMQP when a model has been changed.
|
127
|
+
test_files:
|
128
|
+
- spec/cony/active_record_spec.rb
|
129
|
+
- spec/cony/amqp_connection_handler_spec.rb
|
130
|
+
- spec/spec_helper.rb
|