entity_store 0.1.6 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|