store 0.0.3 → 0.0.4
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/example.rb +115 -40
- data/lib/store/data_mapper.rb +27 -0
- data/lib/store/entity_mapper.rb +20 -0
- data/lib/store/query.rb +11 -0
- data/lib/store/ref.rb +17 -0
- data/lib/store/repo.rb +28 -0
- data/lib/store/version.rb +1 -1
- data/lib/store.rb +98 -26
- data/spec/store_query_spec.rb +11 -0
- data/spec/store_ref_spec.rb +16 -0
- data/spec/store_repo_spec.rb +33 -0
- data/spec/store_spec.rb +173 -64
- metadata +12 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3cf1280f06ddee3f9d3c5190390bbe8cf29ffd6b
|
4
|
+
data.tar.gz: da7e50bad1b9702421edb0820c1bcb91bc02a481
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8fa9a6d146e4c87c796032a3b3b7ff7f2db857c60a4d5ad2fd786f5ac405c26f8c61f4741805223bffeb63830a534da7b4ea385b9f6145a3eff46a8df303ed99
|
7
|
+
data.tar.gz: a0850ae08752a3eb8ea76f80640c71b82fa57609ce323844d0250a8e172a615cd482f844bd08f52bf50fed07f0e58aaa25195d99a35c750891de6c48704c1e02
|
data/example.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'store'
|
2
2
|
|
3
|
+
# Models #######################################################################
|
3
4
|
class User
|
4
5
|
attr_accessor :name
|
5
6
|
end
|
@@ -20,72 +21,131 @@ class Product
|
|
20
21
|
attr_accessor :name, :price
|
21
22
|
end
|
22
23
|
|
23
|
-
|
24
|
-
|
24
|
+
# EntityMappers ################################################################
|
25
|
+
|
26
|
+
class UserEntityMapper < Store::EntityMapper
|
27
|
+
def map(user)
|
25
28
|
{
|
26
29
|
'name' => user.name
|
27
30
|
}
|
28
31
|
end
|
32
|
+
|
33
|
+
def unmap(cls, mapped)
|
34
|
+
user = cls.new
|
35
|
+
user.name = mapped['name']
|
36
|
+
user
|
37
|
+
end
|
29
38
|
end
|
30
39
|
|
31
|
-
class CatalogEntityMapper <
|
32
|
-
def
|
40
|
+
class CatalogEntityMapper < Store::EntityMapper
|
41
|
+
def map(catalog)
|
33
42
|
{
|
34
43
|
'editor' => store.save(catalog.editor),
|
35
44
|
'name' => catalog.name,
|
36
|
-
'products' => store.save(
|
45
|
+
'products' => store.save(catalog.products)
|
37
46
|
}
|
38
47
|
end
|
48
|
+
|
49
|
+
def unmap(cls, mapped)
|
50
|
+
cls.new.tap do |entity|
|
51
|
+
entity.editor = store.load(mapped['editor'])
|
52
|
+
entity.name = mapped['name']
|
53
|
+
entity.products = store.load(mapped['products'])
|
54
|
+
end
|
55
|
+
end
|
39
56
|
end
|
40
57
|
|
41
|
-
class ProductEntityMapper <
|
42
|
-
def
|
58
|
+
class ProductEntityMapper < Store::EntityMapper
|
59
|
+
def map(product)
|
43
60
|
{
|
44
61
|
'name' => product.name,
|
45
62
|
'price' => product.price
|
46
63
|
}
|
47
64
|
end
|
65
|
+
|
66
|
+
def unmap(cls, mapped)
|
67
|
+
user = cls.new
|
68
|
+
user.name = mapped['name']
|
69
|
+
user.price = mapped['price']
|
70
|
+
user
|
71
|
+
end
|
48
72
|
end
|
49
73
|
|
50
|
-
|
74
|
+
# DataMappers###################################################################
|
75
|
+
|
76
|
+
class MemoryDataMapper < Store::DataMapper
|
77
|
+
def initialize
|
78
|
+
@items = {}
|
79
|
+
@id = 0
|
80
|
+
end
|
81
|
+
|
51
82
|
def insert(data)
|
52
|
-
|
53
|
-
|
54
|
-
|
83
|
+
id = next_id
|
84
|
+
@items[id] = dup(data)
|
85
|
+
id
|
55
86
|
end
|
56
87
|
|
57
|
-
def update(
|
58
|
-
|
59
|
-
|
88
|
+
def update(id, data)
|
89
|
+
@items[id] = dup(data)
|
90
|
+
id
|
60
91
|
end
|
61
|
-
end
|
62
92
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
93
|
+
def delete(id)
|
94
|
+
@items.delete(id)
|
95
|
+
id
|
96
|
+
end
|
97
|
+
|
98
|
+
def single_find(reference)
|
99
|
+
dup(@items[reference])
|
68
100
|
end
|
69
101
|
|
70
|
-
def
|
71
|
-
|
72
|
-
|
102
|
+
def bulk_find(references)
|
103
|
+
@items.values_at(*references).map do |data|
|
104
|
+
dup data
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def all
|
111
|
+
@items
|
112
|
+
end
|
113
|
+
|
114
|
+
def next_id
|
115
|
+
@id += 1
|
116
|
+
end
|
117
|
+
|
118
|
+
def dup(data)
|
119
|
+
data and Marshal.load(Marshal.dump(data))
|
73
120
|
end
|
74
121
|
end
|
75
122
|
|
76
|
-
class
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
123
|
+
class UserDataMapper < MemoryDataMapper
|
124
|
+
end
|
125
|
+
|
126
|
+
class CatalogDataMapper < MemoryDataMapper
|
127
|
+
|
128
|
+
def select(query)
|
129
|
+
if query.name == :find_by_name
|
130
|
+
select_by_name(query.args.first)
|
131
|
+
else
|
132
|
+
raise "Query not known"
|
133
|
+
end
|
81
134
|
end
|
82
135
|
|
83
|
-
|
84
|
-
|
85
|
-
|
136
|
+
private
|
137
|
+
|
138
|
+
def select_by_name(name)
|
139
|
+
all.select do |id, data|
|
140
|
+
data['name'] == name
|
141
|
+
end
|
86
142
|
end
|
87
143
|
end
|
88
144
|
|
145
|
+
class ProductDataMapper < MemoryDataMapper
|
146
|
+
end
|
147
|
+
|
148
|
+
# Store instantiation ##########################################################
|
89
149
|
|
90
150
|
entities = {
|
91
151
|
'User' => User,
|
@@ -105,25 +165,40 @@ entity_mappers = {
|
|
105
165
|
'Product' => ProductEntityMapper
|
106
166
|
}
|
107
167
|
|
108
|
-
|
168
|
+
class Repo < Store::Repo
|
169
|
+
def find_catalogs_by_name(name)
|
170
|
+
query Store::Query.new('Catalog', :find_by_name, name)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
repo = Repo.new(entities, data_mappers, entity_mappers)
|
109
175
|
|
110
|
-
|
176
|
+
# Usage ########################################################################
|
111
177
|
|
112
|
-
|
178
|
+
editor = repo.build 'User', :name => 'Mr. Repo'
|
113
179
|
|
114
|
-
catalog
|
115
|
-
|
180
|
+
catalog = repo.build 'Catalog', :name => 'Catalog 1', :editor => editor
|
181
|
+
|
182
|
+
catalog.add_product repo.build('Product', :name => 'Pickaxe', :price => 100)
|
183
|
+
catalog.add_product repo.build('Product', :name => 'Scredriver', :price => 400)
|
116
184
|
|
117
185
|
p 'save call'
|
118
|
-
|
186
|
+
repo.save(catalog)
|
119
187
|
|
120
188
|
catalog.products[0].price = 24
|
121
189
|
|
122
190
|
p 'save call'
|
123
|
-
|
191
|
+
repo.save(catalog)
|
192
|
+
|
193
|
+
product = catalog.products.delete_at(1)
|
194
|
+
repo.remove product
|
124
195
|
|
125
|
-
catalog
|
196
|
+
p catalog
|
126
197
|
|
127
198
|
p 'save call'
|
128
199
|
# no implicit delete here. is this a problem?
|
129
|
-
|
200
|
+
repo.save(catalog)
|
201
|
+
|
202
|
+
p repo.find_catalogs_by_name('test')
|
203
|
+
|
204
|
+
p repo.find_catalogs_by_name('Catalog 1')
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Store
|
2
|
+
class DataMapper
|
3
|
+
def insert(data)
|
4
|
+
raise NotImplementedError
|
5
|
+
end
|
6
|
+
|
7
|
+
def update(reference, data)
|
8
|
+
raise NotImplementedError
|
9
|
+
end
|
10
|
+
|
11
|
+
def delete(reference)
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
14
|
+
|
15
|
+
def select(query)
|
16
|
+
raise NotImplementedError
|
17
|
+
end
|
18
|
+
|
19
|
+
def single_find(reference)
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
|
23
|
+
def bulk_find(reference)
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Store
|
2
|
+
class EntityMapper
|
3
|
+
def initialize(store)
|
4
|
+
@store = store
|
5
|
+
end
|
6
|
+
|
7
|
+
def map(entity)
|
8
|
+
raise NotImplementedError
|
9
|
+
end
|
10
|
+
|
11
|
+
def unmap(entity_class, mapped_entity)
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def store
|
17
|
+
@store
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/store/query.rb
ADDED
data/lib/store/ref.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
class Store
|
2
|
+
class Ref
|
3
|
+
attr_reader :entity_type, :reference
|
4
|
+
|
5
|
+
def initialize(entity_type, reference)
|
6
|
+
@entity_type = entity_type
|
7
|
+
@reference = reference
|
8
|
+
end
|
9
|
+
|
10
|
+
def ==(other)
|
11
|
+
other.respond_to?(:entity_type) &&
|
12
|
+
other.respond_to?(:reference) &&
|
13
|
+
self.entity_type == other.entity_type &&
|
14
|
+
self.reference == other.reference
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/store/repo.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'store/query'
|
2
|
+
|
3
|
+
class Store
|
4
|
+
class Repo
|
5
|
+
def initialize(entity_classes, data_mapping, entity_mapping)
|
6
|
+
@store = Store.new(entity_classes, data_mapping, entity_mapping)
|
7
|
+
end
|
8
|
+
|
9
|
+
def build(entity, *args)
|
10
|
+
@store.build(entity, *args)
|
11
|
+
end
|
12
|
+
|
13
|
+
def save(entites)
|
14
|
+
@store.save(entites)
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def remove(entity)
|
19
|
+
@store.remove(entity)
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def query(query)
|
25
|
+
@store.query(query)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/store/version.rb
CHANGED
data/lib/store.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
|
-
require '
|
1
|
+
require 'store/repo'
|
2
|
+
require 'store/ref'
|
3
|
+
require 'store/entity_mapper'
|
4
|
+
require 'store/data_mapper'
|
2
5
|
|
3
6
|
class Store
|
4
|
-
def initialize(entity_classes, data_mapping, entity_mapping
|
7
|
+
def initialize(entity_classes, data_mapping, entity_mapping)
|
5
8
|
@entity_classes = entity_classes
|
6
9
|
@data_mapping = data_mapping
|
7
10
|
@entity_mapping = entity_mapping
|
11
|
+
|
8
12
|
@entity_references = Hash.new
|
9
13
|
end
|
10
14
|
|
@@ -18,31 +22,20 @@ class Store
|
|
18
22
|
entity
|
19
23
|
end
|
20
24
|
|
21
|
-
def save(
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
data_mapper = data_mapper_for_entity(entity)
|
26
|
-
raise_unless_data_mapper_found(entity, data_mapper)
|
27
|
-
|
28
|
-
reference = find_entity_reference(entity)
|
29
|
-
if reference.nil?
|
30
|
-
reference = data_mapper.insert(map_entity entity)
|
31
|
-
save_entity_reference(reference, entity)
|
32
|
-
else
|
33
|
-
data_mapper.update(reference, map_entity(entity))
|
25
|
+
def save(entities)
|
26
|
+
if entities.kind_of? Array
|
27
|
+
entities.map do |entity|
|
28
|
+
save_entity(entity)
|
34
29
|
end
|
35
|
-
end
|
36
|
-
|
37
|
-
if references.size == 1
|
38
|
-
references[0]
|
39
30
|
else
|
40
|
-
|
31
|
+
entity = entities
|
32
|
+
save_entity(entity)
|
41
33
|
end
|
42
34
|
end
|
43
35
|
|
44
36
|
def remove(entity)
|
45
37
|
data_mapper = data_mapper_for_entity(entity)
|
38
|
+
raise_unless_data_mapper_found(entity, data_mapper)
|
46
39
|
|
47
40
|
reference = find_entity_reference(entity)
|
48
41
|
data_mapper.delete(reference)
|
@@ -52,23 +45,87 @@ class Store
|
|
52
45
|
|
53
46
|
def query(query)
|
54
47
|
data_mapper = data_mapper_for_query(query)
|
48
|
+
entity_mapper = entity_mapper_for_query(query)
|
49
|
+
entity_class = @entity_classes[query.entity]
|
50
|
+
|
51
|
+
raise_unless_data_mapper_found(query, data_mapper)
|
55
52
|
|
56
|
-
|
53
|
+
mapped_entities_with_references = data_mapper.select(query) || {}
|
57
54
|
|
58
|
-
|
55
|
+
mapped_entities_with_references.map do |reference, mapped_entity|
|
56
|
+
entity = entity_mapper.new(self).unmap(entity_class, mapped_entity)
|
59
57
|
save_entity_reference reference, entity
|
60
58
|
entity
|
61
59
|
end
|
62
60
|
end
|
63
61
|
|
62
|
+
def load(refs)
|
63
|
+
if refs.kind_of? Array
|
64
|
+
bulk_load refs
|
65
|
+
else
|
66
|
+
single_load refs
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def single_load(ref)
|
71
|
+
data_mapper = data_mapper_for_ref(ref)
|
72
|
+
entity_mapper = entity_mapper_for_ref(ref)
|
73
|
+
entity_class = @entity_classes[ref.entity_type]
|
74
|
+
reference = ref.reference
|
75
|
+
|
76
|
+
mapped_entity = data_mapper.single_find(reference)
|
77
|
+
|
78
|
+
entity = entity_mapper.new(self).unmap(entity_class, mapped_entity)
|
79
|
+
save_entity_reference reference, entity
|
80
|
+
entity
|
81
|
+
end
|
82
|
+
|
83
|
+
def bulk_load(refs)
|
84
|
+
data_mapper = data_mapper_for_ref(refs.first)
|
85
|
+
entity_mapper = entity_mapper_for_ref(refs.first)
|
86
|
+
entity_class = @entity_classes[refs.first.entity_type]
|
87
|
+
|
88
|
+
references = refs.map(&:reference)
|
89
|
+
mapped_entities = data_mapper.bulk_find(references)
|
90
|
+
|
91
|
+
entities = []
|
92
|
+
|
93
|
+
references.each_with_index do |reference, i|
|
94
|
+
mapped_entity = mapped_entities[i]
|
95
|
+
|
96
|
+
entity = entity_mapper.new(self).unmap(entity_class, mapped_entity)
|
97
|
+
save_entity_reference reference, entity
|
98
|
+
entities << entity
|
99
|
+
end
|
100
|
+
|
101
|
+
entities
|
102
|
+
end
|
103
|
+
|
64
104
|
private
|
65
|
-
def
|
66
|
-
|
105
|
+
def save_entity(entity)
|
106
|
+
return nil unless entity
|
107
|
+
|
108
|
+
data_mapper = data_mapper_for_entity(entity)
|
109
|
+
raise_unless_data_mapper_found(entity, data_mapper)
|
110
|
+
|
111
|
+
reference = find_entity_reference(entity)
|
112
|
+
if reference.nil?
|
113
|
+
reference = data_mapper.insert(map_entity entity)
|
114
|
+
save_entity_reference(reference, entity)
|
115
|
+
else
|
116
|
+
data_mapper.update(reference, map_entity(entity))
|
117
|
+
end
|
118
|
+
|
119
|
+
Ref.new(entity_type(entity), reference)
|
120
|
+
end
|
121
|
+
|
122
|
+
def raise_unless_data_mapper_found(source, data_mapper)
|
123
|
+
raise "No data_mapper for #{source.inspect} found" unless data_mapper
|
67
124
|
end
|
68
125
|
|
69
126
|
def map_entity(entity)
|
70
127
|
entity_mapper = entity_mapper_for_entity(entity)
|
71
|
-
entity_mapper.new(self
|
128
|
+
entity_mapper.new(self).map(entity)
|
72
129
|
end
|
73
130
|
|
74
131
|
def entity_mapper_for_entity(entity)
|
@@ -76,6 +133,16 @@ class Store
|
|
76
133
|
entity_mapper_for_type(type)
|
77
134
|
end
|
78
135
|
|
136
|
+
def entity_mapper_for_query(query)
|
137
|
+
type = query.entity
|
138
|
+
entity_mapper_for_type(type)
|
139
|
+
end
|
140
|
+
|
141
|
+
def entity_mapper_for_ref(ref)
|
142
|
+
type = ref.entity_type
|
143
|
+
entity_mapper_for_type(type)
|
144
|
+
end
|
145
|
+
|
79
146
|
def entity_mapper_for_type(type)
|
80
147
|
@entity_mapping[type]
|
81
148
|
end
|
@@ -91,7 +158,12 @@ class Store
|
|
91
158
|
end
|
92
159
|
|
93
160
|
def data_mapper_for_query(query)
|
94
|
-
type = query.
|
161
|
+
type = query.entity
|
162
|
+
data_mapper_for_type type
|
163
|
+
end
|
164
|
+
|
165
|
+
def data_mapper_for_ref(ref)
|
166
|
+
type = ref.entity_type
|
95
167
|
data_mapper_for_type type
|
96
168
|
end
|
97
169
|
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe 'Store::Query' do
|
4
|
+
it 'consists of three arguments' do
|
5
|
+
query = Store::Query.new('Entity', :query_name, :some, :args)
|
6
|
+
|
7
|
+
assert_equal 'Entity', query.entity
|
8
|
+
assert_equal :query_name, query.name
|
9
|
+
assert_equal [:some, :args], query.args
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe 'Store::Ref' do
|
4
|
+
it 'accepts entity_type and reference' do
|
5
|
+
Store::Ref.new('User', 23)
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'is equal if all attributes are equal' do
|
9
|
+
store = Store::Ref.new('User', 23)
|
10
|
+
|
11
|
+
assert_equal Store::Ref.new('User', 23), store
|
12
|
+
refute_equal Store::Ref.new('Player', 23), store
|
13
|
+
refute_equal Store::Ref.new('User', 24), store
|
14
|
+
refute_equal nil, store
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe 'Store::Repo' do
|
4
|
+
let(:repo) do
|
5
|
+
repo = Store::Repo.new(:a, :b, :c)
|
6
|
+
repo.instance_variable_set :@store, store
|
7
|
+
repo
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:store) { MiniTest::Mock.new }
|
11
|
+
|
12
|
+
it 'accepts three args' do
|
13
|
+
Store::Repo.new(:a, :b, :c)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'delegeates save to @store and swallows result' do
|
17
|
+
store.expect :save, :result, [:arg]
|
18
|
+
assert_equal true, repo.save(:arg)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'delegates remove to @store and swallows result' do
|
22
|
+
store.expect :remove, :result, [:arg]
|
23
|
+
assert_equal true, repo.remove(:arg)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'delegates build to @store and returns result' do
|
27
|
+
store.expect :build, :result, [:arg]
|
28
|
+
assert_equal :result, repo.build(:arg)
|
29
|
+
|
30
|
+
store.expect :build, :result, [:arg, :a, :b]
|
31
|
+
assert_equal :result, repo.build(:arg, :a, :b)
|
32
|
+
end
|
33
|
+
end
|
data/spec/store_spec.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
3
|
describe 'store' do
|
4
|
-
let(:store) { Store.new({}, data_mapping, entity_mapping) }
|
4
|
+
let(:store) { Store.new({entity_class.name => entity_class}, data_mapping, entity_mapping) }
|
5
5
|
let(:data_mapping) {
|
6
6
|
{
|
7
7
|
entity_class.name => data_mapper
|
@@ -13,16 +13,19 @@ describe 'store' do
|
|
13
13
|
}
|
14
14
|
}
|
15
15
|
let(:data_mapper) { MiniTest::Mock.new }
|
16
|
-
let(:entity_mapper) { Struct.new(:store
|
17
|
-
def
|
16
|
+
let(:entity_mapper) { Struct.new(:store) do
|
17
|
+
def map(entity)
|
18
18
|
{
|
19
19
|
'email' => entity.email,
|
20
20
|
'gender' => entity.gender
|
21
21
|
}
|
22
22
|
end
|
23
|
+
def unmap(cls, mapped)
|
24
|
+
cls.new(mapped['email'], mapped['gender'])
|
25
|
+
end
|
23
26
|
end }
|
24
27
|
|
25
|
-
let(:entity_class) { Struct.new(:email, :gender) }
|
28
|
+
let(:entity_class) { Struct.new(:email, :gender) {def self.name; 'name'; end} }
|
26
29
|
let(:entity) { entity_class.new("me@foo.bar", "w") }
|
27
30
|
let(:entity_hash) { {'email' => entity.email, 'gender' => entity.gender} }
|
28
31
|
|
@@ -38,11 +41,11 @@ describe 'store' do
|
|
38
41
|
it 'delegates second save to update' do
|
39
42
|
data_mapper.expect(:insert, :ref1, [entity_hash])
|
40
43
|
|
41
|
-
assert_equal :ref1, store.save(entity)
|
44
|
+
assert_equal Store::Ref.new(entity_class.name, :ref1), store.save(entity)
|
42
45
|
|
43
46
|
data_mapper.expect(:update, :ref1, [:ref1, entity_hash])
|
44
47
|
|
45
|
-
assert_equal :ref1, store.save(entity)
|
48
|
+
assert_equal Store::Ref.new(entity_class.name, :ref1), store.save(entity)
|
46
49
|
|
47
50
|
assert data_mapper.verify
|
48
51
|
end
|
@@ -72,50 +75,21 @@ describe 'store' do
|
|
72
75
|
data_mapper.expect(:update, :ref1, [:ref1, e1_hash])
|
73
76
|
data_mapper.expect(:insert, :ref2, [e2_hash])
|
74
77
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
it 'delegates query to select' do
|
81
|
-
data_mapper.expect(:select, nil, [query])
|
82
|
-
|
83
|
-
store.query(query)
|
78
|
+
refs = [
|
79
|
+
Store::Ref.new(entity_class.name, :ref1),
|
80
|
+
Store::Ref.new(entity_class.name, :ref2)
|
81
|
+
]
|
82
|
+
assert_equal refs, store.save([e1, e2])
|
84
83
|
|
85
84
|
assert data_mapper.verify
|
86
85
|
end
|
87
86
|
|
88
|
-
it 'returns
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
data_mapper.expect(:select, nil, [query])
|
95
|
-
assert_equal [], store.query(query)
|
96
|
-
end
|
97
|
-
|
98
|
-
it 'only calls update for queried results' do
|
99
|
-
e1 = entity_class.new('e1', 'e1')
|
100
|
-
e1_hash = {'email' => 'e1', 'gender' => 'e1'}
|
101
|
-
e2 = entity_class.new('e2', 'e2')
|
102
|
-
e2_hash = {'email' => 'e2', 'gender' => 'e2'}
|
103
|
-
e3 = entity_class.new('e3', 'e3')
|
104
|
-
e3_hash = {'email' => 'e3', 'gender' => 'e3'}
|
105
|
-
|
106
|
-
data_mapper.expect(:select, {:ref1 => e1, :ref2 => e2}, [query])
|
107
|
-
store.query(query)
|
108
|
-
|
109
|
-
data_mapper.expect(:update, nil, [:ref1, e1_hash])
|
110
|
-
store.save(e1)
|
111
|
-
|
112
|
-
data_mapper.expect(:update, nil, [:ref2, e2_hash])
|
113
|
-
store.save(e2)
|
114
|
-
|
115
|
-
data_mapper.expect(:insert, :ref1, [e3_hash])
|
116
|
-
store.save(e3)
|
117
|
-
|
118
|
-
assert data_mapper.verify
|
87
|
+
it 'returns even one id as array on save with array' do
|
88
|
+
refs = [
|
89
|
+
Store::Ref.new(entity_class.name, :ref1)
|
90
|
+
]
|
91
|
+
data_mapper.expect(:insert, :ref1, [entity_hash])
|
92
|
+
assert_equal refs, store.save([entity])
|
119
93
|
end
|
120
94
|
|
121
95
|
it 'identifies entity as existent if changed' do
|
@@ -149,6 +123,55 @@ describe 'store' do
|
|
149
123
|
store.save(entity)
|
150
124
|
end
|
151
125
|
|
126
|
+
it 'fails remove if data_mapper not found' do
|
127
|
+
entity = OpenStruct.new(:test => 42)
|
128
|
+
error = assert_raises RuntimeError do
|
129
|
+
store.remove(entity)
|
130
|
+
end
|
131
|
+
|
132
|
+
assert_match(/no data_mapper .* found/i, error.message)
|
133
|
+
end
|
134
|
+
|
135
|
+
describe 'load' do
|
136
|
+
it 'accepts a ref object and delegates to single_find' do
|
137
|
+
data_mapper.expect(:insert, :ref22, [entity_hash])
|
138
|
+
ref = store.save(entity)
|
139
|
+
|
140
|
+
data_mapper.expect(:single_find, entity_hash, [:ref22])
|
141
|
+
assert_equal entity, store.load(ref)
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'accepts an array of ref objects and delegates to bulk_find' do
|
145
|
+
data_mapper.expect(:insert, :ref22, [entity_hash])
|
146
|
+
ref = store.save(entity)
|
147
|
+
|
148
|
+
data_mapper.expect(:bulk_find, [entity_hash], [[:ref22]])
|
149
|
+
assert_equal [entity], store.load([ref])
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'adds single_loaded entites as known entities' do
|
153
|
+
data_mapper.expect(:insert, :ref22, [entity_hash])
|
154
|
+
ref = store.save(entity)
|
155
|
+
|
156
|
+
data_mapper.expect(:single_find, entity_hash, [:ref22])
|
157
|
+
result = store.load(ref)
|
158
|
+
|
159
|
+
data_mapper.expect(:update, :ref22, [:ref22, entity_hash])
|
160
|
+
store.save(result)
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'adds bulk_loaded entites as known entities' do
|
164
|
+
data_mapper.expect(:insert, :ref22, [entity_hash])
|
165
|
+
ref = store.save(entity)
|
166
|
+
|
167
|
+
data_mapper.expect(:bulk_find, [entity_hash], [[:ref22]])
|
168
|
+
result = store.load([ref])
|
169
|
+
|
170
|
+
data_mapper.expect(:update, :ref22, [:ref22, entity_hash])
|
171
|
+
store.save(result[0])
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
152
175
|
describe 'building' do
|
153
176
|
Project = Struct.new(:name, :due_date)
|
154
177
|
|
@@ -170,6 +193,93 @@ describe 'store' do
|
|
170
193
|
end
|
171
194
|
end
|
172
195
|
|
196
|
+
describe 'query' do
|
197
|
+
Player = Struct.new(:email, :gender)
|
198
|
+
|
199
|
+
let(:store) { Store.new({'Player'=>Player}, data_mapping, entity_mapping) }
|
200
|
+
let(:data_mapping) do
|
201
|
+
{
|
202
|
+
'Player' => player_data_mapper
|
203
|
+
}
|
204
|
+
end
|
205
|
+
let(:entity_mapping) do
|
206
|
+
{
|
207
|
+
'Player' => player_entity_mapper
|
208
|
+
}
|
209
|
+
end
|
210
|
+
let(:player_data_mapper) { MiniTest::Mock.new }
|
211
|
+
let(:player_entity_mapper) { Struct.new(:store) do
|
212
|
+
def map(entity)
|
213
|
+
{
|
214
|
+
'email' => entity.email,
|
215
|
+
'gender' => entity.gender
|
216
|
+
}
|
217
|
+
end
|
218
|
+
|
219
|
+
def unmap(cls, mapped)
|
220
|
+
cls.new.tap do |player|
|
221
|
+
player.email = mapped['email']
|
222
|
+
player.gender = mapped['gender']
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
}
|
227
|
+
let(:query) { Store::Query.new('Player', :test_query) }
|
228
|
+
|
229
|
+
it 'delegates query to select' do
|
230
|
+
player_data_mapper.expect(:select, nil, [query])
|
231
|
+
|
232
|
+
store.query(query)
|
233
|
+
|
234
|
+
assert player_data_mapper.verify
|
235
|
+
end
|
236
|
+
|
237
|
+
it 'returns query result' do
|
238
|
+
e1 = Player.new('e1', 'e2')
|
239
|
+
e1_hash = {'email' => 'e1', 'gender' => 'e2'}
|
240
|
+
e2 = Player.new('e3', 'e4')
|
241
|
+
e2_hash = {'email' => 'e3', 'gender' => 'e4'}
|
242
|
+
|
243
|
+
player_data_mapper.expect(:select, {:ref1 => e1_hash, :ref2 => e2_hash}, [query])
|
244
|
+
assert_equal [e1, e2], store.query(query)
|
245
|
+
end
|
246
|
+
|
247
|
+
it 'maps nil query result to empty collection' do
|
248
|
+
player_data_mapper.expect(:select, nil, [query])
|
249
|
+
assert_equal [], store.query(query)
|
250
|
+
end
|
251
|
+
|
252
|
+
it 'only calls update for queried results' do
|
253
|
+
e1_hash = {'email' => 'e1', 'gender' => 'e1'}
|
254
|
+
e2_hash = {'email' => 'e2', 'gender' => 'e2'}
|
255
|
+
e3 = Player.new('e3', 'e3')
|
256
|
+
e3_hash = {'email' => 'e3', 'gender' => 'e3'}
|
257
|
+
|
258
|
+
player_data_mapper.expect(:select, {:ref1 => e1_hash, :ref2 => e2_hash}, [query])
|
259
|
+
e1, e2 = store.query(query)
|
260
|
+
|
261
|
+
player_data_mapper.expect(:update, nil, [:ref1, e1_hash])
|
262
|
+
store.save(e1)
|
263
|
+
|
264
|
+
player_data_mapper.expect(:update, nil, [:ref2, e2_hash])
|
265
|
+
store.save(e2)
|
266
|
+
|
267
|
+
player_data_mapper.expect(:insert, :ref1, [e3_hash])
|
268
|
+
store.save(e3)
|
269
|
+
|
270
|
+
assert player_data_mapper.verify
|
271
|
+
end
|
272
|
+
|
273
|
+
it 'fails if data_mapper not found' do
|
274
|
+
query = OpenStruct.new(:entity => 'NotFound')
|
275
|
+
error = assert_raises RuntimeError do
|
276
|
+
store.query(query)
|
277
|
+
end
|
278
|
+
|
279
|
+
assert_match(/no data_mapper .* found/i, error.message)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
173
283
|
describe 'nesting' do
|
174
284
|
User = Struct.new(:name)
|
175
285
|
Group = Struct.new(:name, :users)
|
@@ -189,28 +299,19 @@ describe 'store' do
|
|
189
299
|
end
|
190
300
|
let(:user_data_mapper) { MiniTest::Mock.new }
|
191
301
|
let(:group_data_mapper) { MiniTest::Mock.new }
|
192
|
-
let(:user_entity_mapper) {
|
193
|
-
def
|
194
|
-
@entity = entity
|
195
|
-
end
|
196
|
-
|
197
|
-
def mapped
|
302
|
+
let(:user_entity_mapper) { Struct.new(:store) do
|
303
|
+
def map(entity)
|
198
304
|
{
|
199
|
-
'name' =>
|
305
|
+
'name' => entity.name
|
200
306
|
}
|
201
307
|
end
|
202
308
|
end
|
203
309
|
}
|
204
|
-
let(:group_entity_mapper) {
|
205
|
-
def
|
206
|
-
@store = store
|
207
|
-
@entity = entity
|
208
|
-
end
|
209
|
-
|
210
|
-
def mapped
|
310
|
+
let(:group_entity_mapper) { Struct.new(:store) do
|
311
|
+
def map(entity)
|
211
312
|
{
|
212
|
-
'name' =>
|
213
|
-
'users' =>
|
313
|
+
'name' => entity.name,
|
314
|
+
'users' => store.save(entity.users)
|
214
315
|
}
|
215
316
|
end
|
216
317
|
end
|
@@ -223,7 +324,11 @@ describe 'store' do
|
|
223
324
|
]
|
224
325
|
group = Group.new('g1', users)
|
225
326
|
|
226
|
-
|
327
|
+
refs = [
|
328
|
+
Store::Ref.new('User', :ref1),
|
329
|
+
Store::Ref.new('User', :ref2)
|
330
|
+
]
|
331
|
+
group_data_mapper.expect :insert, nil, [{'name' => 'g1', 'users' => refs}]
|
227
332
|
user_data_mapper.expect :insert, :ref1, [{'name' => 'u1'}]
|
228
333
|
user_data_mapper.expect :insert, :ref2, [{'name' => 'u2'}]
|
229
334
|
|
@@ -243,7 +348,11 @@ describe 'store' do
|
|
243
348
|
user_data_mapper.expect :insert, :uref1, [{'name' => 'u1'}]
|
244
349
|
store.save(users[0])
|
245
350
|
|
246
|
-
|
351
|
+
refs = [
|
352
|
+
Store::Ref.new('User', :uref1),
|
353
|
+
Store::Ref.new('User', :ref2)
|
354
|
+
]
|
355
|
+
group_data_mapper.expect :insert, nil, [{'name' => 'g1', 'users' => refs}]
|
247
356
|
user_data_mapper.expect :update, :ref1, [:uref1, {'name' => 'u1'}]
|
248
357
|
user_data_mapper.expect :insert, :ref2, [{'name' => 'u2'}]
|
249
358
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: store
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jakob Holderbaum
|
@@ -65,8 +65,16 @@ files:
|
|
65
65
|
- Rakefile
|
66
66
|
- example.rb
|
67
67
|
- lib/store.rb
|
68
|
+
- lib/store/data_mapper.rb
|
69
|
+
- lib/store/entity_mapper.rb
|
70
|
+
- lib/store/query.rb
|
71
|
+
- lib/store/ref.rb
|
72
|
+
- lib/store/repo.rb
|
68
73
|
- lib/store/version.rb
|
69
74
|
- spec/helper.rb
|
75
|
+
- spec/store_query_spec.rb
|
76
|
+
- spec/store_ref_spec.rb
|
77
|
+
- spec/store_repo_spec.rb
|
70
78
|
- spec/store_spec.rb
|
71
79
|
- store.gemspec
|
72
80
|
homepage: https://github.com/holderbaum/store
|
@@ -95,4 +103,7 @@ specification_version: 4
|
|
95
103
|
summary: Repository Pattern and Data-Mapper Pattern
|
96
104
|
test_files:
|
97
105
|
- spec/helper.rb
|
106
|
+
- spec/store_query_spec.rb
|
107
|
+
- spec/store_ref_spec.rb
|
108
|
+
- spec/store_repo_spec.rb
|
98
109
|
- spec/store_spec.rb
|