entity_store 0.0.15 → 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.
@@ -1,5 +1,7 @@
1
1
  module EntityStore
2
2
  module Entity
3
+ include Hatchet
4
+
3
5
  attr_accessor :id
4
6
 
5
7
  def self.included(klass)
@@ -1,36 +1,65 @@
1
1
  module EntityStore
2
2
  class EventBus
3
- class << self
4
- def publish(entity_type, event)
5
- publish_externally entity_type, event
3
+ include Hatchet
6
4
 
7
- EntityStore.logger.debug { "publishing #{event.inspect}" }
5
+ def publish(entity_type, event)
6
+ publish_externally entity_type, event
8
7
 
9
- subscribers_to(event.receiver_name).each do |s|
10
- begin
11
- s.new.send(event.receiver_name, event)
12
- EntityStore.logger.debug { "called #{s.name}##{event.receiver_name} with #{event.inspect}" }
13
- rescue => e
14
- EntityStore.logger.error { "#{e.message} when calling #{s.name}##{event.receiver_name} with #{event.inspect}" }
15
- end
8
+ logger.debug { "publishing #{event.inspect}" }
9
+
10
+ subscribers_to(event.receiver_name).each do |s|
11
+ begin
12
+ s.new.send(event.receiver_name, event)
13
+ logger.debug { "called #{s.name}##{event.receiver_name} with #{event.inspect}" }
14
+ rescue => e
15
+ logger.error "#{e.message} when calling #{s.name}##{event.receiver_name} with #{event.inspect}", e
16
16
  end
17
17
  end
18
+ end
18
19
 
19
- def subscribers_to(event_name)
20
- subscribers.select { |s| s.instance_methods.include?(event_name.to_sym) }
21
- end
20
+ def subscribers_to(event_name)
21
+ subscribers.select { |s| s.instance_methods.include?(event_name.to_sym) }
22
+ end
22
23
 
23
- def subscribers
24
- EntityStore.event_subscribers
25
- end
24
+ def subscribers
25
+ EntityStore.event_subscribers
26
+ end
26
27
 
27
- def publish_externally(entity_type, event)
28
- external_store.add_event(entity_type, event)
29
- end
28
+ def publish_externally(entity_type, event)
29
+ external_store.add_event(entity_type, event)
30
+ end
31
+
32
+ def external_store
33
+ @_external_store ||= ExternalStore.new
34
+ end
30
35
 
31
- def external_store
32
- @_external_store ||= ExternalStore.new
36
+ # Public - replay events of a given type to a given subscriber
37
+ #
38
+ # since - Time reference point
39
+ # type - String type name of event
40
+ # subscriber - Class of the subscriber to replay events to
41
+ #
42
+ # Returns nothing
43
+ def replay(since, type, subscriber)
44
+ max_items = 100
45
+ event_data_objects = external_store.get_events(since, type, max_items)
46
+
47
+ while event_data_objects.count > 0 do
48
+ event_data_objects.each do |event_data_object|
49
+ begin
50
+ event = get_type_constant(event_data_object.type).new(event_data_object.attrs)
51
+ subscriber.new.send(event.receiver_name, event)
52
+ logger.info { "replayed #{event.inspect} to #{subscriber.name}##{event.receiver_name}" }
53
+ rescue => e
54
+ logger.error "#{e.message} when replaying #{event_data_object.inspect} to #{subscriber}", e
55
+ end
56
+ end
57
+ event_data_objects = external_store.get_events(event_data_objects.last.id, type, max_items)
33
58
  end
34
59
  end
60
+
61
+ def get_type_constant(type_name)
62
+ type_name.split('::').inject(Object) {|obj, name| obj.const_get(name) }
63
+ end
35
64
  end
36
65
  end
@@ -7,7 +7,7 @@ module EntityStore
7
7
  end
8
8
 
9
9
  def id
10
- attrs['_id']
10
+ attrs['_id'].to_s
11
11
  end
12
12
 
13
13
  def entity_type
@@ -29,13 +29,23 @@ module EntityStore
29
29
  )
