action_pubsub 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +99 -0
- data/Rakefile +6 -0
- data/action_pubsub.gemspec +42 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/action_pubsub.rb +81 -0
- data/lib/action_pubsub/active_record.rb +11 -0
- data/lib/action_pubsub/active_record/events.rb +12 -0
- data/lib/action_pubsub/active_record/events/changed.rb +29 -0
- data/lib/action_pubsub/active_record/events/created.rb +29 -0
- data/lib/action_pubsub/active_record/events/destroyed.rb +29 -0
- data/lib/action_pubsub/active_record/events/updated.rb +29 -0
- data/lib/action_pubsub/active_record/on_change.rb +54 -0
- data/lib/action_pubsub/active_record/publishable.rb +47 -0
- data/lib/action_pubsub/active_record/subscriber.rb +90 -0
- data/lib/action_pubsub/active_record/subscription.rb +27 -0
- data/lib/action_pubsub/config.rb +22 -0
- data/lib/action_pubsub/event.rb +31 -0
- data/lib/action_pubsub/exchange_registry.rb +15 -0
- data/lib/action_pubsub/queue.rb +4 -0
- data/lib/action_pubsub/serialization.rb +7 -0
- data/lib/action_pubsub/serialization/marshal.rb +17 -0
- data/lib/action_pubsub/subscriber.rb +39 -0
- data/lib/action_pubsub/version.rb +3 -0
- metadata +311 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: d4a0328f1cb7e3a3a0155c4b0957cd3dc566eaed
|
|
4
|
+
data.tar.gz: be1336cd5d743520ff2c97f6a6b3034357d197f3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ff793a319ee02b8d4cb58d14a0ce0cf3b6146e1211e6d3d5f20e73c559ff5b46174cc062dd4b49c7e0835072b23bcb13fa71014e076af0d22c254da5776ca6e9
|
|
7
|
+
data.tar.gz: b89adff807fb7cb898ad689df79d6dac365623f31f7e838330e56ea617175e2147e0d7117e75a72b75ff8048f8ec5d11bace7a537156379317b7013b4d5afbf7
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015 Jason Ayre
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# ActionPubsub
|
|
2
|
+
|
|
3
|
+
In process, concurrent observers, loosely modeled after rabbitmq
|
|
4
|
+
|
|
5
|
+
## Example
|
|
6
|
+
|
|
7
|
+
Lets say we have a blog app, and our posts have comments enabled on a case by case basis.
|
|
8
|
+
When someone leaves a new comment, we want to blast out an email to everyone who
|
|
9
|
+
has subscribed to recieve new posts
|
|
10
|
+
|
|
11
|
+
In our comments model:
|
|
12
|
+
|
|
13
|
+
``` ruby
|
|
14
|
+
module Blogger
|
|
15
|
+
class Comment < ::ActiveRecord::Base
|
|
16
|
+
include ::ActionPubsub::ActiveRecord::Publishable
|
|
17
|
+
publish_as "blogger/comment"
|
|
18
|
+
publishable_actions :created, :updated
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Our subscriber:
|
|
24
|
+
``` ruby
|
|
25
|
+
module Blogger
|
|
26
|
+
class CommentSubscriber < ::ActionPubsub::ActiveRecord::Subscriber
|
|
27
|
+
#this is the "exchange" we want all of the events we are watching for, to be scoped to
|
|
28
|
+
#i.e. blogger/comment/created will end up being the fully qualified path for on :create
|
|
29
|
+
subscribe_to "blogger/comment"
|
|
30
|
+
|
|
31
|
+
self.concurrency = 5
|
|
32
|
+
|
|
33
|
+
on :created, :if => lambda{ |record| record.post.has_comments_enabled? } do
|
|
34
|
+
#on initialize right now subscriber instance will get a resource instance variable
|
|
35
|
+
#populated for free pertaining to the record in focus, i.e. a comment record
|
|
36
|
+
resource.post.commenters.by_new_comment_notifications_enabled.each do |commenter|
|
|
37
|
+
NewCommentNotificationMailer.deliver(resource, commenter)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### What is the advantage of this pattern?
|
|
45
|
+
|
|
46
|
+
The advantage is it makes your app incredibly reactive. Rather than have to fatten
|
|
47
|
+
up your controller with logic that does not belong, or have some service object that
|
|
48
|
+
does 20 things in sequence, it allows everything to be decoupled, only subscribe to
|
|
49
|
+
the things that are relevant. It also enforces the single responsibility principle, by
|
|
50
|
+
allowing these subscribers to exist in potentially different engines, or areas of your
|
|
51
|
+
application, and do nothing but react to the events occurring within the system.
|
|
52
|
+
|
|
53
|
+
### Callbacks
|
|
54
|
+
|
|
55
|
+
Sure, we could use callbacks, but do we care about any of that if the record has
|
|
56
|
+
not been commited to the database? (No we should not). Unless you use
|
|
57
|
+
after_commit :on => :create, then your callbacks will attempt to run even if record hasn't been committed,
|
|
58
|
+
unless at some point an error or false was returned.
|
|
59
|
+
|
|
60
|
+
So as a best practice, we only broadcast the creation after it's been commited.
|
|
61
|
+
|
|
62
|
+
This also allows for complex chains of events to occur, with no knowledge of each other,
|
|
63
|
+
but that do their one job, and do it well. If that subscriber happens to create a new record,
|
|
64
|
+
We can then subscribe to that models creation somewhere else, settings up the building blocks of a pipeline.
|
|
65
|
+
|
|
66
|
+
## Installation
|
|
67
|
+
|
|
68
|
+
Add this line to your application's Gemfile:
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
gem 'action_pubsub'
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
And then execute:
|
|
75
|
+
|
|
76
|
+
$ bundle
|
|
77
|
+
|
|
78
|
+
Or install it yourself as:
|
|
79
|
+
|
|
80
|
+
$ gem install action_pubsub
|
|
81
|
+
|
|
82
|
+
## Usage
|
|
83
|
+
|
|
84
|
+
TODO: Work in progress
|
|
85
|
+
|
|
86
|
+
## Development
|
|
87
|
+
|
|
88
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
89
|
+
|
|
90
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
91
|
+
|
|
92
|
+
## Contributing
|
|
93
|
+
|
|
94
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/action_pubsub.
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -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 'action_pubsub/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "action_pubsub"
|
|
8
|
+
spec.version = ActionPubsub::VERSION
|
|
9
|
+
spec.authors = ["Jason Ayre"]
|
|
10
|
+
spec.email = ["jasonayre@gmail.com"]
|
|
11
|
+
|
|
12
|
+
spec.summary = %q{In process, async, concurrent pubsub}
|
|
13
|
+
spec.description = %q{In process, async, concurrent pubsub}
|
|
14
|
+
spec.homepage = "http://github.com/jasonayre/action_pubsub"
|
|
15
|
+
spec.license = "MIT"
|
|
16
|
+
|
|
17
|
+
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
|
18
|
+
# delete this section to allow pushing this gem to any host.
|
|
19
|
+
|
|
20
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
21
|
+
spec.bindir = "exe"
|
|
22
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
23
|
+
spec.require_paths = ["lib"]
|
|
24
|
+
|
|
25
|
+
spec.add_dependency "active_attr"
|
|
26
|
+
spec.add_dependency "activesupport"
|
|
27
|
+
spec.add_dependency "concurrent-ruby"
|
|
28
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
|
29
|
+
spec.add_development_dependency 'guard', '~> 2'
|
|
30
|
+
spec.add_development_dependency 'guard-rspec', '~> 4'
|
|
31
|
+
spec.add_development_dependency 'guard-bundler', '~> 2'
|
|
32
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
33
|
+
spec.add_development_dependency "rspec"
|
|
34
|
+
spec.add_development_dependency "rspec-pride"
|
|
35
|
+
spec.add_development_dependency 'rspec-its', '~> 1'
|
|
36
|
+
spec.add_development_dependency 'rspec-collection_matchers', '~> 1'
|
|
37
|
+
spec.add_development_dependency 'rb-fsevent'
|
|
38
|
+
spec.add_development_dependency "simplecov"
|
|
39
|
+
spec.add_development_dependency 'terminal-notifier-guard'
|
|
40
|
+
spec.add_development_dependency "pry"
|
|
41
|
+
spec.add_development_dependency "pry-nav"
|
|
42
|
+
end
|
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "action_pubsub"
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
require "pry"
|
|
11
|
+
Pry.start
|
|
12
|
+
|
|
13
|
+
# require "irb"
|
|
14
|
+
# IRB.start
|
data/bin/setup
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
require "action_pubsub/version"
|
|
2
|
+
require "active_support/all"
|
|
3
|
+
require "concurrent"
|
|
4
|
+
require "active_attr"
|
|
5
|
+
require "concurrent/lazy_register"
|
|
6
|
+
require "concurrent/actor"
|
|
7
|
+
require "concurrent/agent"
|
|
8
|
+
|
|
9
|
+
module ActionPubsub
|
|
10
|
+
extend ::ActiveSupport::Autoload
|
|
11
|
+
|
|
12
|
+
autoload :ActiveRecord
|
|
13
|
+
autoload :Config
|
|
14
|
+
autoload :Event
|
|
15
|
+
autoload :Exchange
|
|
16
|
+
autoload :ExchangeRegistry
|
|
17
|
+
autoload :Publish
|
|
18
|
+
autoload :Publishable
|
|
19
|
+
autoload :Logging
|
|
20
|
+
autoload :Subscriber
|
|
21
|
+
autoload :Queue
|
|
22
|
+
|
|
23
|
+
@configuration ||= ::ActionPubsub::Config.new
|
|
24
|
+
|
|
25
|
+
def self.event_count
|
|
26
|
+
@event_count ||= ::Concurrent::Agent.new(0)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.exchange_registry
|
|
30
|
+
@exchange_registry ||= ::ActionPubsub::ExchangeRegistry.new
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
@some_registry = ::Concurrent::LazyRegister.new
|
|
34
|
+
|
|
35
|
+
def self.destination_tuple_from_path(path_string)
|
|
36
|
+
segs = path_string.split("/")
|
|
37
|
+
worker_index = segs.pop
|
|
38
|
+
action = segs.pop
|
|
39
|
+
|
|
40
|
+
[segs.join("/"), action, worker_index]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.destination_tuple_from_sender_path(path_string)
|
|
44
|
+
segs = path_string.split("/")
|
|
45
|
+
action = segs.pop
|
|
46
|
+
[segs.join("/"), action]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.symbolize_routing_key(routing_key)
|
|
50
|
+
:"#{routing_key.split('.').join('_')}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.publish_event(routing_key, event)
|
|
54
|
+
#need to loop through exchanges and publish to them
|
|
55
|
+
#maybe there is a better way to do this?
|
|
56
|
+
exchange_hash = ::ActionPubsub.exchanges[routing_key].instance_variable_get("@data").value
|
|
57
|
+
queue_names = exchange_hash.keys
|
|
58
|
+
|
|
59
|
+
queue_names.each do |queue_name|
|
|
60
|
+
exchange_registry[routing_key][queue_name] << serialize_event(event)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def self.serialize_event(event)
|
|
65
|
+
event
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def self.deserialize_event(event)
|
|
69
|
+
event
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class << self
|
|
73
|
+
attr_accessor :configuration
|
|
74
|
+
alias_method :config, :configuration
|
|
75
|
+
alias_method :exchanges, :exchange_registry
|
|
76
|
+
|
|
77
|
+
delegate :register_queue, :to => :exchange_registry
|
|
78
|
+
delegate :register_channel, :to => :exchange_registry
|
|
79
|
+
delegate :register_exchange, :to => :exchange_registry
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module ActionPubsub
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
module Events
|
|
4
|
+
module Changed
|
|
5
|
+
extend ::ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
after_commit :publish_changed_event, :on => :update
|
|
9
|
+
|
|
10
|
+
routing_key = [exchange_prefix, "changed"].join("/")
|
|
11
|
+
::ActionPubsub.register_exchange(routing_key)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def publish_changed_event
|
|
15
|
+
routing_key = [self.class.exchange_prefix, "changed"].join("/")
|
|
16
|
+
|
|
17
|
+
record_changed_event = ::ActionPubsub::Event.new(
|
|
18
|
+
:topic => routing_key,
|
|
19
|
+
:record => self
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
::ActiveRecord::Base.connection_pool.with_connection do
|
|
23
|
+
::ActionPubsub.publish_event(routing_key, record_changed_event)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module ActionPubsub
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
module Events
|
|
4
|
+
module Created
|
|
5
|
+
extend ::ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
after_commit :publish_created_event, :on => :create
|
|
9
|
+
|
|
10
|
+
routing_key = [exchange_prefix, "created"].join("/")
|
|
11
|
+
::ActionPubsub.register_exchange(routing_key)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def publish_created_event
|
|
15
|
+
routing_key = [self.class.exchange_prefix, "created"].join("/")
|
|
16
|
+
|
|
17
|
+
record_created_event = ::ActionPubsub::Event.new(
|
|
18
|
+
:topic => routing_key,
|
|
19
|
+
:record => self
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
::ActiveRecord::Base.connection_pool.with_connection do
|
|
23
|
+
::ActionPubsub.publish_event(routing_key, record_created_event)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module ActionPubsub
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
module Events
|
|
4
|
+
module Destroyed
|
|
5
|
+
extend ::ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
after_commit :publish_destroyed_event, :on => :create
|
|
9
|
+
|
|
10
|
+
routing_key = [exchange_prefix, "destroyed"].join("/")
|
|
11
|
+
::ActionPubsub.register_exchange(routing_key)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def publish_destroyed_event
|
|
15
|
+
routing_key = [self.class.exchange_prefix, "destroyed"].join("/")
|
|
16
|
+
|
|
17
|
+
record_destroyed_event = ::ActionPubsub::Event.new(
|
|
18
|
+
:topic => routing_key,
|
|
19
|
+
:record => self
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
::ActiveRecord::Base.connection_pool.with_connection do
|
|
23
|
+
::ActionPubsub.publish_event(routing_key, record_destroyed_event)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module ActionPubsub
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
module Events
|
|
4
|
+
module Updated
|
|
5
|
+
extend ::ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
after_commit :publish_updated_event, :on => :update
|
|
9
|
+
|
|
10
|
+
routing_key = [exchange_prefix, "updated"].join("/")
|
|
11
|
+
::ActionPubsub.register_exchange(routing_key)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def publish_updated_event
|
|
15
|
+
routing_key = [self.class.exchange_prefix, "updated"].join("/")
|
|
16
|
+
|
|
17
|
+
record_updated_event = ::ActionPubsub::Event.new(
|
|
18
|
+
:topic => routing_key,
|
|
19
|
+
:record => self
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
::ActiveRecord::Base.connection_pool.with_connection do
|
|
23
|
+
::ActionPubsub.publish_event(routing_key, record_updated_event)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module ActionPubsub
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
module OnChange
|
|
4
|
+
extend ::ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
class << self
|
|
8
|
+
attr_accessor :_on_change_watches
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
@_on_change_watches = {}.with_indifferent_access
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module ClassMethods
|
|
15
|
+
def on_change(*attribute_names, &block)
|
|
16
|
+
options = attribute_names.extract_options!
|
|
17
|
+
|
|
18
|
+
attribute_names.each do |attribute|
|
|
19
|
+
@_on_change_watches[attribute] = { :block => block }
|
|
20
|
+
@_on_change_watches[attribute].merge!(options)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
on :changed do |record|
|
|
24
|
+
record.previous_changes.each_pair do |k,vals|
|
|
25
|
+
if self.class.react_to_changed?(record, k, vals)
|
|
26
|
+
old_value, new_value = vals
|
|
27
|
+
self.instance_exec(new_value, old_value, &self.class.watched_attributes[k][:block])
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def react_to_changed?(resource, attribute_name, value)
|
|
34
|
+
old_value, new_value = value
|
|
35
|
+
return false unless watching_attribute?(attribute_name)
|
|
36
|
+
result = true
|
|
37
|
+
result &&= old_value == watched_attributes[attribute_name][:from] if watched_attributes[attribute_name].key?(:from)
|
|
38
|
+
result &&= new_value == watched_attributes[attribute_name][:to] if watched_attributes[attribute_name].key?(:to)
|
|
39
|
+
result &&= watched_attributes[attribute_name][:if].call(resource) if watched_attributes[attribute_name].key?(:if)
|
|
40
|
+
result &&= !watched_attributes[attribute_name][:unless].call(resource) if watched_attributes[attribute_name].key?(:unless)
|
|
41
|
+
return result
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def watching_attribute?(attribute_name)
|
|
45
|
+
watched_attributes.key?(attribute_name)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def watched_attributes
|
|
49
|
+
self.instance_variable_get(:@_on_change_watches)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module ActionPubsub
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
module Publishable
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
PUBLISHABLE_EVENTS = {
|
|
7
|
+
:changed => ::ActionPubsub::ActiveRecord::Events::Changed,
|
|
8
|
+
:updated => ::ActionPubsub::ActiveRecord::Events::Updated,
|
|
9
|
+
:created => ::ActionPubsub::ActiveRecord::Events::Created,
|
|
10
|
+
:destroyed => ::ActionPubsub::ActiveRecord::Events::Destroyed
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
included do
|
|
14
|
+
include ::ActiveModel::Dirty unless ancestors.include?(::ActiveModel::Dirty)
|
|
15
|
+
|
|
16
|
+
class_attribute :exchange_prefix
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
attr_accessor :_publishable_actions
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def attributes_hash
|
|
24
|
+
hash = self.as_json
|
|
25
|
+
hash.merge!(:changes => previous_changes) if previous_changes && hash
|
|
26
|
+
hash.symbolize_keys! if hash
|
|
27
|
+
hash
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
module ClassMethods
|
|
33
|
+
def publish_as(_exchange_prefix)
|
|
34
|
+
self.exchange_prefix = _exchange_prefix
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def publishable_actions(*actions)
|
|
38
|
+
@_publishable_actions = actions
|
|
39
|
+
|
|
40
|
+
actions.each do |action|
|
|
41
|
+
include PUBLISHABLE_EVENTS[action] unless ancestors.include?(PUBLISHABLE_EVENTS[action])
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
module ActionPubsub
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
class Subscriber
|
|
4
|
+
class_attribute :concurrency,
|
|
5
|
+
:event_triggered_count,
|
|
6
|
+
:event_processed_count,
|
|
7
|
+
:event_failed_count,
|
|
8
|
+
:observed_exchanges,
|
|
9
|
+
:messages_received_count,
|
|
10
|
+
:messages_processed_count,
|
|
11
|
+
:queue,
|
|
12
|
+
:reactions,
|
|
13
|
+
:subscription
|
|
14
|
+
|
|
15
|
+
self.concurrency = 1
|
|
16
|
+
|
|
17
|
+
attr_accessor :resource, :current_event
|
|
18
|
+
|
|
19
|
+
#the indirection here with the "subscription" dynamically created class, is for the sake
|
|
20
|
+
#of making subscribers immutable and not storing instance state.
|
|
21
|
+
#i.e. subscription is the actual actor, which just instantiates this subscriber class
|
|
22
|
+
#and performs the task it needs to
|
|
23
|
+
def self.inherited(subklass)
|
|
24
|
+
subklass.subscription = subklass.const_set("Subscription", ::Class.new(::ActionPubsub::ActiveRecord::Subscription))
|
|
25
|
+
subklass.subscription.subscriber = subklass
|
|
26
|
+
subklass.reactions = {}
|
|
27
|
+
subklass.observed_exchanges = ::Set.new
|
|
28
|
+
subklass.event_triggered_count = ::Concurrent::AtomicFixnum.new(0)
|
|
29
|
+
subklass.event_failed_count = ::Concurrent::AtomicFixnum.new(0)
|
|
30
|
+
subklass.event_processed_count = ::Concurrent::AtomicFixnum.new(0)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.on(event_name, **options, &block)
|
|
34
|
+
reactions[event_name] = {}.tap do |hash|
|
|
35
|
+
hash[:block] = block
|
|
36
|
+
hash[:conditions] = options.extract!(:if, :unless)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
register_reaction_to_event(event_name)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.increment_event_failed_count!
|
|
43
|
+
self.event_failed_count.increment
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.increment_event_processed_count!
|
|
47
|
+
self.event_processed_count.increment
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.increment_event_triggered_count!
|
|
51
|
+
self.event_triggered_count.increment
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.subscribe_to(*exchanges)
|
|
55
|
+
exchanges.each{ |exchange| self.observed_exchanges << exchange }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.react?(event_name, reaction, record)
|
|
59
|
+
return false if reaction[:block].blank?
|
|
60
|
+
return true if reaction[:conditions].blank?
|
|
61
|
+
result = true
|
|
62
|
+
result &&= !reaction[:conditions][:unless].call(record) if reaction[:conditions].key?(:unless)
|
|
63
|
+
result &&= reaction[:conditions][:if].call(record) if reaction[:conditions].key?(:if)
|
|
64
|
+
return result
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def self.register_reaction_to_event(event_name)
|
|
68
|
+
observed_exchanges.each do |exchange_prefix|
|
|
69
|
+
target_exchange = [exchange_prefix, event_name].join("/")
|
|
70
|
+
subscriber_key = name.underscore
|
|
71
|
+
queue_key = [target_exchange, subscriber_key].join("/")
|
|
72
|
+
|
|
73
|
+
::ActionPubsub.register_queue(target_exchange, subscriber_key)
|
|
74
|
+
|
|
75
|
+
self.concurrency.times do |i|
|
|
76
|
+
self.subscription.spawn("#{queue_key}/#{i}") do
|
|
77
|
+
self.subscription.bind_subscription(target_exchange, subscriber_key)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
### Instance Methods ###
|
|
84
|
+
def initialize(record, event:nil)
|
|
85
|
+
@resource = record
|
|
86
|
+
@current_event = event if event
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module ActionPubsub
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
class Subscription < ::Concurrent::Actor::Utils::AdHoc
|
|
4
|
+
class_attribute :subscriber
|
|
5
|
+
|
|
6
|
+
def self.bind_subscription(target_exchange, subscriber_key)
|
|
7
|
+
::ActionPubsub.exchange_registry[target_exchange][subscriber_key] << :subscribe
|
|
8
|
+
-> message {
|
|
9
|
+
::ActiveRecord::Base.connection_pool.with_connection do
|
|
10
|
+
message = ::ActionPubsub.deserialize_event(message)
|
|
11
|
+
reaction = self.class.subscriber.reactions[message["action"]]
|
|
12
|
+
record = message["record"]
|
|
13
|
+
|
|
14
|
+
if self.class.subscriber.react?(message["action"], reaction, record)
|
|
15
|
+
self.class.subscriber.increment_event_triggered_count!
|
|
16
|
+
|
|
17
|
+
subscriber_instance = self.class.subscriber.new(record)
|
|
18
|
+
subscriber_instance.instance_exec(record, &reaction[:block])
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
self.class.bind_subscription(target_exchange, subscriber_key)
|
|
22
|
+
end
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'active_support/ordered_options'
|
|
2
|
+
|
|
3
|
+
module ActionPubsub
|
|
4
|
+
class Config < ::ActiveSupport::InheritableOptions
|
|
5
|
+
def initialize(*args)
|
|
6
|
+
super(*args)
|
|
7
|
+
|
|
8
|
+
self[:debug] = false
|
|
9
|
+
self[:serializer] = nil
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def debug=(val)
|
|
13
|
+
::Concurrent.use_stdlib_logger(Logger::DEBUG) if val
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def serializer=(val)
|
|
17
|
+
if val && val.ancestors.include?(::ActiveSupport::Concern)
|
|
18
|
+
::ActionPubsub.include(val)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'active_model'
|
|
2
|
+
|
|
3
|
+
module ActionPubsub
|
|
4
|
+
class Event
|
|
5
|
+
include ::ActiveAttr::Model
|
|
6
|
+
include ::ActiveModel::AttributeMethods
|
|
7
|
+
|
|
8
|
+
attribute :id
|
|
9
|
+
attribute :action
|
|
10
|
+
attribute :context
|
|
11
|
+
attribute :meta
|
|
12
|
+
attribute :name
|
|
13
|
+
attribute :occured_at
|
|
14
|
+
attribute :record
|
|
15
|
+
attribute :subject
|
|
16
|
+
attribute :topic
|
|
17
|
+
|
|
18
|
+
#attributes have to be set for purposes of marshaling
|
|
19
|
+
def initialize(topic:, record:nil, context: nil, **options)
|
|
20
|
+
self[:topic] = topic
|
|
21
|
+
self[:action] = topic.split("/").pop.to_sym
|
|
22
|
+
self[:meta] = options[:meta] || {}
|
|
23
|
+
self[:name] = topic
|
|
24
|
+
self[:record] = record
|
|
25
|
+
self[:id] = ::SecureRandom.hex
|
|
26
|
+
self[:subject] = options[:subject] || record.try(:class).try(:name)
|
|
27
|
+
self[:context] = context if context
|
|
28
|
+
self[:occured_at] ||= ::Time.now.to_i
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module ActionPubsub
|
|
2
|
+
class ExchangeRegistry < ::Concurrent::LazyRegister
|
|
3
|
+
def register_queue(exchange_name, subscriber_name)
|
|
4
|
+
register_exchange(exchange_name) unless key?(exchange_name)
|
|
5
|
+
exchange_hash = self[exchange_name].instance_variable_get("@data").value
|
|
6
|
+
exchange_keys = exchange_hash.keys
|
|
7
|
+
queue_name = [exchange_name, subscriber_name].join("/")
|
|
8
|
+
self[exchange_name].add(subscriber_name) { ::ActionPubsub::Queue.spawn(queue_name) }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def register_exchange(exchange_name)
|
|
12
|
+
add(exchange_name) { ::Concurrent::LazyRegister.new }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module ActionPubsub
|
|
2
|
+
module Serialization
|
|
3
|
+
module Marshal
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
module Marshal
|
|
7
|
+
def self.serialize_event(event)
|
|
8
|
+
::Marshal.dump(super(event))
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.deserialize_event(event)
|
|
12
|
+
::Marshal.load(super(event))
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
### NOTE: Don't use this. Only the active record subscriber is working ATM.
|
|
2
|
+
module ActionPubsub
|
|
3
|
+
class Subscriber < ::Concurrent::Actor::Utils::AdHoc
|
|
4
|
+
class_attribute :concurrency, :queue, :exchange_prefix, :watches
|
|
5
|
+
self.concurrency = 1
|
|
6
|
+
|
|
7
|
+
def self.inherited(subklass)
|
|
8
|
+
subklass.watches = {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.register_event_watcher(event_name)
|
|
12
|
+
target_exchange = [exchange_prefix, event_name].join("/")
|
|
13
|
+
subscriber_key = name.underscore
|
|
14
|
+
queue_key = [target_exchange, subscriber_key].join("/")
|
|
15
|
+
|
|
16
|
+
::ActionPubsub.register_queue(target_exchange, subscriber_key)
|
|
17
|
+
|
|
18
|
+
self.concurrency.times do |i|
|
|
19
|
+
spawn("#{queue_key}/#{i}") do
|
|
20
|
+
bind_subscription(target_exchange, subscriber_key)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.on(event_name, &block)
|
|
26
|
+
watches[event_name] = block
|
|
27
|
+
register_event_watcher(event_name)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.bind_subscription(target_exchange, subscriber_key)
|
|
31
|
+
::ActionPubsub.exchange_registry[target_exchange][subscriber_key] << :subscribe
|
|
32
|
+
-> message {
|
|
33
|
+
self.class.watches[message["action"]].call(message["record"])
|
|
34
|
+
|
|
35
|
+
self.class.bind_subscription(target_exchange, subscriber_key)
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: action_pubsub
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Jason Ayre
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2015-07-14 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: activesupport
|
|
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: concurrent-ruby
|
|
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: bundler
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '1.10'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '1.10'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: guard
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '2'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '2'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: guard-rspec
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '4'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '4'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: guard-bundler
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - "~>"
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '2'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - "~>"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '2'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: rake
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - "~>"
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '10.0'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - "~>"
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '10.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: rspec-its
|
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
|
156
|
+
requirements:
|
|
157
|
+
- - "~>"
|
|
158
|
+
- !ruby/object:Gem::Version
|
|
159
|
+
version: '1'
|
|
160
|
+
type: :development
|
|
161
|
+
prerelease: false
|
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
+
requirements:
|
|
164
|
+
- - "~>"
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: '1'
|
|
167
|
+
- !ruby/object:Gem::Dependency
|
|
168
|
+
name: rspec-collection_matchers
|
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
|
170
|
+
requirements:
|
|
171
|
+
- - "~>"
|
|
172
|
+
- !ruby/object:Gem::Version
|
|
173
|
+
version: '1'
|
|
174
|
+
type: :development
|
|
175
|
+
prerelease: false
|
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
177
|
+
requirements:
|
|
178
|
+
- - "~>"
|
|
179
|
+
- !ruby/object:Gem::Version
|
|
180
|
+
version: '1'
|
|
181
|
+
- !ruby/object:Gem::Dependency
|
|
182
|
+
name: rb-fsevent
|
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
|
184
|
+
requirements:
|
|
185
|
+
- - ">="
|
|
186
|
+
- !ruby/object:Gem::Version
|
|
187
|
+
version: '0'
|
|
188
|
+
type: :development
|
|
189
|
+
prerelease: false
|
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
191
|
+
requirements:
|
|
192
|
+
- - ">="
|
|
193
|
+
- !ruby/object:Gem::Version
|
|
194
|
+
version: '0'
|
|
195
|
+
- !ruby/object:Gem::Dependency
|
|
196
|
+
name: simplecov
|
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
|
198
|
+
requirements:
|
|
199
|
+
- - ">="
|
|
200
|
+
- !ruby/object:Gem::Version
|
|
201
|
+
version: '0'
|
|
202
|
+
type: :development
|
|
203
|
+
prerelease: false
|
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
205
|
+
requirements:
|
|
206
|
+
- - ">="
|
|
207
|
+
- !ruby/object:Gem::Version
|
|
208
|
+
version: '0'
|
|
209
|
+
- !ruby/object:Gem::Dependency
|
|
210
|
+
name: terminal-notifier-guard
|
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
|
212
|
+
requirements:
|
|
213
|
+
- - ">="
|
|
214
|
+
- !ruby/object:Gem::Version
|
|
215
|
+
version: '0'
|
|
216
|
+
type: :development
|
|
217
|
+
prerelease: false
|
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
219
|
+
requirements:
|
|
220
|
+
- - ">="
|
|
221
|
+
- !ruby/object:Gem::Version
|
|
222
|
+
version: '0'
|
|
223
|
+
- !ruby/object:Gem::Dependency
|
|
224
|
+
name: pry
|
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
|
226
|
+
requirements:
|
|
227
|
+
- - ">="
|
|
228
|
+
- !ruby/object:Gem::Version
|
|
229
|
+
version: '0'
|
|
230
|
+
type: :development
|
|
231
|
+
prerelease: false
|
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
233
|
+
requirements:
|
|
234
|
+
- - ">="
|
|
235
|
+
- !ruby/object:Gem::Version
|
|
236
|
+
version: '0'
|
|
237
|
+
- !ruby/object:Gem::Dependency
|
|
238
|
+
name: pry-nav
|
|
239
|
+
requirement: !ruby/object:Gem::Requirement
|
|
240
|
+
requirements:
|
|
241
|
+
- - ">="
|
|
242
|
+
- !ruby/object:Gem::Version
|
|
243
|
+
version: '0'
|
|
244
|
+
type: :development
|
|
245
|
+
prerelease: false
|
|
246
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
247
|
+
requirements:
|
|
248
|
+
- - ">="
|
|
249
|
+
- !ruby/object:Gem::Version
|
|
250
|
+
version: '0'
|
|
251
|
+
description: In process, async, concurrent pubsub
|
|
252
|
+
email:
|
|
253
|
+
- jasonayre@gmail.com
|
|
254
|
+
executables: []
|
|
255
|
+
extensions: []
|
|
256
|
+
extra_rdoc_files: []
|
|
257
|
+
files:
|
|
258
|
+
- ".gitignore"
|
|
259
|
+
- ".rspec"
|
|
260
|
+
- ".travis.yml"
|
|
261
|
+
- Gemfile
|
|
262
|
+
- LICENSE.txt
|
|
263
|
+
- README.md
|
|
264
|
+
- Rakefile
|
|
265
|
+
- action_pubsub.gemspec
|
|
266
|
+
- bin/console
|
|
267
|
+
- bin/setup
|
|
268
|
+
- lib/action_pubsub.rb
|
|
269
|
+
- lib/action_pubsub/active_record.rb
|
|
270
|
+
- lib/action_pubsub/active_record/events.rb
|
|
271
|
+
- lib/action_pubsub/active_record/events/changed.rb
|
|
272
|
+
- lib/action_pubsub/active_record/events/created.rb
|
|
273
|
+
- lib/action_pubsub/active_record/events/destroyed.rb
|
|
274
|
+
- lib/action_pubsub/active_record/events/updated.rb
|
|
275
|
+
- lib/action_pubsub/active_record/on_change.rb
|
|
276
|
+
- lib/action_pubsub/active_record/publishable.rb
|
|
277
|
+
- lib/action_pubsub/active_record/subscriber.rb
|
|
278
|
+
- lib/action_pubsub/active_record/subscription.rb
|
|
279
|
+
- lib/action_pubsub/config.rb
|
|
280
|
+
- lib/action_pubsub/event.rb
|
|
281
|
+
- lib/action_pubsub/exchange_registry.rb
|
|
282
|
+
- lib/action_pubsub/queue.rb
|
|
283
|
+
- lib/action_pubsub/serialization.rb
|
|
284
|
+
- lib/action_pubsub/serialization/marshal.rb
|
|
285
|
+
- lib/action_pubsub/subscriber.rb
|
|
286
|
+
- lib/action_pubsub/version.rb
|
|
287
|
+
homepage: http://github.com/jasonayre/action_pubsub
|
|
288
|
+
licenses:
|
|
289
|
+
- MIT
|
|
290
|
+
metadata: {}
|
|
291
|
+
post_install_message:
|
|
292
|
+
rdoc_options: []
|
|
293
|
+
require_paths:
|
|
294
|
+
- lib
|
|
295
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
296
|
+
requirements:
|
|
297
|
+
- - ">="
|
|
298
|
+
- !ruby/object:Gem::Version
|
|
299
|
+
version: '0'
|
|
300
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
301
|
+
requirements:
|
|
302
|
+
- - ">="
|
|
303
|
+
- !ruby/object:Gem::Version
|
|
304
|
+
version: '0'
|
|
305
|
+
requirements: []
|
|
306
|
+
rubyforge_project:
|
|
307
|
+
rubygems_version: 2.4.5
|
|
308
|
+
signing_key:
|
|
309
|
+
specification_version: 4
|
|
310
|
+
summary: In process, async, concurrent pubsub
|
|
311
|
+
test_files: []
|