replay 0.1.1 → 0.2.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 +4 -4
- data/lib/replay.rb +1 -0
- data/lib/replay/backends.rb +4 -4
- data/lib/replay/configuration.rb +7 -1
- data/lib/replay/event_declarations.rb +1 -2
- data/lib/replay/event_decorator.rb +5 -1
- data/lib/replay/event_envelope.rb +19 -0
- data/lib/replay/observer.rb +1 -1
- data/lib/replay/publisher.rb +26 -18
- data/lib/replay/repository.rb +9 -8
- data/lib/replay/repository/configuration.rb +7 -1
- data/lib/replay/rspec.rb +1 -1
- data/lib/replay/subscription_manager.rb +12 -6
- data/lib/replay/subscriptions.rb +6 -2
- data/lib/replay/test.rb +3 -1
- data/lib/replay/test/test_event_stream.rb +7 -4
- data/lib/replay/version.rb +1 -1
- data/proofs/replay/publisher_proof.rb +6 -3
- data/proofs/replay/repository_configuration_proof.rb +3 -1
- data/proofs/replay/subscriber_manager_proof.rb +3 -3
- data/proofs/replay/test_proof.rb +5 -1
- data/replay.gemspec +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d2ba86d3b1d0e597548c8fb5a807f0a5e9f98313
|
4
|
+
data.tar.gz: 940e276d8721b831afd454f55c2dd7d574d4ab5a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3a8d3363c8b40dc87669b5d99934a1321eaa789cce0b18520cf1f2bf9e8228368256a8842ff0f1dad0093b416dee0af2f067a80fb3c096e72373f5be81a4a2cf
|
7
|
+
data.tar.gz: 854dd7ae22f150b007853c8b3d39e2c697dbece4d2c7a421a1c93767243e983ed4bf2591217a4b5662c7d987b7e1ca38141bdf19d20e20c2aa4e82cb5f8a1783
|
data/lib/replay.rb
CHANGED
@@ -32,6 +32,7 @@ require 'replay/inflector'
|
|
32
32
|
require 'replay/events'
|
33
33
|
require 'replay/event_decorator'
|
34
34
|
require 'replay/event_declarations'
|
35
|
+
require 'replay/event_envelope'
|
35
36
|
require 'replay/publisher'
|
36
37
|
require 'replay/subscription_manager'
|
37
38
|
require 'replay/subscriptions'
|
data/lib/replay/backends.rb
CHANGED
@@ -15,8 +15,8 @@ module Replay
|
|
15
15
|
def initialize
|
16
16
|
@store = {}
|
17
17
|
end
|
18
|
-
def self.published(
|
19
|
-
instance.published(
|
18
|
+
def self.published(envelope)
|
19
|
+
instance.published(envelope)
|
20
20
|
end
|
21
21
|
|
22
22
|
def self.clear
|
@@ -27,9 +27,9 @@ module Replay
|
|
27
27
|
@store = {}
|
28
28
|
end
|
29
29
|
|
30
|
-
def published(
|
30
|
+
def published(envelope)
|
31
31
|
@store[stream_id] ||= []
|
32
|
-
@store[stream_id] <<
|
32
|
+
@store[stream_id] << envelope
|
33
33
|
end
|
34
34
|
|
35
35
|
def event_stream(stream_id)
|
data/lib/replay/configuration.rb
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
module Replay
|
2
2
|
class Configuration
|
3
3
|
attr_accessor :storage
|
4
|
+
attr_writer :reject_load_on_empty_stream
|
4
5
|
|
5
6
|
def storage=(stores)
|
6
7
|
stores = [stores] unless stores.is_a?(Array)
|
7
8
|
@storage = stores
|
8
9
|
end
|
10
|
+
|
11
|
+
def reject_load_on_empty_stream?
|
12
|
+
@reject_load_on_empty_stream ||= true
|
13
|
+
@reject_load_on_empty_stream
|
14
|
+
end
|
9
15
|
end
|
10
|
-
end
|
16
|
+
end
|
@@ -15,6 +15,7 @@ module Replay
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
18
|
+
|
18
19
|
def method_missing(name, *args)
|
19
20
|
declare_event(self, name, args.first)
|
20
21
|
end
|
@@ -22,13 +23,11 @@ module Replay
|
|
22
23
|
def declare_event(base, name, props)
|
23
24
|
klass = Class.new do
|
24
25
|
include Replay::EventDecorator
|
25
|
-
attribute :published_at, Time, default: lambda{|p,a| Time.now}
|
26
26
|
values do
|
27
27
|
props.keys.each do |prop|
|
28
28
|
attribute prop, props[prop]
|
29
29
|
end
|
30
30
|
end
|
31
|
-
include Virtus::Equalizer.new("#{name.to_s} equalizer", (self.attribute_set.map(&:name) - [:published_at]).map(&:to_s))
|
32
31
|
end
|
33
32
|
base.const_set name, klass
|
34
33
|
end
|
@@ -4,8 +4,12 @@ module Replay
|
|
4
4
|
def self.included(base)
|
5
5
|
base.class_eval do
|
6
6
|
include Virtus.value_object
|
7
|
+
attr_accessor :metadata
|
7
8
|
def inspect
|
8
|
-
"#{self.
|
9
|
+
"#{self.type}: #{self.attributes.map{|k, v| "#{k.to_s} = #{v.to_s}"}.join(", ")}"
|
10
|
+
end
|
11
|
+
def type
|
12
|
+
self.class.to_s
|
9
13
|
end
|
10
14
|
end
|
11
15
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Replay
|
2
|
+
class EventEnvelope
|
3
|
+
attr_reader :stream_id, :event, :metadata
|
4
|
+
def initialize(stream_id, event, metadata = {})
|
5
|
+
@metadata = metadata
|
6
|
+
@event = event
|
7
|
+
@stream_id = stream_id
|
8
|
+
end
|
9
|
+
|
10
|
+
def type
|
11
|
+
@event.type
|
12
|
+
end
|
13
|
+
|
14
|
+
def method_missing(method, *args)
|
15
|
+
return @event.send(method, args) if @event.respond_to?(method)
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/replay/observer.rb
CHANGED
@@ -21,7 +21,7 @@ module Replay
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def published(stream_id, event)
|
24
|
-
blk = @observer_blocks[Replay::Inflector.underscore(event.
|
24
|
+
blk = @observer_blocks[Replay::Inflector.underscore(event.type)]
|
25
25
|
blk.call(stream_id, event, binding) if blk
|
26
26
|
end
|
27
27
|
end
|
data/lib/replay/publisher.rb
CHANGED
@@ -13,28 +13,36 @@ module Replay
|
|
13
13
|
return apply([events], raise_unhandled) unless events.is_a?(Array)
|
14
14
|
|
15
15
|
events.each do |event|
|
16
|
-
|
17
|
-
raise UnhandledEventError.new "event #{event.
|
18
|
-
self.
|
16
|
+
apply_method = apply_method_for(event.class)
|
17
|
+
raise UnhandledEventError.new "event #{event.type} is not handled by #{self.class.name}" if (!respond_to?(apply_method) && raise_unhandled)
|
18
|
+
self.send(apply_method, event)
|
19
19
|
end
|
20
20
|
return self
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
24
|
-
self.class.
|
23
|
+
def apply_method_for(klass)
|
24
|
+
self.class.apply_method_for(klass)
|
25
25
|
end
|
26
|
-
protected :block_for
|
27
26
|
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
private :apply_method_for
|
28
|
+
|
29
|
+
def publish(event, metadata={})
|
30
|
+
return publish([event]) unless event.is_a?(Array)
|
31
|
+
event.each do |evt|
|
32
|
+
metadata = ({:published_at => Time.now}.merge!(metadata))
|
33
|
+
apply(evt)
|
34
|
+
subscription_manager.notify_subscribers(to_stream_id, evt, metadata)
|
35
|
+
end
|
31
36
|
return self
|
32
37
|
end
|
33
38
|
|
34
|
-
|
35
39
|
def to_stream_id
|
36
|
-
raise Replay::UndefinedKeyError.new("No key attribute defined for #{self.
|
37
|
-
self.send(self.
|
40
|
+
raise Replay::UndefinedKeyError.new("No key attribute defined for #{self.type}") unless self.key_attr
|
41
|
+
self.send(self.key_attr).to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
def key_attr
|
45
|
+
self.class.key_attr
|
38
46
|
end
|
39
47
|
|
40
48
|
module ClassMethods
|
@@ -47,17 +55,17 @@ module Replay
|
|
47
55
|
end
|
48
56
|
|
49
57
|
def apply(event_type, &block)
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
def block_for(event_type)
|
54
|
-
blk = @application_blocks[stringify_class(event_type)]
|
55
|
-
return blk
|
58
|
+
method_name = apply_method_for(event_type)
|
59
|
+
define_method method_name, block
|
56
60
|
end
|
57
61
|
|
58
62
|
def stringify_class(klass)
|
59
63
|
Replay::Inflector.underscore(klass.to_s.dup)
|
60
64
|
end
|
65
|
+
|
66
|
+
def apply_method_for(klass)
|
67
|
+
"handle_#{stringify_class(klass).gsub(".", "_")}"
|
68
|
+
end
|
61
69
|
end
|
62
70
|
end
|
63
71
|
end
|
data/lib/replay/repository.rb
CHANGED
@@ -19,15 +19,15 @@ module Replay
|
|
19
19
|
repository_load(klass, stream_id, options)
|
20
20
|
end
|
21
21
|
|
22
|
-
def repository_load(
|
23
|
-
|
24
|
-
if
|
22
|
+
def repository_load(klass_or_instance, stream_id, options={})
|
23
|
+
stream = store.event_stream(stream_id)
|
24
|
+
if stream.empty? && configuration.reject_load_on_empty_stream?
|
25
25
|
raise Errors::EventStreamNotFoundError.new("Could not find any events for stream identifier #{stream_id}") if options[:create].nil?
|
26
26
|
end
|
27
27
|
|
28
|
-
obj = prepare(klass.new)
|
29
|
-
obj.create(stream_id) if options[:create] &&
|
30
|
-
obj.apply(
|
28
|
+
obj = klass_or_instance.is_a?(Class) ? prepare(klass.new, options[:metadata]) : klass_or_instance
|
29
|
+
obj.create(stream_id) if options[:create] && stream.empty?
|
30
|
+
obj.apply(stream.map(&:event))
|
31
31
|
|
32
32
|
obj
|
33
33
|
end
|
@@ -40,7 +40,8 @@ module Replay
|
|
40
40
|
new_obj
|
41
41
|
end
|
42
42
|
|
43
|
-
def prepare(obj)
|
43
|
+
def prepare(obj, metadata={})
|
44
|
+
obj.subscription_manager = SubscriptionManager.new(configuration.logger, metadata)
|
44
45
|
@configuration.subscribers.each do |subscriber|
|
45
46
|
obj.add_subscriber(subscriber)
|
46
47
|
end
|
@@ -48,7 +49,7 @@ module Replay
|
|
48
49
|
end
|
49
50
|
|
50
51
|
def configure
|
51
|
-
@configuration ||= Configuration.
|
52
|
+
@configuration ||= Configuration.default
|
52
53
|
yield @configuration
|
53
54
|
@configuration
|
54
55
|
end
|
@@ -1,8 +1,14 @@
|
|
1
1
|
module Replay
|
2
2
|
module Repository
|
3
3
|
class Configuration
|
4
|
-
|
4
|
+
attr_accessor :logger
|
5
|
+
def initialize(logger = nil)
|
5
6
|
@default_subscribers =[]
|
7
|
+
@logger = logger
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.default
|
11
|
+
self.new(Replay.logger)
|
6
12
|
end
|
7
13
|
|
8
14
|
def add_default_subscriber(subscriber)
|
data/lib/replay/rspec.rb
CHANGED
@@ -43,7 +43,7 @@ RSpec::Matchers.define :publish do |expected_event|
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def event_interpretation(event)
|
46
|
-
"#{event.
|
46
|
+
"#{event.type} [#{event.attributes.reject{|k,v| v.nil?}.keys.map{|k| "#{k.to_s} = #{event.attributes[k]}"}.join(", ")}]"
|
47
47
|
end
|
48
48
|
|
49
49
|
end
|
@@ -1,26 +1,32 @@
|
|
1
1
|
module Replay
|
2
2
|
class SubscriptionManager
|
3
|
-
|
4
|
-
def initialize(logger = nil)
|
3
|
+
def initialize(logger = nil, session_metadata = {})
|
5
4
|
@subscribers = []
|
6
5
|
@logger = logger
|
6
|
+
@session_metadata = session_metadata
|
7
7
|
end
|
8
8
|
|
9
9
|
def add_subscriber(subscriber)
|
10
10
|
if subscriber.respond_to?(:published)
|
11
|
-
@subscribers << subscriber
|
11
|
+
@subscribers << subscriber
|
12
12
|
else
|
13
13
|
raise Replay::InvalidSubscriberError.new(subscriber)
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
-
def notify_subscribers(stream_id, event)
|
17
|
+
def notify_subscribers(stream_id, event, metadata = {})
|
18
18
|
@subscribers.each do |sub|
|
19
19
|
begin
|
20
|
-
|
20
|
+
meta = metadata.merge(@session_metadata)
|
21
|
+
sub.published(EventEnvelope.new(stream_id, event, meta))
|
22
|
+
#sub.published(stream_id, event, metadata)
|
21
23
|
rescue Exception => e
|
22
24
|
#hmmmm
|
23
|
-
|
25
|
+
if @logger
|
26
|
+
@logger.error "exception in event subscriber #{sub.class.to_s} while handling event stream #{stream_id} #{event.inspect}: #{e.message}\n#{e.backtrace.join("\n")}"
|
27
|
+
else
|
28
|
+
raise e
|
29
|
+
end
|
24
30
|
end
|
25
31
|
end
|
26
32
|
end
|
data/lib/replay/subscriptions.rb
CHANGED
@@ -4,12 +4,16 @@ module Replay
|
|
4
4
|
@subscription_manager ||= Replay::SubscriptionManager.new(Replay.logger)
|
5
5
|
end
|
6
6
|
|
7
|
+
def subscription_manager=(sm)
|
8
|
+
@subscription_manager = sm
|
9
|
+
end
|
10
|
+
|
7
11
|
def add_subscriber(subscriber)
|
8
12
|
subscription_manager.add_subscriber(subscriber)
|
9
13
|
end
|
10
14
|
|
11
|
-
def published(stream_id, event)
|
12
|
-
@subscription_manager.notify_subscribers(stream_id, event)
|
15
|
+
def published(stream_id, event, metadata)
|
16
|
+
@subscription_manager.notify_subscribers(stream_id, event, metadata)
|
13
17
|
end
|
14
18
|
end
|
15
19
|
end
|
data/lib/replay/test.rb
CHANGED
@@ -61,8 +61,10 @@ Replay::EventDecorator.module_exec do
|
|
61
61
|
relevant_attrs_match = event.attributes.reject{|k,v| v.nil?}
|
62
62
|
relevant_attrs_self = self.attributes.reject{|k,v| v.nil?}
|
63
63
|
|
64
|
+
keys_self = relevant_attrs_self.keys
|
64
65
|
if (relevant_attrs_self.keys - relevant_attrs_match.keys).empty?
|
65
|
-
|
66
|
+
#publication time is not considered part of the event data
|
67
|
+
if keys_self.reject{|k| event[k] == self[k]}.any?
|
66
68
|
return false
|
67
69
|
else
|
68
70
|
return true
|
@@ -1,19 +1,22 @@
|
|
1
|
+
require 'replay/test'
|
1
2
|
module Replay
|
2
3
|
class TestEventStream
|
4
|
+
include EventExaminer
|
5
|
+
attr_accessor :events
|
3
6
|
|
4
7
|
def initialize
|
5
8
|
@events = []
|
6
9
|
end
|
7
|
-
def
|
8
|
-
@events <<
|
10
|
+
def published(event_envelope)
|
11
|
+
@events << event_envelope
|
9
12
|
end
|
10
13
|
|
11
14
|
def published_event?(event)
|
12
|
-
@events.detect{|e| e
|
15
|
+
@events.detect{|e| e.event==event}
|
13
16
|
end
|
14
17
|
|
15
18
|
def published?(stream_id, event)
|
16
|
-
@events.detect{|e| e ==
|
19
|
+
@events.detect{|e| e.stream_id == stream_id && e.event == event}
|
17
20
|
end
|
18
21
|
end
|
19
22
|
end
|
data/lib/replay/version.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require_relative "../proofs_init.rb"
|
2
2
|
require 'replay/test'
|
3
|
+
require 'replay/test/test_event_stream'
|
3
4
|
|
4
5
|
class ReplayTest
|
5
6
|
include Replay::Publisher
|
@@ -29,8 +30,10 @@ end
|
|
29
30
|
|
30
31
|
module ReplayTest::Proof
|
31
32
|
def sets_publish_time
|
33
|
+
ts=Replay::TestEventStream.new
|
34
|
+
add_subscriber(ts)
|
32
35
|
publish SomeEvent(pid: 123)
|
33
|
-
events.last.published_at != nil && (Time.now - events.last.published_at) <
|
36
|
+
ts.events.last.metadata[:published_at] != nil && (Time.now - ts.events.last.metadata[:published_at]) < 1
|
34
37
|
end
|
35
38
|
|
36
39
|
def published_at_not_considered_in_equality
|
@@ -72,7 +75,7 @@ module ReplayTest::Proof
|
|
72
75
|
|
73
76
|
def subscribers_receive_events
|
74
77
|
sub = Class.new do
|
75
|
-
def published(
|
78
|
+
def published(envelope)
|
76
79
|
@published = true
|
77
80
|
end
|
78
81
|
def published?; @published; end
|
@@ -154,7 +157,7 @@ proof "Returns self from publish" do
|
|
154
157
|
r.prove{ publish([]) == self}
|
155
158
|
end
|
156
159
|
|
157
|
-
proof "
|
160
|
+
proof "adds the publish time to event metadata" do
|
158
161
|
r = ReplayTest.new
|
159
162
|
r.prove{ sets_publish_time }
|
160
163
|
end
|
@@ -2,9 +2,11 @@ require_relative "../proofs_init.rb"
|
|
2
2
|
require 'replay/test'
|
3
3
|
|
4
4
|
class Subscriber
|
5
|
-
def published(
|
5
|
+
def published(envelope); end
|
6
6
|
end
|
7
7
|
|
8
|
+
title "Repository configuration"
|
9
|
+
|
8
10
|
module Replay::Repository::Configuration::Proof
|
9
11
|
def can_configure_store?
|
10
12
|
self.store = :memory
|
@@ -14,9 +14,9 @@ module Replay::SubscriptionManager::Proof
|
|
14
14
|
def sub_gets_notified
|
15
15
|
sub = Class.new do
|
16
16
|
attr_accessor :stream, :event
|
17
|
-
def published(
|
18
|
-
self.stream =
|
19
|
-
self.event = event
|
17
|
+
def published(envelope)
|
18
|
+
self.stream = envelope.stream_id
|
19
|
+
self.event = envelope.event
|
20
20
|
end
|
21
21
|
end.new
|
22
22
|
add_subscriber(sub)
|
data/proofs/replay/test_proof.rb
CHANGED
@@ -21,8 +21,12 @@ proof "fuzzy matching of events" do
|
|
21
21
|
|
22
22
|
e1 = TestEvent.new(one: '1')
|
23
23
|
e2 = TestEvent.new(one: '1', two: '2')
|
24
|
+
e3 = TestEvent.new(one: '1', two: '2')
|
25
|
+
e4 = TestEvent.new(one: '1', two: '4')
|
24
26
|
|
25
27
|
e1.prove{ matches_fuzzy(e2)}
|
26
28
|
e2.prove{ !matches_fuzzy(e1)}
|
27
|
-
|
29
|
+
e2.prove{ matches_fuzzy(e2)}
|
30
|
+
e2.prove{ matches_fuzzy(e3)}
|
31
|
+
e2.prove{ !matches_fuzzy(e4)}
|
28
32
|
end
|
data/replay.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.version = Replay::VERSION
|
8
8
|
s.authors = ["karmajunkie"]
|
9
9
|
s.email = ["keith.gaddis@gmail.com"]
|
10
|
-
s.homepage = ""
|
10
|
+
s.homepage = "https://github.com/karmajunkie/replay"
|
11
11
|
s.summary = %q{Replay supports event-sourced data models.}
|
12
12
|
s.description = %q{Replay supports event-sourced data models.}
|
13
13
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: replay
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- karmajunkie
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-05-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -85,6 +85,7 @@ files:
|
|
85
85
|
- lib/replay/configuration.rb
|
86
86
|
- lib/replay/event_declarations.rb
|
87
87
|
- lib/replay/event_decorator.rb
|
88
|
+
- lib/replay/event_envelope.rb
|
88
89
|
- lib/replay/events.rb
|
89
90
|
- lib/replay/inflector.rb
|
90
91
|
- lib/replay/observer.rb
|
@@ -112,7 +113,7 @@ files:
|
|
112
113
|
- test/replay/observer_spec.rb
|
113
114
|
- test/replay/router/default_router_spec.rb
|
114
115
|
- test/test_helper.rb
|
115
|
-
homepage:
|
116
|
+
homepage: https://github.com/karmajunkie/replay
|
116
117
|
licenses: []
|
117
118
|
metadata: {}
|
118
119
|
post_install_message:
|