30
30
  end
31
31
 
32
- def get_events(opts={})
33
- query = {}
34
- query['_id'] = { '$gt' => opts[:after] } if opts[:after]
35
- query['_type'] = opts[:type] if opts[:type]
32
+ # Public - get events since a Time or ID
33
+ #
34
+ # since - Time or String id to filter events from
35
+ # type - String optionally filter the event type to return (default=nil)
36
+ # max_items - Fixnum max items to return (default=100)
37
+ #
38
+ # Returns Enumerable EventDataObject
39
+ def get_events(since, type=nil, max_items=100)
40
+ since_id = since.is_a?(Time) ? BSON::ObjectId.from_time(since) : BSON::ObjectId.from_string(since)
41
+
42
+ query = { '_id' => { '$gt' => since_id } }
43
+ query['_type'] = type if type
36
44
 
37
- options = {:sort => [['_id', -1]]}
38
- options[:limit] = opts[:limit] || 100
45
+ options = {
46
+ :sort => [['_id', Mongo::ASCENDING]],
47
+ :limit => max_items
48
+ }
39
49
 
40
50
  collection.find(query, options).collect { |e| EventDataObject.new(e)}
41
51
  end
@@ -4,6 +4,7 @@ require 'uri'
4
4
  module EntityStore
5
5
  class MongoEntityStore
6
6
  include Mongo
7
+ include Hatchet
7
8
 
8
9
  def open_connection
9
10
  @db ||= open_store
@@ -79,7 +80,7 @@ module EntityStore
79
80
  begin
80
81
  event.apply(entity)
81
82
  rescue => e
82
- EntityStore.logger.error { "Failed to apply #{event.class.name} #{event.attributes} to #{id} with #{e.inspect}" }
83
+ logger.error "Failed to apply #{event.class.name} #{event.attributes} to #{id} with #{e.inspect}", e
83
84
  end
84
85
  entity.version = event.entity_version
85
86
  end
@@ -107,8 +108,7 @@ module EntityStore
107
108
  begin
108
109
  get_type_constant(attrs['_type']).new(attrs)
109
110
  rescue => e
110
- logger = Logger.new(STDERR)
111
- logger.error "Error loading type #{attrs['_type']}"
111
+ logger.error "Error loading type #{attrs['_type']}", e
112
112
  nil
113
113
  end
114
114
  end.select { |e| !e.nil? }
@@ -1,5 +1,7 @@
1
1
  module EntityStore
2
2
  class Store
3
+ include Hatchet
4
+
3
5
  def storage_client
4
6
  @_storage_client ||= MongoEntityStore.new
5
7
  end
@@ -9,7 +11,7 @@ module EntityStore
9
11
  add_events(entity)
10
12
  entity
11
13
  rescue => e
12
- EntityStore.logger.error { "Store#add error: #{e.inspect} - #{entity.inspect}" }
14
+ logger.error { "Store#add error: #{e.inspect} - #{entity.inspect}" }
13
15
  raise e
14
16
  end
15
17
 
@@ -33,12 +35,12 @@ module EntityStore
33
35
  end
34
36
  entity
35
37
  rescue => e
36
- EntityStore.logger.error { "Store#do_save error: #{e.inspect} - #{entity.inspect}" }
38
+ logger.error { "Store#do_save error: #{e.inspect} - #{entity.inspect}" }
37
39
  raise e
38
40
  end
39
41
 
40
42
  def snapshot_entity(entity)
41
- EntityStore.logger.info { "Store#snapshot_entity : Snapshotting #{entity.id}"}
43
+ logger.info { "Store#snapshot_entity : Snapshotting #{entity.id}"}
42
44
  storage_client.snapshot_entity(entity)
43
45
  end
44
46
 
@@ -52,7 +54,7 @@ module EntityStore
52
54
  e.entity_version = entity.version
53
55
  storage_client.add_event(e)
54
56
  end
55
- entity.pending_events.each {|e| EventBus.publish(entity.type, e) }
57
+ entity.pending_events.each {|e| event_bus.publish(entity.type, e) }
56
58
  entity.clear_pending_events
