entity_store 0.2.15 → 0.3.1
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 +4 -4
- data/lib/entity_store/entity.rb +0 -29
- data/lib/entity_store/mongo_entity_store.rb +48 -27
- data/lib/entity_store/store.rb +48 -16
- data/lib/entity_store/version.rb +1 -1
- data/spec/entity_store/entity_spec.rb +5 -66
- data/spec/entity_store/mongo_entity_store_spec.rb +22 -22
- data/spec/entity_store/store_spec.rb +74 -58
- metadata +10 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 80ef2d2f7cb967ed15151bd5d3fa17c5526c4022
|
4
|
+
data.tar.gz: ee08573ec53e96fa70df0cb30c48db9df19d56de
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 653358d1589d59332805e9501e41c637bc2bbb4a4292b19ca0586871cb7352cbb709f2a23e97813cfdcb2be823414b61a9f50acd01e5983c002d08dfcd4d05e3
|
7
|
+
data.tar.gz: e2fde849bd73b14d086f78fb2268304c55771f85f74d21db3c0b74b4f800c8856e369767e82cdc353c8efa9cbf9d06ac7027f4ad69c8daa7cc289c903eb7d7b3
|
data/lib/entity_store/entity.rb
CHANGED
@@ -6,7 +6,6 @@ module EntityStore
|
|
6
6
|
klass.class_eval do
|
7
7
|
include HashSerialization
|
8
8
|
include Attributes
|
9
|
-
extend ClassMethods
|
10
9
|
|
11
10
|
version_incremented_event_class = "#{self.name}VersionIncremented".split('::').inject(Object) {|obj, name|
|
12
11
|
obj.const_defined?(name) ? obj.const_get(name) : obj.const_set(name, Class.new)
|
@@ -24,28 +23,6 @@ module EntityStore
|
|
24
23
|
end
|
25
24
|
end
|
26
25
|
|
27
|
-
module ClassMethods
|
28
|
-
|
29
|
-
def related_entities(*names)
|
30
|
-
names.each do |name|
|
31
|
-
# attr accessor for the id
|
32
|
-
define_method("#{name}_id") { instance_variable_get("@#{name}_id")}
|
33
|
-
define_method("#{name}_id=") do |value| instance_variable_set("@#{name}_id", value) end
|
34
|
-
|
35
|
-
# lazy loader for related entity
|
36
|
-
define_method(name) {
|
37
|
-
if instance_variable_get("@#{name}_id") && @_related_entity_loader
|
38
|
-
instance_variable_get("@_#{name}") || instance_variable_set("@_#{name}", @_related_entity_loader.get(instance_variable_get("@#{name}_id")))
|
39
|
-
end
|
40
|
-
}
|
41
|
-
end
|
42
|
-
|
43
|
-
define_method(:loaded_related_entities) {
|
44
|
-
names.collect{ |name| instance_variable_get("@_#{name}") }.select{|entity| !entity.nil? }
|
45
|
-
}
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
26
|
def type
|
50
27
|
self.class.name
|
51
28
|
end
|
@@ -63,12 +40,6 @@ module EntityStore
|
|
63
40
|
event_class.new(:entity_id => id, :version => version)
|
64
41
|
end
|
65
42
|
|
66
|
-
# Holds a reference to the store used to load this entity so the same store
|
67
|
-
# can be used for related entities
|
68
|
-
def related_entity_loader=(value)
|
69
|
-
@_related_entity_loader = value
|
70
|
-
end
|
71
|
-
|
72
43
|
def pending_events
|
73
44
|
@pending_events ||= []
|
74
45
|
end
|
@@ -51,7 +51,7 @@ module EntityStore
|
|
51
51
|
entities.update({'_id' => BSON::ObjectId.from_string(entity.id)}, { '$set' => { 'version' => entity.version } })
|
52
52
|
end
|
53
53
|
|
54
|
-
# Public
|
54
|
+
# Public: create a snapshot of the entity and store in the entities collection
|
55
55
|
#
|
56
56
|
def snapshot_entity(entity)
|
57
57
|
query = {'_id' => BSON::ObjectId.from_string(entity.id)}
|
@@ -74,7 +74,7 @@ module EntityStore
|
|
74
74
|
entities.update({'_id' => BSON::ObjectId.from_string(id)}, { '$unset' => { 'snapshot' => 1}})
|
75
75
|
end
|
76
76
|
|
77
|
-
# Public
|
77
|
+
# Public: remove all snapshots
|
78
78
|
#
|
79
79
|
# type - String optional class name for the entity
|
80
80
|
#
|
@@ -88,19 +88,26 @@ module EntityStore
|
|
88
88
|
events.insert({'_type' => event.class.name, '_entity_id' => BSON::ObjectId.from_string(event.entity_id) }.merge(event.attributes) ).to_s
|
89
89
|
end
|
90
90
|
|
91
|
-
|
92
|
-
get_entity(id, true)
|
93
|
-
end
|
94
|
-
|
95
|
-
# Public - loads the entity from the store, including any available snapshots
|
91
|
+
# Public: loads the entity from the store, including any available snapshots
|
96
92
|
# then loads the events to complete the state
|
97
93
|
#
|
98
|
-
#
|
99
|
-
#
|
94
|
+
# ids - Array of Strings representation of BSON::ObjectId
|
95
|
+
# options - Hash of options (default: {})
|
96
|
+
# :raise_exception - Boolean (default: true)
|
100
97
|
#
|
101
|
-
# Returns an
|
102
|
-
def
|
103
|
-
|
98
|
+
# Returns an array of entities
|
99
|
+
def get_entities(ids, options={})
|
100
|
+
|
101
|
+
object_ids = ids.map do |id|
|
102
|
+
begin
|
103
|
+
BSON::ObjectId.from_string(id)
|
104
|
+
rescue BSON::InvalidObjectId
|
105
|
+
raise NotFound.new(id) if options.fetch(:raise_exception, true)
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
entities.find('_id' => { '$in' => object_ids }).map do |attrs|
|
104
111
|
begin
|
105
112
|
entity_type = EntityStore::Config.load_type(attrs['_type'])
|
106
113
|
|
@@ -111,41 +118,55 @@ module EntityStore
|
|
111
118
|
attrs.delete('snapshot') unless active_key == attrs['snapshot_key']
|
112
119
|
end
|
113
120
|
|
114
|
-
entity = entity_type.new(attrs['snapshot'] || {'id' =>
|
121
|
+
entity = entity_type.new(attrs['snapshot'] || {'id' => attrs['_id'].to_s })
|
115
122
|
rescue => e
|
116
123
|
log_error "Error loading type #{attrs['_type']}", e
|
117
124
|
raise
|
118
125
|
end
|
119
126
|
|
120
127
|
entity
|
121
|
-
else
|
122
|
-
raise NotFound.new(id) if raise_exception
|
123
|
-
nil
|
124
128
|
end
|
125
|
-
|
126
|
-
raise NotFound.new(id) if raise_exception
|
127
|
-
nil
|
129
|
+
|
128
130
|
end
|
129
131
|
|
130
|
-
|
131
|
-
|
132
|
+
# Public: get events for an array of criteria objects
|
133
|
+
# because each entity could have a different reference
|
134
|
+
# version this allows optional criteria to be specifed
|
135
|
+
#
|
136
|
+
#
|
137
|
+
# criteria - Hash :id mandatory, :since_version optional
|
138
|
+
#
|
139
|
+
# Examples
|
140
|
+
#
|
141
|
+
# get_events_for_criteria([ { id: "23232323"}, { id: "2398429834", since_version: 4 } ] )
|
142
|
+
#
|
143
|
+
# Returns Hash with id as key and Array of Event instances as value
|
144
|
+
def get_events(criteria)
|
145
|
+
return {} if criteria.empty?
|
146
|
+
|
147
|
+
query_items = criteria.map do |item|
|
148
|
+
raise ArgumentError.new(":id missing from criteria") unless item[:id]
|
149
|
+
item_query = { '_entity_id' => BSON::ObjectId.from_string(item[:id]) }
|
150
|
+
item_query['entity_version'] = { '$gt' => item[:since_version] } if item[:since_version]
|
151
|
+
item_query
|
152
|
+
end
|
132
153
|
|
133
|
-
query
|
154
|
+
query = { '$or' => query_items }
|
134
155
|
|
135
156
|
options = {
|
136
157
|
:sort => [['entity_version', Mongo::ASCENDING], ['_id', Mongo::ASCENDING]]
|
137
158
|
}
|
138
159
|
|
139
|
-
|
160
|
+
result = Hash[ criteria.map { |item| [ item[:id], [] ] } ]
|
161
|
+
|
162
|
+
events.find(query, options).each do |attrs|
|
140
163
|
begin
|
141
|
-
EntityStore::Config.load_type(attrs['_type']).new(attrs)
|
164
|
+
result[attrs['_entity_id'].to_s] << EntityStore::Config.load_type(attrs['_type']).new(attrs)
|
142
165
|
rescue => e
|
143
166
|
log_error "Error loading type #{attrs['_type']}", e
|
144
|
-
nil
|
145
167
|
end
|
146
168
|
end
|
147
|
-
|
148
|
-
loaded_events.compact
|
169
|
+
result
|
149
170
|
end
|
150
171
|
end
|
151
172
|
end
|
data/lib/entity_store/store.rb
CHANGED
@@ -20,12 +20,6 @@ module EntityStore
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def save(entity)
|
23
|
-
do_save entity
|
24
|
-
entity.loaded_related_entities.each do |e| do_save e end if entity.respond_to?(:loaded_related_entities)
|
25
|
-
entity
|
26
|
-
end
|
27
|
-
|
28
|
-
def do_save(entity)
|
29
23
|
# need to look at concurrency if we start storing version on client
|
30
24
|
unless entity.pending_events.empty?
|
31
25
|
entity.version += 1
|
@@ -42,7 +36,7 @@ module EntityStore
|
|
42
36
|
end
|
43
37
|
entity
|
44
38
|
rescue => e
|
45
|
-
log_error "Store#
|
39
|
+
log_error "Store#save error: #{e.inspect} - #{entity.inspect}", e
|
46
40
|
raise e
|
47
41
|
end
|
48
42
|
|
@@ -74,29 +68,67 @@ module EntityStore
|
|
74
68
|
end
|
75
69
|
|
76
70
|
def get(id, raise_exception=false)
|
77
|
-
|
78
|
-
|
71
|
+
options = {
|
72
|
+
raise_exception: raise_exception
|
73
|
+
}
|
79
74
|
|
80
|
-
|
75
|
+
get_with_ids([id], options).first
|
76
|
+
end
|
77
|
+
|
78
|
+
# Public: get a series of entities
|
79
|
+
#
|
80
|
+
# ids - Array of id strings
|
81
|
+
# options - Hash of options (default: {})
|
82
|
+
# :raise_exception - Boolean (default true)
|
83
|
+
#
|
84
|
+
# Returns an Array of entities
|
85
|
+
def get_with_ids(ids, options={})
|
86
|
+
|
87
|
+
entities = Hash[ storage_client.get_entities(ids, options).map { |e| [ e.id, e] } ]
|
88
|
+
|
89
|
+
if options.fetch(:raise_exception, true)
|
90
|
+
ids.each do |id|
|
91
|
+
raise NotFound.new(id) unless entities[id]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
criteria = entities.map do |id, entity|
|
96
|
+
{ id: id, since_version: entity.version }
|
97
|
+
end
|
98
|
+
|
99
|
+
events = storage_client.get_events(criteria)
|
100
|
+
|
101
|
+
entities.each do |id, entity|
|
102
|
+
|
103
|
+
next unless entity_events = events[id]
|
104
|
+
|
105
|
+
entity_events.each do |event|
|
81
106
|
begin
|
82
107
|
event.apply(entity)
|
83
108
|
log_debug { "Applied #{event.inspect} to #{id}" }
|
84
109
|
rescue => e
|
85
110
|
log_error "Failed to apply #{event.class.name} #{event.attributes} to #{id} with #{e.inspect}", e
|
111
|
+
raise if options.fetch(:raise_exception, true)
|
86
112
|
end
|
87
113
|
entity.version = event.entity_version
|
88
114
|
end
|
89
115
|
|
90
|
-
# assign this entity loader to allow lazy loading of related entities
|
91
|
-
entity.related_entity_loader = self
|
92
116
|
end
|
93
|
-
|
117
|
+
|
118
|
+
# ensure entities are returned in same order as requested
|
119
|
+
ids.map { |id| entities[id] }
|
120
|
+
|
94
121
|
end
|
95
122
|
|
96
|
-
# Public
|
123
|
+
# Public: USE AT YOUR PERIL this clears the ENTIRE data store
|
124
|
+
#
|
125
|
+
# confirm - Symbol that must equal :i_am_sure
|
97
126
|
#
|
98
127
|
# Returns nothing
|
99
|
-
def clear_all
|
128
|
+
def clear_all(confirm)
|
129
|
+
unless confirm == :i_am_sure
|
130
|
+
raise "#clear_all call with :i_am_sure in order to do this"
|
131
|
+
end
|
100
132
|
storage_client.clear
|
101
133
|
@_storage_client = nil
|
102
134
|
end
|
@@ -105,7 +137,7 @@ module EntityStore
|
|
105
137
|
@_event_bus ||= EventBus.new
|
106
138
|
end
|
107
139
|
|
108
|
-
# Public
|
140
|
+
# Public: returns an array representing a full audit trail for the entity.
|
109
141
|
# After each event is applied the state of the entity is rendered.
|
110
142
|
# Optionally accepts a block which should return true or false to indicate
|
111
143
|
# whether to render the line. The block yields entity, event, lines collection
|
data/lib/entity_store/version.rb
CHANGED
@@ -9,76 +9,16 @@ end
|
|
9
9
|
class DummyEntity
|
10
10
|
include Entity
|
11
11
|
|
12
|
-
related_entities :club, :user
|
13
|
-
|
14
12
|
attr_accessor :name, :description, :members
|
15
13
|
entity_value_array_attribute :things, ThingEntityValue
|
16
14
|
entity_value_dictionary_attribute :other_things, ThingEntityValue
|
17
15
|
end
|
18
16
|
|
19
17
|
describe Entity do
|
20
|
-
describe ".related_entities" do
|
21
|
-
before(:each) do
|
22
|
-
@entity_loader = double(Store)
|
23
|
-
@club = double('Entity', :id => random_string)
|
24
|
-
@user = double('Entity', :id => random_string)
|
25
|
-
@entity = DummyEntity.new(:related_entity_loader => @entity_loader, :club_id => @club.id, :user_id => @user.id)
|
26
|
-
@entity_loader.stub(:get) { |id|
|
27
|
-
case id
|
28
|
-
when @club.id
|
29
|
-
@club
|
30
|
-
when @user.id
|
31
|
-
@user
|
32
|
-
end
|
33
|
-
}
|
34
|
-
end
|
35
|
-
|
36
|
-
it "should have the club_id set" do
|
37
|
-
@entity.club_id.should eq(@club.id)
|
38
|
-
end
|
39
|
-
it "should load club" do
|
40
|
-
@entity.club.should eq(@club)
|
41
|
-
end
|
42
|
-
it "should call entity_loader with club id" do
|
43
|
-
@entity_loader.should_receive(:get).with(@club.id)
|
44
|
-
@entity.club
|
45
|
-
end
|
46
|
-
it "should have the user_id set" do
|
47
|
-
@entity.user_id.should eq(@user.id)
|
48
|
-
end
|
49
|
-
it "should load user" do
|
50
|
-
@entity.user.should eq(@user)
|
51
|
-
end
|
52
|
-
it "should call entity_loader with user id" do
|
53
|
-
@entity_loader.should_receive(:get).with(@user.id)
|
54
|
-
@entity.user
|
55
|
-
end
|
56
|
-
|
57
|
-
context "when only user loaded" do
|
58
|
-
before(:each) do
|
59
|
-
@entity.user
|
60
|
-
end
|
61
|
-
|
62
|
-
it "should only have user in the loaded related entities collection" do
|
63
|
-
@entity.loaded_related_entities.should eq([@user])
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
context "when both user and club loaded" do
|
68
|
-
before(:each) do
|
69
|
-
@entity.club
|
70
|
-
@entity.user
|
71
|
-
end
|
72
|
-
|
73
|
-
it "should only have user in the loaded related entities collection" do
|
74
|
-
@entity.loaded_related_entities.should eq([@club, @user])
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
18
|
|
79
19
|
describe "#attributes" do
|
80
20
|
before(:each) do
|
81
|
-
@entity = DummyEntity.new(:id => @id = random_object_id, :club_id => @club_id = random_string,
|
21
|
+
@entity = DummyEntity.new(:id => @id = random_object_id, :club_id => @club_id = random_string,
|
82
22
|
:user_id => @user_id = random_string, :name => @name = random_string, :version => @version = random_integer,
|
83
23
|
:members => [], things: [ ThingEntityValue.new(name: random_string), ThingEntityValue.new(name: random_string) ],
|
84
24
|
other_things_dictionary: { random_string => ThingEntityValue.new(name: random_string) } )
|
@@ -88,8 +28,7 @@ describe Entity do
|
|
88
28
|
|
89
29
|
it "returns a hash of the attributes" do
|
90
30
|
subject.should eq({
|
91
|
-
:id => @id, :version => @version, :name => @name, :
|
92
|
-
:user_id => @user_id, :description => nil, :members => [],
|
31
|
+
:id => @id, :version => @version, :name => @name, :description => nil, :members => [],
|
93
32
|
:things => @entity.things.map { |t| { name: t.name } },
|
94
33
|
:other_things_dictionary => { @entity.other_things_dictionary.keys.first => { name: @entity.other_things_dictionary.values.first.name }}
|
95
34
|
})
|
@@ -183,7 +122,7 @@ describe Entity do
|
|
183
122
|
end
|
184
123
|
it "should set items" do
|
185
124
|
entity.things.each_with_index do |item, i| item.should be(items[i]) end
|
186
|
-
end
|
125
|
+
end
|
187
126
|
end
|
188
127
|
context "when something else in array" do
|
189
128
|
let(:items) { [ random_string, random_string ] }
|
@@ -248,7 +187,7 @@ describe Entity do
|
|
248
187
|
|
249
188
|
it "should set items" do
|
250
189
|
ids.each do |id| entity.other_things_dictionary[id].name.should eq(items[id].name) end
|
251
|
-
end
|
190
|
+
end
|
252
191
|
end
|
253
192
|
context "when something else in array" do
|
254
193
|
let(:items) { { ids[0] => random_string, ids[1] => random_string } }
|
@@ -268,7 +207,7 @@ describe Entity do
|
|
268
207
|
end
|
269
208
|
|
270
209
|
describe "hash initialisation, ie from snapshot" do
|
271
|
-
let(:attributes) { { other_things_dictionary: { ids[0] => { name: random_string }, ids[1] => { name: random_string } } } }
|
210
|
+
let(:attributes) { { other_things_dictionary: { ids[0] => { name: random_string }, ids[1] => { name: random_string } } } }
|
272
211
|
|
273
212
|
subject { DummyEntity.new(attributes) }
|
274
213
|
|
@@ -56,7 +56,7 @@ describe MongoEntityStore do
|
|
56
56
|
store.add_event(third_event)
|
57
57
|
end
|
58
58
|
|
59
|
-
subject { store.get_events(event_entity_id, since_version) }
|
59
|
+
subject { store.get_events( [{ id: event_entity_id, since_version: since_version }])[event_entity_id] }
|
60
60
|
|
61
61
|
context "all events" do
|
62
62
|
let(:event_entity_id) { entity_id }
|
@@ -86,7 +86,7 @@ describe MongoEntityStore do
|
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
|
-
describe "#
|
89
|
+
describe "#get_entities" do
|
90
90
|
let(:entity_class) { DummyEntity }
|
91
91
|
|
92
92
|
let(:saved_entity) do
|
@@ -95,23 +95,26 @@ describe MongoEntityStore do
|
|
95
95
|
entity
|
96
96
|
end
|
97
97
|
|
98
|
-
|
98
|
+
let(:id) { saved_entity.id }
|
99
|
+
let(:options) { { } }
|
100
|
+
|
101
|
+
subject { store.get_entities( [ id ], options) }
|
99
102
|
|
100
103
|
it "should retrieve an entity from the store with the same ID" do
|
101
|
-
subject.id.should == saved_entity.id
|
104
|
+
subject.first.id.should == saved_entity.id
|
102
105
|
end
|
103
106
|
|
104
107
|
it "should retrieve an entity from the store with the same class" do
|
105
|
-
subject.class.should == saved_entity.class
|
108
|
+
subject.first.class.should == saved_entity.class
|
106
109
|
end
|
107
110
|
|
108
111
|
it "should have the same version" do
|
109
|
-
subject.version.should == saved_entity.version
|
112
|
+
subject.first.version.should == saved_entity.version
|
110
113
|
end
|
111
114
|
|
112
115
|
context "when a snapshot does not exist" do
|
113
116
|
it "should not have set the name" do
|
114
|
-
subject.name.should be_nil
|
117
|
+
subject.first.name.should be_nil
|
115
118
|
end
|
116
119
|
end
|
117
120
|
|
@@ -123,7 +126,7 @@ describe MongoEntityStore do
|
|
123
126
|
|
124
127
|
context "when a snapshot key not in use" do
|
125
128
|
it "should have set the name" do
|
126
|
-
subject.name.should == saved_entity.name
|
129
|
+
subject.first.name.should == saved_entity.name
|
127
130
|
end
|
128
131
|
end
|
129
132
|
|
@@ -132,7 +135,7 @@ describe MongoEntityStore do
|
|
132
135
|
|
133
136
|
context "when the key matches the class's key" do
|
134
137
|
it "should have set the name" do
|
135
|
-
subject.name.should == saved_entity.name
|
138
|
+
subject.first.name.should == saved_entity.name
|
136
139
|
end
|
137
140
|
end
|
138
141
|
|
@@ -142,29 +145,26 @@ describe MongoEntityStore do
|
|
142
145
|
end
|
143
146
|
|
144
147
|
it "should ignore the invalidated snapshot" do
|
145
|
-
subject.name.should be_nil
|
148
|
+
subject.first.name.should be_nil
|
146
149
|
end
|
147
150
|
end
|
148
151
|
end
|
149
152
|
end
|
150
|
-
end
|
151
|
-
|
152
|
-
describe "#get_entity!" do
|
153
|
-
context "when invalid id format passed" do
|
154
|
-
subject { store.get_entity!(random_string) }
|
155
153
|
|
156
|
-
|
157
|
-
|
154
|
+
describe "context when enable exceptions" do
|
155
|
+
let(:options) do
|
156
|
+
{ raise_exception: true }
|
158
157
|
end
|
159
|
-
end
|
160
158
|
|
161
|
-
|
162
|
-
|
159
|
+
context "when invalid id format passed" do
|
160
|
+
let(:id) { random_string }
|
163
161
|
|
164
|
-
|
165
|
-
|
162
|
+
it "should raise not found" do
|
163
|
+
expect { subject }.to raise_error(NotFound)
|
164
|
+
end
|
166
165
|
end
|
167
166
|
end
|
167
|
+
|
168
168
|
end
|
169
169
|
|
170
170
|
describe "#snapshot_entity" do
|
@@ -77,30 +77,7 @@ describe Store do
|
|
77
77
|
end
|
78
78
|
|
79
79
|
describe "#save" do
|
80
|
-
context "when entity has related entities loaded" do
|
81
|
-
before(:each) do
|
82
|
-
@entity = DummyEntityForStore.new(:id => random_string)
|
83
|
-
@entity.version = random_integer * EntityStore::Config.snapshot_threshold + 1
|
84
|
-
@store = Store.new
|
85
|
-
@related_entity = double('Entity')
|
86
|
-
@entity.stub(:loaded_related_entities) { [ @related_entity ] }
|
87
|
-
@store.stub(:do_save)
|
88
|
-
end
|
89
|
-
|
90
|
-
subject { @store.save(@entity) }
|
91
|
-
|
92
|
-
it "should save the entity" do
|
93
|
-
@store.should_receive(:do_save).with(@entity)
|
94
|
-
subject
|
95
|
-
end
|
96
|
-
it "should save them as well" do
|
97
|
-
@store.should_receive(:do_save).with(@related_entity)
|
98
|
-
subject
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
80
|
|
103
|
-
describe "#do_save" do
|
104
81
|
before(:each) do
|
105
82
|
@new_id = random_string
|
106
83
|
@entity = DummyEntityForStore.new(:id => random_string)
|
@@ -112,7 +89,7 @@ describe Store do
|
|
112
89
|
@entity.stub(:pending_events) { [ double('Event') ] }
|
113
90
|
end
|
114
91
|
|
115
|
-
subject { @store.
|
92
|
+
subject { @store.save(@entity) }
|
116
93
|
|
117
94
|
it "increments the entity version number" do
|
118
95
|
expect { subject }.to change { @entity.version }.by 1
|
@@ -176,45 +153,84 @@ describe Store do
|
|
176
153
|
|
177
154
|
end
|
178
155
|
|
179
|
-
describe "
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
156
|
+
describe "getters" do
|
157
|
+
let(:ids) { [ random_string, random_string, random_string ] }
|
158
|
+
let(:entities) { ids.map { |id| DummyEntityForStore.new(id: id, version: random_integer) } }
|
159
|
+
let(:events) do
|
160
|
+
Hash[ ids.map do |id|
|
161
|
+
[
|
162
|
+
id,
|
163
|
+
[
|
164
|
+
double("Event", apply: true, entity_version: entities.find { |e| e.id == id } .version + 1),
|
165
|
+
double("Event", apply: true, entity_version: entities.find { |e| e.id == id } .version + 2)
|
166
|
+
]
|
167
|
+
]
|
168
|
+
end ]
|
192
169
|
end
|
193
170
|
|
194
|
-
|
171
|
+
let(:storage_client) { double("StorageClient") }
|
172
|
+
let(:store) { Store.new }
|
195
173
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
subject.should eq(@entity)
|
206
|
-
end
|
207
|
-
it "should retrieve it's events" do
|
208
|
-
@storage_client.should_receive(:get_events).with(@id, @entity.version)
|
209
|
-
subject
|
174
|
+
before(:each) do
|
175
|
+
storage_client.stub(:get_entities) do |ids|
|
176
|
+
entities.select { |e| ids.include?(e.id) }
|
177
|
+
end
|
178
|
+
|
179
|
+
storage_client.stub(:get_events) do |criteria|
|
180
|
+
Hash[ criteria.map { |c| [ c[:id], events[c[:id]] ] }]
|
181
|
+
end
|
182
|
+
store.stub(:storage_client) { storage_client }
|
210
183
|
end
|
211
|
-
|
212
|
-
|
213
|
-
|
184
|
+
describe "#get" do
|
185
|
+
let(:entity) { entities[1] }
|
186
|
+
let(:id) { entity.id }
|
187
|
+
|
188
|
+
subject { store.get(id) }
|
189
|
+
|
190
|
+
it "should retrieve object from the storage client" do
|
191
|
+
storage_client.should_receive(:get_entities).with([id], { raise_exception: false })
|
192
|
+
subject
|
193
|
+
end
|
194
|
+
it "should return the entity" do
|
195
|
+
subject.id.should eq(entity.id)
|
196
|
+
end
|
197
|
+
it "should apply each event to the entity" do
|
198
|
+
events[id].each do |event|
|
199
|
+
event.should_receive(:apply).with(entity)
|
200
|
+
end
|
201
|
+
subject
|
202
|
+
end
|
203
|
+
it "should set the entity version to that of the last event" do
|
204
|
+
subject
|
205
|
+
entity.version.should eq(events[id].last.entity_version)
|
206
|
+
end
|
214
207
|
end
|
215
|
-
|
216
|
-
|
217
|
-
|
208
|
+
|
209
|
+
describe "#get_with_ids" do
|
210
|
+
|
211
|
+
subject { store.get_with_ids(ids) }
|
212
|
+
|
213
|
+
it "should retrieve object from the storage client" do
|
214
|
+
storage_client.should_receive(:get_entities).with(ids, {})
|
215
|
+
subject
|
216
|
+
end
|
217
|
+
it "should return the entities" do
|
218
|
+
subject.map { |e| e.id }.should eq(ids)
|
219
|
+
end
|
220
|
+
it "should apply each event to the entities" do
|
221
|
+
entities.each do |entity|
|
222
|
+
events[entity.id].each do |event|
|
223
|
+
event.should_receive(:apply).with(entity)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
subject
|
227
|
+
end
|
228
|
+
it "should set the entity version to that of the last event" do
|
229
|
+
subject
|
230
|
+
entities.each do |entity|
|
231
|
+
entity.version.should eq(events[entity.id].last.entity_version)
|
232
|
+
end
|
233
|
+
end
|
218
234
|
end
|
219
235
|
end
|
220
236
|
end
|
metadata
CHANGED
@@ -1,55 +1,55 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: entity_store
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Bird
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-05-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mongo
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - ~>
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.8'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - ~>
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.8'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bson_ext
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - ~>
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '1.8'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - ~>
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1.8'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: hatchet
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - ~>
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '0.2'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - ~>
|
52
|
+
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0.2'
|
55
55
|
description: Event sourced entity store with a replaceable body
|
@@ -96,12 +96,12 @@ require_paths:
|
|
96
96
|
- lib
|
97
97
|
required_ruby_version: !ruby/object:Gem::Requirement
|
98
98
|
requirements:
|
99
|
-
- -
|
99
|
+
- - ">="
|
100
100
|
- !ruby/object:Gem::Version
|
101
101
|
version: '0'
|
102
102
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
103
|
requirements:
|
104
|
-
- -
|
104
|
+
- - ">="
|
105
105
|
- !ruby/object:Gem::Version
|
106
106
|
version: '0'
|
107
107
|
requirements: []
|