entity_store 0.0.7 → 0.0.8
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.
- data/lib/entity_store/attributes.rb +18 -0
- data/lib/entity_store/entity.rb +21 -14
- data/lib/entity_store/entity_value.rb +2 -20
- data/lib/entity_store/event.rb +5 -16
- data/lib/entity_store/hash_serialization.rb +32 -0
- data/lib/entity_store/mongo_entity_store.rb +49 -18
- data/lib/entity_store/store.rb +13 -3
- data/lib/entity_store/version.rb +1 -1
- data/lib/entity_store.rb +12 -0
- data/spec/entity_store/entity_spec.rb +16 -0
- data/spec/entity_store/entity_value_spec.rb +7 -3
- data/spec/entity_store/external_store_spec.rb +1 -0
- data/spec/entity_store/mongo_entity_store_spec.rb +85 -3
- data/spec/entity_store/store_spec.rb +24 -15
- metadata +4 -2
@@ -0,0 +1,18 @@
|
|
1
|
+
module EntityStore
|
2
|
+
module Attributes
|
3
|
+
def self.included(klass)
|
4
|
+
klass.class_eval do
|
5
|
+
extend ClassMethods
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def entity_value_attribute(name, klass)
|
11
|
+
define_method(name) { instance_variable_get("@#{name}") }
|
12
|
+
define_method("#{name}=") do |value|
|
13
|
+
instance_variable_set("@#{name}", value.is_a?(Hash) ? klass.new(value) : value)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/entity_store/entity.rb
CHANGED
@@ -2,14 +2,10 @@ module EntityStore
|
|
2
2
|
module Entity
|
3
3
|
attr_accessor :id
|
4
4
|
|
5
|
-
# Holds a reference to the store used to load this entity so the same store
|
6
|
-
# can be used for related entities
|
7
|
-
attr_accessor :related_entity_loader
|
8
|
-
|
9
|
-
attr_writer :version
|
10
|
-
|
11
5
|
def self.included(klass)
|
12
6
|
klass.class_eval do
|
7
|
+
include HashSerialization
|
8
|
+
include Attributes
|
13
9
|
extend ClassMethods
|
14
10
|
end
|
15
11
|
end
|
@@ -24,8 +20,8 @@ module EntityStore
|
|
24
20
|
|
25
21
|
# lazy loader for related entity
|
26
22
|
define_method(name) {
|
27
|
-
if instance_variable_get("@#{name}_id") &&
|
28
|
-
instance_variable_get("@_#{name}") || instance_variable_set("@_#{name}",
|
23
|
+
if instance_variable_get("@#{name}_id") && @_related_entity_loader
|
24
|
+
instance_variable_get("@_#{name}") || instance_variable_set("@_#{name}", @_related_entity_loader.get(instance_variable_get("@#{name}_id")))
|
29
25
|
end
|
30
26
|
}
|
31
27
|
end
|
@@ -37,22 +33,32 @@ module EntityStore
|
|
37
33
|
|
38
34
|
end
|
39
35
|
|
40
|
-
def initialize(attr={})
|
41
|
-
attr.each_pair { |k,v| self.send("#{k}=", v) }
|
42
|
-
end
|
43
|
-
|
44
36
|
def type
|
45
37
|
self.class.name
|
46
38
|
end
|
47
39
|
|
48
40
|
def version
|
49
|
-
@
|
41
|
+
@_version ||= 1
|
50
42
|
end
|
51
|
-
|
43
|
+
|
44
|
+
def version=(value)
|
45
|
+
@_version = value
|
46
|
+
end
|
47
|
+
|
48
|
+
# Holds a reference to the store used to load this entity so the same store
|
49
|
+
# can be used for related entities
|
50
|
+
def related_entity_loader=(value)
|
51
|
+
@_related_entity_loader = value
|
52
|
+
end
|
53
|
+
|
52
54
|
def pending_events
|
53
55
|
@pending_events ||= []
|
54
56
|
end
|
55
57
|
|
58
|
+
def clear_pending_events
|
59
|
+
@pending_events = []
|
60
|
+
end
|
61
|
+
|
56
62
|
def record_event(event)
|
57
63
|
apply_event(event)
|
58
64
|
pending_events<<event
|
@@ -61,5 +67,6 @@ module EntityStore
|
|
61
67
|
def apply_event(event)
|
62
68
|
event.apply(self)
|
63
69
|
end
|
70
|
+
|
64
71
|
end
|
65
72
|
end
|
@@ -2,21 +2,11 @@ module EntityStore
|
|
2
2
|
module EntityValue
|
3
3
|
def self.included(klass)
|
4
4
|
klass.class_eval do
|
5
|
-
|
5
|
+
include HashSerialization
|
6
|
+
include Attributes
|
6
7
|
end
|
7
8
|
end
|
8
9
|
|
9
|
-
def initialize(attr={})
|
10
|
-
attr.each_pair { |k,v| self.send("#{k}=", v) if self.respond_to? "#{k}=" }
|
11
|
-
end
|
12
|
-
|
13
|
-
def attributes
|
14
|
-
Hash[*public_methods.select {|m| m =~ /\w\=$/}.collect do |m|
|
15
|
-
attribute_name = m.to_s.chop.to_sym
|
16
|
-
[attribute_name, send(attribute_name).respond_to?(:attributes) ? send(attribute_name).attributes : send(attribute_name)]
|
17
|
-
end.flatten]
|
18
|
-
end
|
19
|
-
|
20
10
|
def ==(other)
|
21
11
|
attributes.each_key do |attr|
|
22
12
|
return false unless other.respond_to?(attr) && send(attr) == other.send(attr)
|
@@ -24,13 +14,5 @@ module EntityStore
|
|
24
14
|
return true
|
25
15
|
end
|
26
16
|
|
27
|
-
module ClassMethods
|
28
|
-
def entity_value_attribute(name, klass)
|
29
|
-
define_method(name) { instance_variable_get("@#{name}") }
|
30
|
-
define_method("#{name}=") do |value|
|
31
|
-
instance_variable_set("@#{name}", value.is_a?(Hash) ? klass.new(value) : value)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
17
|
end
|
36
18
|
end
|
data/lib/entity_store/event.rb
CHANGED
@@ -2,9 +2,11 @@ module EntityStore
|
|
2
2
|
module Event
|
3
3
|
attr_accessor :entity_id, :entity_version
|
4
4
|
|
5
|
-
def
|
6
|
-
|
7
|
-
|
5
|
+
def self.included(klass)
|
6
|
+
klass.class_eval do
|
7
|
+
include Attributes
|
8
|
+
include HashSerialization
|
9
|
+
extend ClassMethods
|
8
10
|
end
|
9
11
|
end
|
10
12
|
|
@@ -17,19 +19,6 @@ module EntityStore
|
|
17
19
|
downcase
|
18
20
|
end
|
19
21
|
|
20
|
-
def attributes
|
21
|
-
Hash[*public_methods.select {|m| m =~ /\w\=$/}.collect do |m|
|
22
|
-
attribute_name = m.to_s.chop.to_sym
|
23
|
-
[attribute_name, send(attribute_name).respond_to?(:attributes) ? send(attribute_name).attributes : send(attribute_name)]
|
24
|
-
end.flatten]
|
25
|
-
end
|
26
|
-
|
27
|
-
def self.included(klass)
|
28
|
-
klass.class_eval do
|
29
|
-
extend ClassMethods
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
22
|
module ClassMethods
|
34
23
|
def time_attribute(*names)
|
35
24
|
class_eval do
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module EntityStore
|
2
|
+
module HashSerialization
|
3
|
+
|
4
|
+
def initialize(attr={})
|
5
|
+
attr.each_pair { |k,v| send("#{k}=", v) if respond_to?("#{k}=") }
|
6
|
+
end
|
7
|
+
|
8
|
+
# Public - generate attributes hash
|
9
|
+
# did use flatten but this came a-cropper when the attribute value was an array
|
10
|
+
def attributes
|
11
|
+
attrs = {}
|
12
|
+
public_methods
|
13
|
+
.select { |m| m =~ /\w\=$/ }
|
14
|
+
.select { |m| respond_to?(m.to_s.chop) }
|
15
|
+
.collect { |m| m.to_s.chop.to_sym }
|
16
|
+
.collect { |m| [m, attribute_value(send(m))] }
|
17
|
+
.each do |item| attrs[item[0]] = item[1] end
|
18
|
+
attrs
|
19
|
+
end
|
20
|
+
|
21
|
+
def attribute_value(value)
|
22
|
+
if value.respond_to?(:attributes)
|
23
|
+
value.attributes
|
24
|
+
elsif value.respond_to?(:each)
|
25
|
+
value.collect { |v| attribute_value(v) }
|
26
|
+
else
|
27
|
+
value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -28,7 +28,8 @@ module EntityStore
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def ensure_indexes
|
31
|
-
|
31
|
+
events.ensure_index([['entity_id', Mongo::ASCENDING], ['_id', Mongo::ASCENDING]])
|
32
|
+
events.ensure_index([['entity_id', Mongo::ASCENDING], ['entity_version', Mongo::ASCENDING], ['_id', Mongo::ASCENDING]])
|
32
33
|
end
|
33
34
|
|
34
35
|
def add_entity(entity)
|
@@ -39,6 +40,20 @@ module EntityStore
|
|
39
40
|
entities.update({'_id' => BSON::ObjectId.from_string(entity.id)}, { '$set' => { 'version' => entity.version } })
|
40
41
|
end
|
41
42
|
|
43
|
+
# Public - create a snapshot of the entity and store in the entities collection
|
44
|
+
#
|
45
|
+
def snapshot_entity(entity)
|
46
|
+
query = {'_id' => BSON::ObjectId.from_string(entity.id)}
|
47
|
+
updates = { '$set' => { 'snapshot' => entity.attributes } }
|
48
|
+
entities.update(query, updates, { :upsert => true} )
|
49
|
+
end
|
50
|
+
|
51
|
+
# Public - remove the snapshot for an entity
|
52
|
+
#
|
53
|
+
def remove_entity_snapshot(id)
|
54
|
+
entities.update({'_id' => BSON::ObjectId.from_string(id)}, { '$unset' => { 'snapshot' => 1}})
|
55
|
+
end
|
56
|
+
|
42
57
|
def add_event(event)
|
43
58
|
events.insert({'_type' => event.class.name, '_entity_id' => BSON::ObjectId.from_string(event.entity_id) }.merge(event.attributes) ).to_s
|
44
59
|
end
|
@@ -47,28 +62,44 @@ module EntityStore
|
|
47
62
|
get_entity(id, true)
|
48
63
|
end
|
49
64
|
|
65
|
+
# Public - loads the entity from the store, including any available snapshots
|
66
|
+
# then loads the events to complete the state
|
67
|
+
#
|
68
|
+
# id - String representation of BSON::ObjectId
|
69
|
+
# raise_exception - Boolean indicating whether to raise an exception if not found (default=false)
|
70
|
+
#
|
71
|
+
# Returns an object of the entity type
|
50
72
|
def get_entity(id, raise_exception=false)
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
end
|
60
|
-
end
|
61
|
-
rescue BSON::InvalidObjectId
|
62
|
-
if raise_exception
|
63
|
-
raise NotFound.new(id)
|
64
|
-
else
|
65
|
-
return nil
|
73
|
+
if attrs = entities.find_one('_id' => BSON::ObjectId.from_string(id))
|
74
|
+
entity = get_type_constant(attrs['_type']).new(attrs['snapshot'] || {'id' => id, 'version' => attrs['version']})
|
75
|
+
|
76
|
+
since_version = attrs['snapshot'] ? attrs['snapshot']['version'] : nil
|
77
|
+
|
78
|
+
get_events(id, since_version).each do |e|
|
79
|
+
e.apply(entity)
|
80
|
+
entity.version = e.entity_version
|
66
81
|
end
|
82
|
+
|
83
|
+
entity
|
84
|
+
else
|
85
|
+
raise NotFound.new(id) if raise_exception
|
86
|
+
nil
|
67
87
|
end
|
88
|
+
rescue BSON::InvalidObjectId
|
89
|
+
raise NotFound.new(id) if raise_exception
|
90
|
+
nil
|
68
91
|
end
|
69
92
|
|
70
|
-
def get_events(id)
|
71
|
-
|
93
|
+
def get_events(id, since_version=nil)
|
94
|
+
|
95
|
+
query = { '_entity_id' => BSON::ObjectId.from_string(id) }
|
96
|
+
query['entity_version'] = { '$gt' => since_version } if since_version
|
97
|
+
|
98
|
+
options = {
|
99
|
+
:sort => [['entity_version', Mongo::ASCENDING], ['_id', Mongo::ASCENDING]]
|
100
|
+
}
|
101
|
+
|
102
|
+
events.find(query, options).collect do |attrs|
|
72
103
|
begin
|
73
104
|
get_type_constant(attrs['_type']).new(attrs)
|
74
105
|
rescue => e
|
data/lib/entity_store/store.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module EntityStore
|
2
2
|
class Store
|
3
3
|
def storage_client
|
4
|
-
@
|
4
|
+
@_storage_client ||= MongoEntityStore.new
|
5
5
|
end
|
6
6
|
|
7
7
|
def add(entity)
|
@@ -29,6 +29,7 @@ module EntityStore
|
|
29
29
|
entity.id = storage_client.add_entity(entity)
|
30
30
|
end
|
31
31
|
add_events(entity)
|
32
|
+
snapshot_entity(entity) if entity.version % EntityStore.snapshot_threshold == 0
|
32
33
|
end
|
33
34
|
entity
|
34
35
|
rescue => e
|
@@ -36,6 +37,15 @@ module EntityStore
|
|
36
37
|
raise e
|
37
38
|
end
|
38
39
|
|
40
|
+
def snapshot_entity(entity)
|
41
|
+
EntityStore.logger.info { "Store#snapshot_entity : Snapshotting #{entity.id}"}
|
42
|
+
storage_client.snapshot_entity(entity)
|
43
|
+
end
|
44
|
+
|
45
|
+
def remove_entity_snapshot(id)
|
46
|
+
storage_client.remove_entity_snapshot(id)
|
47
|
+
end
|
48
|
+
|
39
49
|
def add_events(entity)
|
40
50
|
entity.pending_events.each do |e|
|
41
51
|
e.entity_id = entity.id.to_s
|
@@ -43,6 +53,7 @@ module EntityStore
|
|
43
53
|
storage_client.add_event(e)
|
44
54
|
end
|
45
55
|
entity.pending_events.each {|e| EventBus.publish(entity.type, e) }
|
56
|
+
entity.clear_pending_events
|
46
57
|
end
|
47
58
|
|
48
59
|
def get!(id)
|
@@ -51,7 +62,6 @@ module EntityStore
|
|
51
62
|
|
52
63
|
def get(id, raise_exception=false)
|
53
64
|
if entity = storage_client.get_entity(id, raise_exception)
|
54
|
-
storage_client.get_events(id).each { |e| e.apply(entity) }
|
55
65
|
# assign this entity loader to allow lazy loading of related entities
|
56
66
|
entity.related_entity_loader = self
|
57
67
|
end
|
@@ -64,7 +74,7 @@ module EntityStore
|
|
64
74
|
def clear_all
|
65
75
|
storage_client.entities.drop
|
66
76
|
storage_client.events.drop
|
67
|
-
@
|
77
|
+
@_storage_client = nil
|
68
78
|
end
|
69
79
|
|
70
80
|
end
|
data/lib/entity_store/version.rb
CHANGED
data/lib/entity_store.rb
CHANGED
@@ -9,6 +9,8 @@ module EntityStore
|
|
9
9
|
require 'entity_store/mongo_entity_store'
|
10
10
|
require 'entity_store/event_bus'
|
11
11
|
require 'entity_store/not_found'
|
12
|
+
require 'entity_store/hash_serialization'
|
13
|
+
require 'entity_store/attributes'
|
12
14
|
|
13
15
|
class << self
|
14
16
|
def setup
|
@@ -35,6 +37,16 @@ module EntityStore
|
|
35
37
|
@_event_subscribers ||=[]
|
36
38
|
end
|
37
39
|
|
40
|
+
# Public - indicates the version increment that is used to
|
41
|
+
# decided whether a snapshot of an entity should be created when it's saved
|
42
|
+
def snapshot_threshold
|
43
|
+
@_snapshot_threshold ||= 10
|
44
|
+
end
|
45
|
+
|
46
|
+
def snapshot_threshold=(value)
|
47
|
+
@_snapshot_threshold = value
|
48
|
+
end
|
49
|
+
|
38
50
|
def log_level
|
39
51
|
@_log_level ||= Logger::INFO
|
40
52
|
end
|
@@ -5,6 +5,8 @@ class DummyEntity
|
|
5
5
|
|
6
6
|
related_entities :club, :user
|
7
7
|
|
8
|
+
attr_accessor :name, :description, :members
|
9
|
+
|
8
10
|
end
|
9
11
|
|
10
12
|
describe Entity do
|
@@ -66,4 +68,18 @@ describe Entity do
|
|
66
68
|
end
|
67
69
|
end
|
68
70
|
end
|
71
|
+
|
72
|
+
describe "#attributes" do
|
73
|
+
before(:each) do
|
74
|
+
@entity = DummyEntity.new(:id => @id = random_object_id, :club_id => @club_id = random_string,
|
75
|
+
:user_id => @user_id = random_string, :name => @name = random_string, :version => @version = random_integer,
|
76
|
+
:members => [])
|
77
|
+
end
|
78
|
+
|
79
|
+
subject { @entity.attributes }
|
80
|
+
|
81
|
+
it "returns a hash of the attributes" do
|
82
|
+
subject.should eq({:id => @id, :version => @version, :name => @name, :club_id => @club_id, :user_id => @user_id, :description => nil, :members => []})
|
83
|
+
end
|
84
|
+
end
|
69
85
|
end
|
@@ -32,8 +32,11 @@ describe EntityValue do
|
|
32
32
|
before(:each) do
|
33
33
|
@value = DummyEntityValue.new(:name => @name, :home => @home)
|
34
34
|
end
|
35
|
-
|
36
|
-
|
35
|
+
|
36
|
+
subject { @value.attributes }
|
37
|
+
|
38
|
+
it "should return hash of attributes and type" do
|
39
|
+
subject.should eq({:name => @name, :home => @home})
|
37
40
|
end
|
38
41
|
context "nested attributes" do
|
39
42
|
before(:each) do
|
@@ -42,8 +45,9 @@ describe EntityValue do
|
|
42
45
|
@value.home = NestedEntityValue.new(:street => @street, :town => @town)
|
43
46
|
end
|
44
47
|
it "should return a hash containing the nested attribute" do
|
45
|
-
|
48
|
+
subject.should eq({:name => @name, :home => {:street => @street, :town => @town}})
|
46
49
|
end
|
50
|
+
|
47
51
|
end
|
48
52
|
|
49
53
|
end
|
@@ -7,12 +7,80 @@ module Level1
|
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
10
|
+
module MongoEntityStoreSpec
|
11
|
+
class DummyEntity
|
12
|
+
include Entity
|
13
|
+
|
14
|
+
attr_accessor :name, :description
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
10
19
|
describe MongoEntityStore do
|
11
20
|
before(:each) do
|
12
21
|
EntityStore.connection_profile = "mongodb://localhost/entity_store_default"
|
13
22
|
@store = MongoEntityStore.new
|
14
23
|
end
|
24
|
+
|
25
|
+
describe "#get_entity" do
|
26
|
+
before(:each) do
|
27
|
+
@id = random_object_id
|
28
|
+
@attrs = {
|
29
|
+
'_type' => "MongoEntityStoreSpec::DummyEntity",
|
30
|
+
'version' => @version = random_integer
|
31
|
+
}
|
32
|
+
@entity = MongoEntityStoreSpec::DummyEntity.new
|
33
|
+
MongoEntityStoreSpec::DummyEntity.stub(:new) { @entity }
|
34
|
+
@entities_collection = mock('MongoCollection', :find_one => @attrs)
|
35
|
+
@store.stub(:entities) { @entities_collection }
|
36
|
+
@events = [
|
37
|
+
mock('Event', :apply => true, :entity_version => random_integer), mock('Event', :apply => true, :entity_version => random_integer)
|
38
|
+
]
|
39
|
+
@store.stub(:get_events) { @events }
|
40
|
+
end
|
41
|
+
|
42
|
+
subject { @store.get_entity(@id) }
|
43
|
+
|
44
|
+
it "should attempt to retrieve the entity record from the store" do
|
45
|
+
@entities_collection.should_receive(:find_one).with({'_id' => BSON::ObjectId.from_string(@id)})
|
46
|
+
subject
|
47
|
+
end
|
48
|
+
it "should construct a new entity" do
|
49
|
+
MongoEntityStoreSpec::DummyEntity.should_receive(:new).with({'id' => @id, 'version' => @version})
|
50
|
+
subject
|
51
|
+
end
|
52
|
+
it "should retrieve it's events" do
|
53
|
+
@store.should_receive(:get_events).with(@id, nil)
|
54
|
+
subject
|
55
|
+
end
|
56
|
+
it "should apply each event to the entity" do
|
57
|
+
@events.each do |event| event.should_receive(:apply).with(@entity) end
|
58
|
+
subject
|
59
|
+
end
|
60
|
+
it "should set the entity version to that of the last event" do
|
61
|
+
subject
|
62
|
+
@entity.version.should eq(@events.last.entity_version)
|
63
|
+
end
|
64
|
+
context "when a snapshot exists" do
|
65
|
+
before(:each) do
|
66
|
+
@attrs['snapshot'] = {
|
67
|
+
'version' => @snapshot_version = random_integer,
|
68
|
+
'name' => @name = random_string
|
69
|
+
}
|
70
|
+
end
|
71
|
+
it "should construct a new entity with from the snapshot" do
|
72
|
+
MongoEntityStoreSpec::DummyEntity.should_receive(:new).with(@attrs['snapshot'])
|
73
|
+
subject
|
74
|
+
end
|
75
|
+
it "should load the events since the snapshot version" do
|
76
|
+
@store.should_receive(:get_events).with(@id, @snapshot_version)
|
77
|
+
subject
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
15
82
|
describe "#get_entity!" do
|
83
|
+
|
16
84
|
context "when invalid id format passed" do
|
17
85
|
|
18
86
|
subject { @store.get_entity!(random_string) }
|
@@ -22,9 +90,6 @@ describe MongoEntityStore do
|
|
22
90
|
end
|
23
91
|
end
|
24
92
|
context "when valid id format passed but no object exists" do
|
25
|
-
before(:each) do
|
26
|
-
@store = MongoEntityStore.new
|
27
|
-
end
|
28
93
|
|
29
94
|
subject { @store.get_entity!(random_object_id) }
|
30
95
|
|
@@ -43,4 +108,21 @@ describe MongoEntityStore do
|
|
43
108
|
subject.should eq(Level1::Level2::MyClass)
|
44
109
|
end
|
45
110
|
end
|
111
|
+
|
112
|
+
describe "#snapshot_entity" do
|
113
|
+
before(:each) do
|
114
|
+
@entity = MongoEntityStoreSpec::DummyEntity.new(:id => random_object_id, :version => random_integer, :name => random_string)
|
115
|
+
end
|
116
|
+
|
117
|
+
subject { @store.snapshot_entity(@entity) }
|
118
|
+
|
119
|
+
it "should add a snaphot to the entity record" do
|
120
|
+
subject
|
121
|
+
saved_entity = @store.entities.find_one({'_id' => BSON::ObjectId.from_string(@entity.id)})['snapshot']
|
122
|
+
saved_entity['id'].should eq(@entity.id)
|
123
|
+
saved_entity['version'].should eq(@entity.version)
|
124
|
+
saved_entity['name'].should eq(@entity.name)
|
125
|
+
saved_entity['description'].should eq(@entity.description)
|
126
|
+
end
|
127
|
+
end
|
46
128
|
end
|
@@ -79,6 +79,7 @@ describe Store do
|
|
79
79
|
context "when entity has related entities loaded" do
|
80
80
|
before(:each) do
|
81
81
|
@entity = DummyEntityForStore.new(:id => random_string)
|
82
|
+
@entity.version = random_integer * EntityStore.snapshot_threshold + 1
|
82
83
|
@store = Store.new
|
83
84
|
@related_entity = mock('Entity')
|
84
85
|
@entity.stub(:loaded_related_entities) { [ @related_entity ] }
|
@@ -96,13 +97,13 @@ describe Store do
|
|
96
97
|
subject
|
97
98
|
end
|
98
99
|
end
|
99
|
-
|
100
100
|
end
|
101
101
|
|
102
102
|
describe "#do_save" do
|
103
103
|
before(:each) do
|
104
104
|
@new_id = random_string
|
105
105
|
@entity = DummyEntityForStore.new(:id => random_string)
|
106
|
+
@entity.version = random_integer * EntityStore.snapshot_threshold
|
106
107
|
@storage_client = mock("StorageClient", :save_entity => true)
|
107
108
|
@store = Store.new
|
108
109
|
@store.stub(:add_events)
|
@@ -113,8 +114,7 @@ describe Store do
|
|
113
114
|
subject { @store.do_save(@entity) }
|
114
115
|
|
115
116
|
it "increments the entity version number" do
|
116
|
-
|
117
|
-
subject
|
117
|
+
expect { subject }.to change { @entity.version }.by 1
|
118
118
|
end
|
119
119
|
it "save the new entity to the store" do
|
120
120
|
@storage_client.should_receive(:save_entity).with(@entity)
|
@@ -127,6 +127,11 @@ describe Store do
|
|
127
127
|
it "returns a reference to the entity" do
|
128
128
|
subject.should eq(@entity)
|
129
129
|
end
|
130
|
+
it "should not snapshot the entity" do
|
131
|
+
@store.should_not_receive(:snapshot_entity)
|
132
|
+
subject
|
133
|
+
end
|
134
|
+
|
130
135
|
context "when no pending events" do
|
131
136
|
before(:each) do
|
132
137
|
@entity.stub(:pending_events) { [] }
|
@@ -140,6 +145,7 @@ describe Store do
|
|
140
145
|
subject
|
141
146
|
end
|
142
147
|
end
|
148
|
+
|
143
149
|
context "when entity doesn't have an id" do
|
144
150
|
before(:each) do
|
145
151
|
@entity.id = nil
|
@@ -151,13 +157,25 @@ describe Store do
|
|
151
157
|
subject
|
152
158
|
end
|
153
159
|
end
|
160
|
+
|
161
|
+
context "when entity version is commensurate with snapshotting" do
|
162
|
+
before(:each) do
|
163
|
+
@entity.version = random_integer * EntityStore.snapshot_threshold - 1
|
164
|
+
end
|
165
|
+
|
166
|
+
it "should snapshot the entity" do
|
167
|
+
@storage_client.should_receive(:snapshot_entity).with(@entity)
|
168
|
+
subject
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
154
172
|
end
|
155
173
|
|
156
174
|
describe "#get" do
|
157
175
|
before(:each) do
|
158
176
|
@id = random_integer
|
159
177
|
@entity = DummyEntityForStore.new
|
160
|
-
DummyEntityForStore.stub(:new).and_return(@
|
178
|
+
DummyEntityForStore.stub(:new).and_return(@entity)
|
161
179
|
@events = [mock("Event", :apply => true), mock("Event", :apply => true)]
|
162
180
|
|
163
181
|
@storage_client = mock("StorageClient", :get_entity => @entity, :get_events => @events)
|
@@ -171,18 +189,9 @@ describe Store do
|
|
171
189
|
@storage_client.should_receive(:get_entity).with(@id, false)
|
172
190
|
subject
|
173
191
|
end
|
174
|
-
it "should retrieve the events for the entity" do
|
175
|
-
@storage_client.should_receive(:get_events).with(@id)
|
176
|
-
subject
|
177
|
-
end
|
178
|
-
it "should apply each event" do
|
179
|
-
@events.each do |e|
|
180
|
-
e.should_receive(:apply).with(@entity)
|
181
|
-
end
|
182
|
-
subject
|
183
|
-
end
|
184
192
|
it "should assign itself as the related_entity_loader" do
|
185
|
-
|
193
|
+
@entity.should_receive(:related_entity_loader=).with(@store)
|
194
|
+
subject
|
186
195
|
end
|
187
196
|
it "should return a ride" do
|
188
197
|
subject.should eq(@entity)
|
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.
|
4
|
+
version: 0.0.8
|
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-
|
12
|
+
date: 2012-10-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: mongo
|
@@ -49,6 +49,7 @@ executables: []
|
|
49
49
|
extensions: []
|
50
50
|
extra_rdoc_files: []
|
51
51
|
files:
|
52
|
+
- lib/entity_store/attributes.rb
|
52
53
|
- lib/entity_store/config.rb
|
53
54
|
- lib/entity_store/entity.rb
|
54
55
|
- lib/entity_store/entity_value.rb
|
@@ -56,6 +57,7 @@ files:
|
|
56
57
|
- lib/entity_store/event_bus.rb
|
57
58
|
- lib/entity_store/event_data_object.rb
|
58
59
|
- lib/entity_store/external_store.rb
|
60
|
+
- lib/entity_store/hash_serialization.rb
|
59
61
|
- lib/entity_store/mongo_entity_store.rb
|
60
62
|
- lib/entity_store/not_found.rb
|
61
63
|
- lib/entity_store/store.rb
|