57
59
  end
58
60
 
@@ -77,5 +79,8 @@ module EntityStore
77
79
  @_storage_client = nil
78
80
  end
79
81
 
82
+ def event_bus
83
+ @_event_bus ||= EventBus.new
84
+ end
80
85
  end
81
86
  end
@@ -1,3 +1,3 @@
1
1
  module EntityStore
2
- VERSION = "0.0.15".freeze
2
+ VERSION = "0.1.0".freeze
3
3
  end
data/lib/entity_store.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  module EntityStore
2
- require 'logger'
2
+
3
+ require 'hatchet'
3
4
  require 'entity_store/entity'
4
5
  require 'entity_store/entity_value'
5
6
  require 'entity_store/event'
@@ -47,26 +48,6 @@ module EntityStore
47
48
  @_snapshot_threshold = value
48
49
  end
49
50
 
50
- def log_level
51
- @_log_level ||= Logger::INFO
52
- end
53
-
54
- def log_level=(value)
55
- @_log_level = value
56
- end
57
-
58
- def logger
59
- unless @_logger
60
- @_logger = Logger.new(STDOUT)
61
- @_logger.progname = "Entity_Store"
62
- @_logger.level = log_level
63
- end
64
- @_logger
65
- end
66
-
67
- def logger=(value)
68
- @_logger = value
69
- end
70
51
  end
71
52
 
72
53
 
@@ -15,17 +15,18 @@ describe EventBus do
15
15
  before(:each) do
16
16
  @entity_type = random_string
17
17
  @event = DummyEvent.new(:name => random_string)
18
+ @event_bus = EventBus.new
18
19
  end
19
20
  describe ".publish" do
20
21
  before(:each) do
21
22
  @subscriber = mock("Subscriber", :dummy_event => true)
22
23
  DummySubscriber.stub(:new) { @subscriber }
23
24
  @subscriber_class2 = mock("SubscriberClass", :instance_methods => ['bilge'], :name => "SubscriberClass")
24
- EventBus.stub(:subscribers).and_return([DummySubscriber, @subscriber_class2])
25
- EventBus.stub(:publish_externally)
25
+ @event_bus.stub(:subscribers).and_return([DummySubscriber, @subscriber_class2])
26
+ @event_bus.stub(:publish_externally)
26
27
  end
27
28
 
28
- subject { EventBus.publish(@entity_type, @event) }
29
+ subject { @event_bus.publish(@entity_type, @event) }
29
30
 
30
31
  it "calls the receiver method on the subscriber" do
31
32
  @subscriber.should_receive(:dummy_event).with(@event)
@@ -36,7 +37,7 @@ describe EventBus do
36
37
  subject
37
38
  end
38
39
  it "publishes event to the external event push" do
39
- EventBus.should_receive(:publish_externally).with(@entity_type, @event)
40
+ @event_bus.should_receive(:publish_externally).with(@entity_type, @event)
40
41
  subject
41
42
  end
42
43
  end
@@ -44,14 +45,41 @@ describe EventBus do
44
45
  describe ".publish_externally" do
45
46
  before(:each) do
46
47
  @external_store = mock(ExternalStore)
47
- EventBus.stub(:external_store) { @external_store }
48
+ @event_bus.stub(:external_store) { @external_store }
48
49
  end
49
50
 
50
- subject { EventBus.publish_externally @entity_type, @event }
51
+ subject { @event_bus.publish_externally @entity_type, @event }
51
52
 
52
53
  it "should publish to the external store" do
53
54
  @external_store.should_receive(:add_event).with(@entity_type, @event)
54
55
  subject
55
56
  end
56
57
  end
