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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 084d0ba8e80a2b93566f0ba8e780feedccf92d28
4
- data.tar.gz: 7cf7c4e15f76ee0659cb0be60cac9a21d0fc2d6b
3
+ metadata.gz: d2ba86d3b1d0e597548c8fb5a807f0a5e9f98313
4
+ data.tar.gz: 940e276d8721b831afd454f55c2dd7d574d4ab5a
5
5
  SHA512:
6
- metadata.gz: e1561eba9d3b6469a38243a840e22dd850500a057f3b272f8ea67964680436867f40c57a892e07cf31aa44cdd519660d3f273f4bd657e0e304635120bec65d64
7
- data.tar.gz: ebd32484a7c18fa8c7dbbd77c49b3564bfaede2ee2c785c839e0a1af00d1dac5ee876ea57cc1f6659c6438f28013e1353aecd2b9abf80afe422389a7a9ab43eb
6
+ metadata.gz: 3a8d3363c8b40dc87669b5d99934a1321eaa789cce0b18520cf1f2bf9e8228368256a8842ff0f1dad0093b416dee0af2f067a80fb3c096e72373f5be81a4a2cf
7
+ data.tar.gz: 854dd7ae22f150b007853c8b3d39e2c697dbece4d2c7a421a1c93767243e983ed4bf2591217a4b5662c7d987b7e1ca38141bdf19d20e20c2aa4e82cb5f8a1783
@@ -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'
@@ -15,8 +15,8 @@ module Replay
15
15
  def initialize
16
16
  @store = {}
17
17
  end
18
- def self.published(stream_id, event)
19
- instance.published(stream_id, event)
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(stream_id, event)
30
+ def published(envelope)
31
31
  @store[stream_id] ||= []
32
- @store[stream_id] << event
32
+ @store[stream_id] << envelope
33
33
  end
34
34
 
35
35
  def event_stream(stream_id)
@@ -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.class.to_s}: #{self.attributes.map{|k, v| "#{k.to_s} = #{v.to_s}"}.join(", ")}"
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
@@ -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.class.to_s)]
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
@@ -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
- blk = block_for(event.class)
17
- raise UnhandledEventError.new "event #{event.class.name} is not handled by #{self.class.name}" if (blk.nil? && raise_unhandled)
18
- self.instance_exec(event, &blk)
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 block_for(event_type)
24
- self.class.block_for(event_type)
23
+ def apply_method_for(klass)
24
+ self.class.apply_method_for(klass)
25
25
  end
26
- protected :block_for
27
26
 
28
- def publish(event)
29
- apply(event)
30
- subscription_manager.notify_subscribers(to_stream_id, event)
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.class.to_s}") unless self.class.key_attr
37
- self.send(self.class.key_attr).to_s
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
- @application_blocks[stringify_class(event_type)] = block
51
- end
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
@@ -19,15 +19,15 @@ module Replay
19
19
  repository_load(klass, stream_id, options)
20
20
  end
21
21
 
22
- def repository_load(klass, stream_id, options={})
23
- events = store.event_stream(stream_id)
24
- if events.empty?
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] && events.empty?
30
- obj.apply(events)
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.new
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
- def initialize
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)
@@ -43,7 +43,7 @@ RSpec::Matchers.define :publish do |expected_event|
43
43
  end
44
44
 
45
45
  def event_interpretation(event)
46
- "#{event.class.to_s} [#{event.attributes.reject{|k,v| v.nil?}.keys.map{|k| "#{k.to_s} = #{event.attributes[k]}"}.join(", ")}]"
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
- sub.published(stream_id, event)
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
- @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")}" if @logger
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
@@ -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
@@ -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
- if relevant_attrs_self.reject{|k, v| event[k] == v}.any?
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 publish(stream_id, event)
8
- @events << {stream: stream_id, event: event}
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[:event]==event}
15
+ @events.detect{|e| e.event==event}
13
16
  end
14
17
 
15
18
  def published?(stream_id, event)
16
- @events.detect{|e| e == {stream: stream_id, event: event}}
19
+ @events.detect{|e| e.stream_id == stream_id && e.event == event}
17
20
  end
18
21
  end
19
22
  end
@@ -1,3 +1,3 @@
1
1
  module Replay
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -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) < 20
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(stream, event)
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 "sets the publish time on events" do
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(stream_id, event); end
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(stream, event)
18
- self.stream = 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)
@@ -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
@@ -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.1.1
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-04-15 00:00:00.000000000 Z
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: