entity_store 0.1.6 → 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 +15 -0
- data/lib/entity_store.rb +18 -57
- data/lib/entity_store/config.rb +53 -0
- data/lib/entity_store/entity.rb +0 -2
- data/lib/entity_store/event.rb +11 -2
- data/lib/entity_store/event_bus.rb +14 -16
- data/lib/entity_store/external_store.rb +15 -2
- data/lib/entity_store/hash_serialization.rb +8 -4
- data/lib/entity_store/logging.rb +17 -0
- data/lib/entity_store/mongo_entity_store.rb +31 -22
- data/lib/entity_store/store.rb +17 -7
- data/lib/entity_store/version.rb +1 -1
- data/spec/entity_store/config_spec.rb +38 -0
- data/spec/entity_store/event_bus_spec.rb +11 -11
- data/spec/entity_store/external_store_spec.rb +1 -0
- data/spec/entity_store/mongo_entity_store_spec.rb +3 -19
- data/spec/entity_store/store_spec.rb +20 -5
- data/spec/entity_store_spec.rb +1 -34
- data/spec/spec_helper.rb +5 -16
- metadata +10 -44
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MTNjMDA0M2EyMWNmMWM5Y2U1NDc2MjMwOWU0NWQ5Mjk0ZWFlYjUxOA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZWMzNDEzNTYzYzU4ZjE1ZjVjOGNkZTJhMjQxMWMzOWUwNTllOGUwNQ==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MThjMmU5N2RhMzVjMjRkMGJmNDFjOTMxNjU5OWI5MzU3NWJkYzI5NDM4OTJm
|
10
|
+
ZjJjNzg3M2I4ZWJhMmIzMzUwYWFjOGU1Njk4OWMxY2Q0ZDA5ZGUyZjIzNGUz
|
11
|
+
MmQyMDE5YzgyOTFhZDI4Y2RlYzQzZDQ4M2Y3Yjg3YjYyNTdjNTU=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NDE3ODdjNGI2MzJiYzlhZDk0OTA2NzkwOTBlM2I1OGNmYWRhMjBlMTAyZjIw
|
14
|
+
ZTk0NmFmYTZmZWJjZTI3MmQzNDJkNmFlY2JhN2VkZWIzNGYzY2I2NTUxMDU1
|
15
|
+
ZTdlY2NkMjk4ZmU1MjExNmYwZjM5Yjk2N2Y4YzE1ZmQ3M2M0ZWQ=
|
data/lib/entity_store.rb
CHANGED
@@ -1,68 +1,29 @@
|
|
1
1
|
module EntityStore
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
3
|
+
require_relative 'entity_store/logging'
|
4
|
+
require_relative 'entity_store/config'
|
5
|
+
require_relative 'entity_store/entity'
|
6
|
+
require_relative 'entity_store/entity_value'
|
7
|
+
require_relative 'entity_store/event'
|
8
|
+
require_relative 'entity_store/store'
|
9
|
+
require_relative 'entity_store/event_data_object'
|
10
|
+
require_relative 'entity_store/event_bus'
|
11
|
+
require_relative 'entity_store/not_found'
|
12
|
+
require_relative 'entity_store/hash_serialization'
|
13
|
+
require_relative 'entity_store/attributes'
|
14
|
+
|
15
|
+
if defined?(Mongo)
|
16
|
+
require_relative 'entity_store/mongo_entity_store'
|
17
|
+
require_relative 'entity_store/external_store'
|
18
|
+
end
|
15
19
|
|
16
20
|
class << self
|
17
|
-
attr_reader :mongo_connection, :external_mongo_connection, :entity_db, :external_db
|
18
|
-
attr_accessor :connection_profile, :external_connection_profile
|
19
21
|
def setup
|
20
|
-
yield
|
21
|
-
|
22
|
-
@mongo_connection = open_store(@connection_profile)
|
23
|
-
@entity_db = extract_db(@connection_profile)
|
24
|
-
@external_mongo_connection = open_store(@external_connection_profile)
|
25
|
-
@external_db = extract_db(@external_connection_profile)
|
22
|
+
yield EntityStore::Config.setup
|
26
23
|
end
|
27
24
|
|
28
25
|
def event_subscribers
|
29
|
-
|
30
|
-
end
|
31
|
-
|
32
|
-
# Public - indicates the version increment that is used to
|
33
|
-
# decided whether a snapshot of an entity should be created when it's saved
|
34
|
-
def snapshot_threshold
|
35
|
-
@_snapshot_threshold ||= 10
|
36
|
-
end
|
37
|
-
|
38
|
-
def snapshot_threshold=(value)
|
39
|
-
@_snapshot_threshold = value
|
40
|
-
end
|
41
|
-
|
42
|
-
# Allows config to pass in a lambda or Proc to use as the type loader in place
|
43
|
-
# of the default.
|
44
|
-
# Original use case was migration of entity classes to new module namespace when
|
45
|
-
# extracting to a shared library
|
46
|
-
attr_accessor :type_loader
|
47
|
-
|
48
|
-
def load_type(type_name)
|
49
|
-
if EntityStore.type_loader
|
50
|
-
EntityStore.type_loader.call(type_name)
|
51
|
-
else
|
52
|
-
type_name.split('::').inject(Object) {|obj, name| obj.const_get(name) }
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def open_store(url)
|
57
|
-
Mongo::MongoClient.from_uri(url, :connect_timeout => connect_timeout)
|
58
|
-
end
|
59
|
-
|
60
|
-
def extract_db(url)
|
61
|
-
URI.parse(url).path.gsub(/^\//, '')
|
62
|
-
end
|
63
|
-
|
64
|
-
def connect_timeout
|
65
|
-
(ENV['ENTITY_STORE_CONNECT_TIMEOUT'] || '2').to_i
|
26
|
+
EntityStore::Config.event_subscribers
|
66
27
|
end
|
67
28
|
end
|
68
29
|
|
data/lib/entity_store/config.rb
CHANGED
@@ -0,0 +1,53 @@
|
|
1
|
+
module EntityStore
|
2
|
+
module Config
|
3
|
+
class << self
|
4
|
+
# Stores
|
5
|
+
attr_accessor :store, :feed_store
|
6
|
+
|
7
|
+
# Allows config to pass in a lambda or Proc to use as the type loader in place
|
8
|
+
# of the default.
|
9
|
+
# Original use case was migration of entity classes to new module namespace when
|
10
|
+
# extracting to a shared library
|
11
|
+
attr_accessor :type_loader
|
12
|
+
|
13
|
+
# Logger can be assigned
|
14
|
+
attr_accessor :logger
|
15
|
+
|
16
|
+
def setup
|
17
|
+
yield self
|
18
|
+
|
19
|
+
raise StandardError.new("store not assigned") unless store
|
20
|
+
store.open
|
21
|
+
feed_store.open if feed_store
|
22
|
+
end
|
23
|
+
|
24
|
+
def event_subscribers
|
25
|
+
@_event_subscribers ||=[]
|
26
|
+
end
|
27
|
+
|
28
|
+
# Public - indicates the version increment that is used to
|
29
|
+
# decided whether a snapshot of an entity should be created when it's saved
|
30
|
+
def snapshot_threshold
|
31
|
+
@_snapshot_threshold ||= 10
|
32
|
+
end
|
33
|
+
|
34
|
+
def snapshot_threshold=(value)
|
35
|
+
@_snapshot_threshold = value
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def load_type(type_name)
|
40
|
+
if EntityStore::Config.type_loader
|
41
|
+
EntityStore::Config.type_loader.call(type_name)
|
42
|
+
else
|
43
|
+
type_name.split('::').inject(Object) {|obj, name| obj.const_get(name) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def connect_timeout
|
48
|
+
(ENV['ENTITY_STORE_CONNECT_TIMEOUT'] || '2').to_i
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
data/lib/entity_store/entity.rb
CHANGED
data/lib/entity_store/event.rb
CHANGED
@@ -24,8 +24,17 @@ module EntityStore
|
|
24
24
|
class_eval do
|
25
25
|
names.each do |name|
|
26
26
|
define_method "#{name}=" do |value|
|
27
|
-
|
28
|
-
|
27
|
+
if value.kind_of?(String)
|
28
|
+
# implementing parsing here rather than using std-lib Time.parse to
|
29
|
+
# allow portability across platforms
|
30
|
+
parts = /(?:(\d+))-(?:(\d+))-(?:(\d+))\s(?:(\d+)):(?:(\d+)):(?:(\d+))\s(?:(.+))/.match(value)
|
31
|
+
offset = parts[7].gsub(/\d{4}/) do |m| m.scan(/../).join(":") end
|
32
|
+
new_value = Time.new(parts[1].to_i, parts[2].to_i, parts[3].to_i, parts[4].to_i, parts[5].to_i, parts[6].to_i, offset)
|
33
|
+
else
|
34
|
+
new_value = value
|
35
|
+
end
|
36
|
+
|
37
|
+
instance_variable_set "@#{name}", new_value
|
29
38
|
end
|
30
39
|
define_method name do
|
31
40
|
instance_variable_get "@#{name}"
|
@@ -1,18 +1,16 @@
|
|
1
1
|
module EntityStore
|
2
2
|
class EventBus
|
3
|
-
include
|
3
|
+
include Logging
|
4
4
|
|
5
5
|
def publish(entity_type, event)
|
6
|
-
|
7
|
-
|
8
|
-
logger.debug { "publishing #{event.inspect}" }
|
6
|
+
publish_to_feed entity_type, event
|
9
7
|
|
10
8
|
subscribers_to(event.receiver_name).each do |s|
|
11
9
|
begin
|
12
10
|
s.new.send(event.receiver_name, event)
|
13
|
-
|
11
|
+
log_debug { "called #{s.name}##{event.receiver_name} with #{event.inspect}" }
|
14
12
|
rescue => e
|
15
|
-
|
13
|
+
log_error "#{e.message} when calling #{s.name}##{event.receiver_name} with #{event.inspect}", e
|
16
14
|
end
|
17
15
|
end
|
18
16
|
end
|
@@ -22,15 +20,15 @@ module EntityStore
|
|
22
20
|
end
|
23
21
|
|
24
22
|
def subscribers
|
25
|
-
EntityStore.event_subscribers
|
23
|
+
EntityStore::Config.event_subscribers
|
26
24
|
end
|
27
25
|
|
28
|
-
def
|
29
|
-
|
26
|
+
def publish_to_feed(entity_type, event)
|
27
|
+
feed_store.add_event(entity_type, event) if feed_store
|
30
28
|
end
|
31
29
|
|
32
|
-
def
|
33
|
-
|
30
|
+
def feed_store
|
31
|
+
EntityStore::Config.feed_store
|
34
32
|
end
|
35
33
|
|
36
34
|
# Public - replay events of a given type to a given subscriber
|
@@ -42,19 +40,19 @@ module EntityStore
|
|
42
40
|
# Returns nothing
|
43
41
|
def replay(since, type, subscriber)
|
44
42
|
max_items = 100
|
45
|
-
event_data_objects =
|
43
|
+
event_data_objects = feed_store.get_events(since, type, max_items)
|
46
44
|
|
47
45
|
while event_data_objects.count > 0 do
|
48
46
|
event_data_objects.each do |event_data_object|
|
49
47
|
begin
|
50
|
-
event = EntityStore.load_type(event_data_object.type).new(event_data_object.attrs)
|
48
|
+
event = EntityStore::Config.load_type(event_data_object.type).new(event_data_object.attrs)
|
51
49
|
subscriber.new.send(event.receiver_name, event)
|
52
|
-
|
50
|
+
log_info { "replayed #{event.inspect} to #{subscriber.name}##{event.receiver_name}" }
|
53
51
|
rescue => e
|
54
|
-
|
52
|
+
log_error "#{e.message} when replaying #{event_data_object.inspect} to #{subscriber}", e
|
55
53
|
end
|
56
54
|
end
|
57
|
-
event_data_objects =
|
55
|
+
event_data_objects = feed_store.get_events(event_data_objects.last.id, type, max_items)
|
58
56
|
end
|
59
57
|
end
|
60
58
|
end
|
@@ -4,9 +4,22 @@ require 'uri'
|
|
4
4
|
module EntityStore
|
5
5
|
class ExternalStore
|
6
6
|
include Mongo
|
7
|
-
|
7
|
+
|
8
|
+
class << self
|
9
|
+
attr_accessor :connection_profile
|
10
|
+
attr_writer :connect_timeout
|
11
|
+
|
12
|
+
def connection
|
13
|
+
@_connection ||= Mongo::MongoClient.from_uri(ExternalStore.connection_profile, :connect_timeout => EntityStore::Config.connect_timeout)
|
14
|
+
end
|
15
|
+
|
16
|
+
def database
|
17
|
+
URI.parse(ExternalStore.connection_profile).path.gsub(/^\//, '')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
8
21
|
def open_connection
|
9
|
-
|
22
|
+
ExternalStore.connection.db(ExternalStore.database)
|
10
23
|
end
|
11
24
|
|
12
25
|
def collection
|
@@ -9,15 +9,19 @@ module EntityStore
|
|
9
9
|
# did use flatten but this came a-cropper when the attribute value was an array
|
10
10
|
def attributes
|
11
11
|
attrs = {}
|
12
|
-
|
13
|
-
.select { |m| m =~ /\w\=$/ }
|
14
|
-
.select { |m| respond_to?(m.to_s.chop) }
|
15
|
-
.collect { |m| m.to_s.chop.to_sym }
|
12
|
+
attribute_methods
|
16
13
|
.collect { |m| [m, attribute_value(send(m))] }
|
17
14
|
.each do |item| attrs[item[0]] = item[1] end
|
18
15
|
attrs
|
19
16
|
end
|
20
17
|
|
18
|
+
def attribute_methods
|
19
|
+
public_methods
|
20
|
+
.select { |m| m =~ /\w\=$/ }
|
21
|
+
.collect { |m| m.to_s.chop.to_sym }
|
22
|
+
.select { |m| respond_to?(m) }
|
23
|
+
end
|
24
|
+
|
21
25
|
def attribute_value(value)
|
22
26
|
if value.respond_to?(:attributes)
|
23
27
|
value.attributes
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module EntityStore
|
2
|
+
module Logging
|
3
|
+
|
4
|
+
[:debug, :info, :warn].each do |level|
|
5
|
+
define_method("log_#{level}") do |message=nil, &block|
|
6
|
+
Config.logger.send(level, message || block) if Config.logger
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def log_error(message, exception)
|
11
|
+
if Config.logger
|
12
|
+
Config.logger.error message
|
13
|
+
Config.logger.error exception.backtrace
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -4,18 +4,38 @@ require 'uri'
|
|
4
4
|
module EntityStore
|
5
5
|
class MongoEntityStore
|
6
6
|
include Mongo
|
7
|
-
include
|
7
|
+
include Logging
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
class << self
|
10
|
+
attr_accessor :connection_profile
|
11
|
+
attr_writer :connect_timeout
|
12
|
+
|
13
|
+
def connection
|
14
|
+
@_connection ||= Mongo::MongoClient.from_uri(MongoEntityStore.connection_profile, :connect_timeout => EntityStore::Config.connect_timeout)
|
15
|
+
end
|
16
|
+
|
17
|
+
def database
|
18
|
+
URI.parse(MongoEntityStore.connection_profile).path.gsub(/^\//, '')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def open
|
23
|
+
MongoEntityStore.connection.db(MongoEntityStore.database)
|
11
24
|
end
|
12
25
|
|
13
26
|
def entities
|
14
|
-
@entities_collection ||=
|
27
|
+
@entities_collection ||= open['entities']
|
15
28
|
end
|
16
29
|
|
17
30
|
def events
|
18
|
-
@events_collection ||=
|
31
|
+
@events_collection ||= open['entity_events']
|
32
|
+
end
|
33
|
+
|
34
|
+
def clear
|
35
|
+
entities.drop
|
36
|
+
@entities_collection = nil
|
37
|
+
events.drop
|
38
|
+
@events_collection = nil
|
19
39
|
end
|
20
40
|
|
21
41
|
def ensure_indexes
|
@@ -63,24 +83,13 @@ module EntityStore
|
|
63
83
|
def get_entity(id, raise_exception=false)
|
64
84
|
if attrs = entities.find_one('_id' => BSON::ObjectId.from_string(id))
|
65
85
|
begin
|
66
|
-
|
86
|
+
entity_type = EntityStore::Config.load_type(attrs['_type'])
|
87
|
+
entity = entity_type.new(attrs['snapshot'] || {'id' => id, 'version' => attrs['version']})
|
67
88
|
rescue => e
|
68
|
-
|
89
|
+
log_error "Error loading type #{attrs['_type']}", e
|
69
90
|
raise
|
70
91
|
end
|
71
|
-
|
72
|
-
since_version = attrs['snapshot'] ? attrs['snapshot']['version'] : nil
|
73
|
-
|
74
|
-
get_events(id, since_version).each do |event|
|
75
|
-
begin
|
76
|
-
event.apply(entity)
|
77
|
-
logger.debug { "Applied #{event.inspect} to #{id}" }
|
78
|
-
rescue => e
|
79
|
-
logger.error "Failed to apply #{event.class.name} #{event.attributes} to #{id} with #{e.inspect}", e
|
80
|
-
end
|
81
|
-
entity.version = event.entity_version
|
82
|
-
end
|
83
|
-
|
92
|
+
|
84
93
|
entity
|
85
94
|
else
|
86
95
|
raise NotFound.new(id) if raise_exception
|
@@ -102,9 +111,9 @@ module EntityStore
|
|
102
111
|
|
103
112
|
events.find(query, options).collect do |attrs|
|
104
113
|
begin
|
105
|
-
EntityStore.load_type(attrs['_type']).new(attrs)
|
114
|
+
EntityStore::Config.load_type(attrs['_type']).new(attrs)
|
106
115
|
rescue => e
|
107
|
-
|
116
|
+
log_error "Error loading type #{attrs['_type']}", e
|
108
117
|
nil
|
109
118
|
end
|
110
119
|
end.select { |e| !e.nil? }
|
data/lib/entity_store/store.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
module EntityStore
|
2
2
|
class Store
|
3
|
-
include
|
3
|
+
include Logging
|
4
4
|
|
5
5
|
def storage_client
|
6
|
-
@_storage_client ||=
|
6
|
+
@_storage_client ||= EntityStore::Config.store
|
7
7
|
end
|
8
8
|
|
9
9
|
def add(entity)
|
@@ -31,16 +31,16 @@ module EntityStore
|
|
31
31
|
entity.id = storage_client.add_entity(entity)
|
32
32
|
end
|
33
33
|
add_events(entity)
|
34
|
-
snapshot_entity(entity) if entity.version %
|
34
|
+
snapshot_entity(entity) if entity.version % Config.snapshot_threshold == 0
|
35
35
|
end
|
36
36
|
entity
|
37
37
|
rescue => e
|
38
|
-
|
38
|
+
log_error "Store#do_save error: #{e.inspect} - #{entity.inspect}", e
|
39
39
|
raise e
|
40
40
|
end
|
41
41
|
|
42
42
|
def snapshot_entity(entity)
|
43
|
-
|
43
|
+
log_info { "Store#snapshot_entity : Snapshotting #{entity.id}"}
|
44
44
|
storage_client.snapshot_entity(entity)
|
45
45
|
end
|
46
46
|
|
@@ -64,6 +64,17 @@ module EntityStore
|
|
64
64
|
|
65
65
|
def get(id, raise_exception=false)
|
66
66
|
if entity = storage_client.get_entity(id, raise_exception)
|
67
|
+
|
68
|
+
storage_client.get_events(id, entity.version).each do |event|
|
69
|
+
begin
|
70
|
+
event.apply(entity)
|
71
|
+
log_debug { "Applied #{event.inspect} to #{id}" }
|
72
|
+
rescue => e
|
73
|
+
log_error "Failed to apply #{event.class.name} #{event.attributes} to #{id} with #{e.inspect}", e
|
74
|
+
end
|
75
|
+
entity.version = event.entity_version
|
76
|
+
end
|
77
|
+
|
67
78
|
# assign this entity loader to allow lazy loading of related entities
|
68
79
|
entity.related_entity_loader = self
|
69
80
|
end
|
@@ -74,8 +85,7 @@ module EntityStore
|
|
74
85
|
#
|
75
86
|
# Returns nothing
|
76
87
|
def clear_all
|
77
|
-
storage_client.
|
78
|
-
storage_client.events.drop
|
88
|
+
storage_client.clear
|
79
89
|
@_storage_client = nil
|
80
90
|
end
|
81
91
|
|
data/lib/entity_store/version.rb
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Level1
|
4
|
+
module Level2
|
5
|
+
class MyClass
|
6
|
+
end
|
7
|
+
class AnotherClass
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe EntityStore::Config do
|
13
|
+
|
14
|
+
describe "load_type" do
|
15
|
+
|
16
|
+
subject { EntityStore::Config.load_type('Level1::Level2::MyClass') }
|
17
|
+
|
18
|
+
it "should be an Level1::Level2::MyClass" do
|
19
|
+
subject.should eq(Level1::Level2::MyClass)
|
20
|
+
end
|
21
|
+
|
22
|
+
context "when type_loader set" do
|
23
|
+
before(:each) do
|
24
|
+
EntityStore::Config.type_loader = lambda { |type_name|
|
25
|
+
Level1::Level2::AnotherClass
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should return the result of that type loader" do
|
30
|
+
subject.should eq(Level1::Level2::AnotherClass)
|
31
|
+
end
|
32
|
+
|
33
|
+
after(:each) do
|
34
|
+
EntityStore::Config.type_loader = nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -23,7 +23,7 @@ describe EventBus do
|
|
23
23
|
DummySubscriber.stub(:new) { @subscriber }
|
24
24
|
@subscriber_class2 = mock("SubscriberClass", :instance_methods => ['bilge'], :name => "SubscriberClass")
|
25
25
|
@event_bus.stub(:subscribers).and_return([DummySubscriber, @subscriber_class2])
|
26
|
-
@event_bus.stub(:
|
26
|
+
@event_bus.stub(:publish_to_feed)
|
27
27
|
end
|
28
28
|
|
29
29
|
subject { @event_bus.publish(@entity_type, @event) }
|
@@ -37,21 +37,21 @@ describe EventBus do
|
|
37
37
|
subject
|
38
38
|
end
|
39
39
|
it "publishes event to the external event push" do
|
40
|
-
@event_bus.should_receive(:
|
40
|
+
@event_bus.should_receive(:publish_to_feed).with(@entity_type, @event)
|
41
41
|
subject
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
-
describe ".
|
45
|
+
describe ".publish_to_feed" do
|
46
46
|
before(:each) do
|
47
|
-
@
|
48
|
-
@event_bus.stub(:
|
47
|
+
@feed_store = mock(ExternalStore)
|
48
|
+
@event_bus.stub(:feed_store) { @feed_store }
|
49
49
|
end
|
50
50
|
|
51
|
-
subject { @event_bus.
|
51
|
+
subject { @event_bus.publish_to_feed @entity_type, @event }
|
52
52
|
|
53
53
|
it "should publish to the external store" do
|
54
|
-
@
|
54
|
+
@feed_store.should_receive(:add_event).with(@entity_type, @event)
|
55
55
|
subject
|
56
56
|
end
|
57
57
|
end
|
@@ -63,18 +63,18 @@ describe EventBus do
|
|
63
63
|
@subscriber = mock("Subscriber", :dummy_event => true)
|
64
64
|
DummySubscriber.stub(:new) { @subscriber }
|
65
65
|
|
66
|
-
@
|
66
|
+
@feed_store = mock(ExternalStore)
|
67
67
|
@id = random_object_id
|
68
|
-
@
|
68
|
+
@feed_store.stub(:get_events) { |since| since == @id ? [] : [
|
69
69
|
EventDataObject.new('_id' => @id, '_type' => DummyEvent.name, 'name' => random_string)
|
70
70
|
]}
|
71
|
-
@event_bus.stub(:
|
71
|
+
@event_bus.stub(:feed_store) { @feed_store }
|
72
72
|
end
|
73
73
|
|
74
74
|
subject { @event_bus.replay(@since, @type, DummySubscriber) }
|
75
75
|
|
76
76
|
it "gets the events for that period" do
|
77
|
-
@
|
77
|
+
@feed_store.should_receive(:get_events).with(@since, @type, 100)
|
78
78
|
subject
|
79
79
|
end
|
80
80
|
it "publishes them to the subscriber" do
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module MongoEntityStoreSpec
|
4
4
|
class DummyEntity
|
5
|
-
include Entity
|
5
|
+
include EntityStore::Entity
|
6
6
|
|
7
7
|
attr_accessor :name, :description
|
8
8
|
|
@@ -11,7 +11,7 @@ end
|
|
11
11
|
|
12
12
|
describe MongoEntityStore do
|
13
13
|
before(:each) do
|
14
|
-
|
14
|
+
MongoEntityStore.connection_profile = "mongodb://localhost/entity_store_default"
|
15
15
|
@store = MongoEntityStore.new
|
16
16
|
end
|
17
17
|
|
@@ -19,7 +19,7 @@ describe MongoEntityStore do
|
|
19
19
|
before(:each) do
|
20
20
|
@id = random_object_id
|
21
21
|
@attrs = {
|
22
|
-
'_type' =>
|
22
|
+
'_type' => MongoEntityStoreSpec::DummyEntity.name,
|
23
23
|
'version' => @version = random_integer
|
24
24
|
}
|
25
25
|
@entity = MongoEntityStoreSpec::DummyEntity.new
|
@@ -42,18 +42,6 @@ describe MongoEntityStore do
|
|
42
42
|
MongoEntityStoreSpec::DummyEntity.should_receive(:new).with({'id' => @id, 'version' => @version})
|
43
43
|
subject
|
44
44
|
end
|
45
|
-
it "should retrieve it's events" do
|
46
|
-
@store.should_receive(:get_events).with(@id, nil)
|
47
|
-
subject
|
48
|
-
end
|
49
|
-
it "should apply each event to the entity" do
|
50
|
-
@events.each do |event| event.should_receive(:apply).with(@entity) end
|
51
|
-
subject
|
52
|
-
end
|
53
|
-
it "should set the entity version to that of the last event" do
|
54
|
-
subject
|
55
|
-
@entity.version.should eq(@events.last.entity_version)
|
56
|
-
end
|
57
45
|
it "should return the entity" do
|
58
46
|
subject.should eq(@entity)
|
59
47
|
end
|
@@ -68,10 +56,6 @@ describe MongoEntityStore do
|
|
68
56
|
MongoEntityStoreSpec::DummyEntity.should_receive(:new).with(@attrs['snapshot'])
|
69
57
|
subject
|
70
58
|
end
|
71
|
-
it "should load the events since the snapshot version" do
|
72
|
-
@store.should_receive(:get_events).with(@id, @snapshot_version)
|
73
|
-
subject
|
74
|
-
end
|
75
59
|
end
|
76
60
|
end
|
77
61
|
|
@@ -80,7 +80,7 @@ describe Store do
|
|
80
80
|
context "when entity has related entities loaded" do
|
81
81
|
before(:each) do
|
82
82
|
@entity = DummyEntityForStore.new(:id => random_string)
|
83
|
-
@entity.version = random_integer * EntityStore.snapshot_threshold + 1
|
83
|
+
@entity.version = random_integer * EntityStore::Config.snapshot_threshold + 1
|
84
84
|
@store = Store.new
|
85
85
|
@related_entity = mock('Entity')
|
86
86
|
@entity.stub(:loaded_related_entities) { [ @related_entity ] }
|
@@ -104,7 +104,7 @@ describe Store do
|
|
104
104
|
before(:each) do
|
105
105
|
@new_id = random_string
|
106
106
|
@entity = DummyEntityForStore.new(:id => random_string)
|
107
|
-
@entity.version = random_integer * EntityStore.snapshot_threshold
|
107
|
+
@entity.version = random_integer * EntityStore::Config.snapshot_threshold
|
108
108
|
@storage_client = mock("StorageClient", :save_entity => true)
|
109
109
|
@store = Store.new
|
110
110
|
@store.stub(:add_events)
|
@@ -161,7 +161,7 @@ describe Store do
|
|
161
161
|
|
162
162
|
context "when entity version is commensurate with snapshotting" do
|
163
163
|
before(:each) do
|
164
|
-
@entity.version = random_integer * EntityStore.snapshot_threshold - 1
|
164
|
+
@entity.version = random_integer * EntityStore::Config.snapshot_threshold - 1
|
165
165
|
end
|
166
166
|
|
167
167
|
it "should snapshot the entity" do
|
@@ -175,9 +175,12 @@ describe Store do
|
|
175
175
|
describe "#get" do
|
176
176
|
before(:each) do
|
177
177
|
@id = random_integer
|
178
|
-
@entity = DummyEntityForStore.new
|
178
|
+
@entity = DummyEntityForStore.new(id: random_string, version: random_integer)
|
179
179
|
DummyEntityForStore.stub(:new).and_return(@entity)
|
180
|
-
@events = [
|
180
|
+
@events = [
|
181
|
+
mock("Event", apply: true, entity_version: @entity.version + 1),
|
182
|
+
mock("Event", apply: true, entity_version: @entity.version + 2)
|
183
|
+
]
|
181
184
|
|
182
185
|
@storage_client = mock("StorageClient", :get_entity => @entity, :get_events => @events)
|
183
186
|
@store = Store.new
|
@@ -197,5 +200,17 @@ describe Store do
|
|
197
200
|
it "should return a ride" do
|
198
201
|
subject.should eq(@entity)
|
199
202
|
end
|
203
|
+
it "should retrieve it's events" do
|
204
|
+
@storage_client.should_receive(:get_events).with(@id, @entity.version)
|
205
|
+
subject
|
206
|
+
end
|
207
|
+
it "should apply each event to the entity" do
|
208
|
+
@events.each do |event| event.should_receive(:apply).with(@entity) end
|
209
|
+
subject
|
210
|
+
end
|
211
|
+
it "should set the entity version to that of the last event" do
|
212
|
+
subject
|
213
|
+
@entity.version.should eq(@events.last.entity_version)
|
214
|
+
end
|
200
215
|
end
|
201
216
|
end
|
data/spec/entity_store_spec.rb
CHANGED
@@ -1,34 +1 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
module Level1
|
4
|
-
module Level2
|
5
|
-
class MyClass
|
6
|
-
end
|
7
|
-
class AnotherClass
|
8
|
-
end
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
describe EntityStore do
|
13
|
-
|
14
|
-
describe "load_type" do
|
15
|
-
|
16
|
-
subject { EntityStore.load_type('Level1::Level2::MyClass') }
|
17
|
-
|
18
|
-
it "should be an Level1::Level2::MyClass" do
|
19
|
-
subject.should eq(Level1::Level2::MyClass)
|
20
|
-
end
|
21
|
-
|
22
|
-
context "when type_loader set" do
|
23
|
-
before(:each) do
|
24
|
-
EntityStore.type_loader = lambda { |type_name|
|
25
|
-
Level1::Level2::AnotherClass
|
26
|
-
}
|
27
|
-
end
|
28
|
-
|
29
|
-
it "should return the result of that type loader" do
|
30
|
-
subject.should eq(Level1::Level2::AnotherClass)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
1
|
+
require 'spec_helper'
|
data/spec/spec_helper.rb
CHANGED
@@ -1,29 +1,18 @@
|
|
1
1
|
require 'rake'
|
2
2
|
require 'rspec'
|
3
|
+
require 'mongo'
|
3
4
|
require "#{Rake.application.original_dir}/lib/entity_store"
|
4
5
|
|
5
6
|
RSpec.configure do |config|
|
6
7
|
config.color_enabled = true
|
7
8
|
end
|
8
9
|
|
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
|
-
|
21
10
|
include EntityStore
|
22
11
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
12
|
+
require 'logger'
|
13
|
+
logger = ::Logger.new(STDOUT)
|
14
|
+
logger.level = ::Logger::ERROR
|
15
|
+
EntityStore::Config.logger = logger
|
27
16
|
|
28
17
|
def random_string
|
29
18
|
(0...24).map{ ('a'..'z').to_a[rand(26)] }.join
|
metadata
CHANGED
@@ -1,52 +1,18 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: entity_store
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.2.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Adam Bird
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2013-03-30 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
|
-
- !ruby/object:Gem::Dependency
|
15
|
-
name: mongo
|
16
|
-
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
|
-
requirements:
|
19
|
-
- - ~>
|
20
|
-
- !ruby/object:Gem::Version
|
21
|
-
version: '1.6'
|
22
|
-
type: :runtime
|
23
|
-
prerelease: false
|
24
|
-
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
|
-
requirements:
|
27
|
-
- - ~>
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: '1.6'
|
30
|
-
- !ruby/object:Gem::Dependency
|
31
|
-
name: bson_ext
|
32
|
-
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
|
-
requirements:
|
35
|
-
- - ~>
|
36
|
-
- !ruby/object:Gem::Version
|
37
|
-
version: '1.6'
|
38
|
-
type: :runtime
|
39
|
-
prerelease: false
|
40
|
-
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
|
-
requirements:
|
43
|
-
- - ~>
|
44
|
-
- !ruby/object:Gem::Version
|
45
|
-
version: '1.6'
|
46
13
|
- !ruby/object:Gem::Dependency
|
47
14
|
name: hatchet
|
48
15
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
16
|
requirements:
|
51
17
|
- - ~>
|
52
18
|
- !ruby/object:Gem::Version
|
@@ -54,12 +20,11 @@ dependencies:
|
|
54
20
|
type: :runtime
|
55
21
|
prerelease: false
|
56
22
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
23
|
requirements:
|
59
24
|
- - ~>
|
60
25
|
- !ruby/object:Gem::Version
|
61
26
|
version: 0.0.20
|
62
|
-
description: Event sourced entity store with a
|
27
|
+
description: Event sourced entity store with a replaceable body
|
63
28
|
email: adam.bird@gmail.com
|
64
29
|
executables: []
|
65
30
|
extensions: []
|
@@ -74,12 +39,14 @@ files:
|
|
74
39
|
- lib/entity_store/event_data_object.rb
|
75
40
|
- lib/entity_store/external_store.rb
|
76
41
|
- lib/entity_store/hash_serialization.rb
|
42
|
+
- lib/entity_store/logging.rb
|
77
43
|
- lib/entity_store/mongo_entity_store.rb
|
78
44
|
- lib/entity_store/not_found.rb
|
79
45
|
- lib/entity_store/store.rb
|
80
46
|
- lib/entity_store/version.rb
|
81
47
|
- lib/entity_store.rb
|
82
48
|
- lib/tasks/entity_store.rake
|
49
|
+
- spec/entity_store/config_spec.rb
|
83
50
|
- spec/entity_store/entity_spec.rb
|
84
51
|
- spec/entity_store/entity_value_spec.rb
|
85
52
|
- spec/entity_store/event_bus_spec.rb
|
@@ -91,29 +58,29 @@ files:
|
|
91
58
|
- spec/spec_helper.rb
|
92
59
|
homepage: http://github.com/adambird/entity_store
|
93
60
|
licenses: []
|
61
|
+
metadata: {}
|
94
62
|
post_install_message:
|
95
63
|
rdoc_options: []
|
96
64
|
require_paths:
|
97
65
|
- lib
|
98
66
|
required_ruby_version: !ruby/object:Gem::Requirement
|
99
|
-
none: false
|
100
67
|
requirements:
|
101
68
|
- - ! '>='
|
102
69
|
- !ruby/object:Gem::Version
|
103
70
|
version: '0'
|
104
71
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
-
none: false
|
106
72
|
requirements:
|
107
73
|
- - ! '>='
|
108
74
|
- !ruby/object:Gem::Version
|
109
75
|
version: '0'
|
110
76
|
requirements: []
|
111
77
|
rubyforge_project:
|
112
|
-
rubygems_version:
|
78
|
+
rubygems_version: 2.0.3
|
113
79
|
signing_key:
|
114
|
-
specification_version:
|
115
|
-
summary: Event sourced entity store with a
|
80
|
+
specification_version: 4
|
81
|
+
summary: Event sourced entity store with a replaceable body
|
116
82
|
test_files:
|
83
|
+
- spec/entity_store/config_spec.rb
|
117
84
|
- spec/entity_store/entity_spec.rb
|
118
85
|
- spec/entity_store/entity_value_spec.rb
|
119
86
|
- spec/entity_store/event_bus_spec.rb
|
@@ -123,4 +90,3 @@ test_files:
|
|
123
90
|
- spec/entity_store/store_spec.rb
|
124
91
|
- spec/entity_store_spec.rb
|
125
92
|
- spec/spec_helper.rb
|
126
|
-
has_rdoc:
|