58
+
59
+ describe "#replay" do
60
+ before(:each) do
61
+ @since = random_time
62
+ @type = 'DummyEvent'
63
+ @subscriber = mock("Subscriber", :dummy_event => true)
64
+ DummySubscriber.stub(:new) { @subscriber }
65
+
66
+ @external_store = mock(ExternalStore)
67
+ @id = random_object_id
68
+ @external_store.stub(:get_events) { |since| since == @id ? [] : [
69
+ EventDataObject.new('_id' => @id, '_type' => DummyEvent.name, 'name' => random_string)
70
+ ]}
71
+ @event_bus.stub(:external_store) { @external_store }
72
+ end
73
+
74
+ subject { @event_bus.replay(@since, @type, DummySubscriber) }
75
+
76
+ it "gets the events for that period" do
77
+ @external_store.should_receive(:get_events).with(@since, @type, 100)
78
+ subject
79
+ end
80
+ it "publishes them to the subscriber" do
81
+ @subscriber.should_receive(:dummy_event).with(an_instance_of(DummyEvent))
82
+ subject
83
+ end
84
+ end
57
85
  end
@@ -36,63 +36,89 @@ describe ExternalStore do
36
36
 
37
37
  describe "#get_events" do
38
38
  before(:each) do
39
- @entity_type = random_string
40
- @events = [
41
- DummyEvent.new(:name => random_string, :entity_id => random_object_id),
42
- DummyEventTwo.new(:name => random_string, :entity_id => random_object_id),
43
- DummyEvent.new(:name => random_string, :entity_id => random_object_id),
44
- DummyEventTwo.new(:name => random_string, :entity_id => random_object_id),
45
- DummyEvent.new(:name => random_string, :entity_id => random_object_id)
46
- ]
47
-
48
- @events.each { |e| @store.add_event(@entity_type, e)}
49
- end
50
-
51
- context "when no options" do
39
+ @reference_time = Time.now
40
+ @ids = (-2..2).collect { |i| BSON::ObjectId.from_time(@reference_time + i) }
52
41
 
53
- subject { @store.get_events }
42
+ @store.collection.insert({'_id' => @ids[0], '_type' => 'DummyEvent'})
43
+ @store.collection.insert({'_id' => @ids[1], '_type' => 'DummyEventTwo'})
44
+ @store.collection.insert({'_id' => @ids[2], '_type' => 'DummyEvent'})
45
+ @store.collection.insert({'_id' => @ids[3], '_type' => 'DummyEventTwo'})
46
+ @store.collection.insert({'_id' => @ids[4], '_type' => 'DummyEvent'})
47
+ end
48
+
49
+ subject { @store.get_events @since, @type }
54
50
 
55
- it "returns all of the events" do
56
- subject.count.should eq(@events.count)
51
+ context "when time passed as since" do
52
+ before(:each) do
53
+ @since = @reference_time
57
54
  end
58
- end
59
-
60
- context "when options passed" do
61
- subject { @store.get_events(@options) }
62
-
63
- context "when limit option passed" do
55
+ context "when no type filter" do
56
+ before(:each) do
57
+ @type = nil
58
+ @results = subject
59
+ end
60
+ it "returns two records" do
61
+ @results.count.should eq(2)
62
+ end
63
+ it "it returns the 4th item first" do
64
+ @results.first.id.should eq(@ids[3].to_s)
65
+ end
66
+ it "it returns the 5th item second" do
67
+ @results[1].id.should eq(@ids[4].to_s)
68
+ end
69
+ end
70
+ context "when type filter 'DummyEventTwo' passed" do
64
71
  before(:each) do
65
- @options = {:limit => 3}
72
+ @type = "DummyEventTwo"
73
+ @results = subject
66
74
  end
67
-
68
- it "returns limited records records" do
69
- subject.count.should eq(@options[:limit])
75
+ it "returns 1 record" do
76
+ @results.count.should eq(1)
70
77
  end
78
+ it "returns the 4th item" do
79
+ @results.first.id.should eq(@ids[3].to_s)
80
+ end
81
+ end
82
+ end
83
+
84
+ context "when id passed as since" do
85
+ before(:each) do
86
+ @since = @ids[1].to_s
71
87
  end
72
-
73
- context "when after index passed" do
88
+ context "when no type filter passed" do
74
89
  before(:each) do
75
- items = @store.get_events(:limit => 3)
76
- @options = {:after => items[2].id}
90
+ @type = nil
91
+ @results = subject
92
+ end
93
+ it "returns 3 records" do
94
+ @results.count.should eq(3)
95
+ end
96
+ it "it returns the 3rd item first" do
97
+ @results.first.id.should eq(@ids[2].to_s)
77
98
  end
78
-
79
- it "returns limited records records" do
80
- subject.count.should eq(2)
99
+ it "it returns the 4th item second" do
100
+ @results[1].id.should eq(@ids[3].to_s)
81
101
  end
82
- end
83
-
84
- context "when type passed" do
102
+ it "it returns the 5th item second" do
103
+ @results[2].id.should eq(@ids[4].to_s)
104
+ end
105
+ end
106
+ context "when type filter 'DummyEvent' passed" do
85
107
  before(:each) do
86
- @options = {:type => @events[2].class.name}
108
+ @type = "DummyEvent"
109
+ @results = subject
110
+ end
111
+ it "returns 2 records" do
112
+ @results.count.should eq(2)
87
113
  end
88
-
89
- it "returns type records records" do
90
- subject.count.should eq(3)
114
+ it "returns the 3rd item" do
115
+ @results.first.id.should eq(@ids[2].to_s)
91
116
  end
92
- end
93
-
117
+ it "returns the 5th item" do
118
+ @results[1].id.should eq(@ids[4].to_s)
119
+ end
120
+ end
94
121
  end
95
-
96
122
  end
97
123
 
98
124
  end
@@ -43,7 +43,8 @@ describe Store do
43
43
  @storage_client = mock("StorageClient", :add_event => true)
44
44
  @store = Store.new
45
45
  @store.stub(:storage_client) { @storage_client }
46
- EventBus.stub(:publish)
46
+ @event_bus = mock(EventBus, :publish => true)
47
+ @store.stub(:event_bus) { @event_bus}
47
48
  end
48
49
 
49
50
  subject { @store.add_events(@entity) }
@@ -68,7 +69,7 @@ describe Store do
68
69
  end
69
70
  it "publishes each event to the EventBus" do
70
71
  @entity.pending_events.each do |e|
71
- EventBus.should_receive(:publish).with(@entity.type, e)
72
+ @event_bus.should_receive(:publish).with(@entity.type, e)
72
73
  end
73
74
  subject
74
75
  end
@@ -2,14 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe EntityStore do
4
4
  describe ".setup" do
5
- before(:each) do
6
- EntityStore.setup do |config|
7
- config.log_level = Logger::WARN
8
- end
9
- end
10
- it "has a log_level of WARN" do
11
- EntityStore.log_level.should eq(Logger::WARN)
12
- end
5
+
13
6
  end
14
7
 
15
8
  end
data/spec/spec_helper.rb CHANGED
@@ -6,6 +6,18 @@ RSpec.configure do |config|
6
6
  config.color_enabled = true
7
7
  end
8
8
 
9
+ Hatchet.configure do |config|
10
+ # Reset the logging configuration
11
+ config.reset!
12
+ config.level :error
13
+ # Use the format without time, etc so we don't duplicate it
14
+ config.formatter = Hatchet::SimpleFormatter.new
15
+ # Set up a STDOUT appender
16
+ config.appenders << Hatchet::LoggerAppender.new do |appender|
17
+ appender.logger = Logger.new(STDOUT)
18
+ end
19
+ end
20
+
9
21
  include EntityStore
10
22
 
11
23
  def random_string
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: entity_store
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.15
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-31 00:00:00.000000000 Z
12
+ date: 2012-11-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: mongo
@@ -43,6 +43,22 @@ dependencies:
43
43
  - - ~>
44
44
  - !ruby/object:Gem::Version
45
45
  version: '1.6'
46
+ - !ruby/object:Gem::Dependency
47
+ name: hatchet
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 0.0.20
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: 0.0.20
46
62
  description: Event sourced entity store with a Mongo body
47
63
  email: adam.bird@gmail.com
48
64
  executables: []