store 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a74cccaaa0ed4cdab16de31489b1ff617729d4c1
4
- data.tar.gz: 5e91a636c220af914cb10fd87c91c13b69ccf476
3
+ metadata.gz: 3cf1280f06ddee3f9d3c5190390bbe8cf29ffd6b
4
+ data.tar.gz: da7e50bad1b9702421edb0820c1bcb91bc02a481
5
5
  SHA512:
6
- metadata.gz: e4e07870ed58bdd9502ccda5399c2f356eb80044f1e35e100f2fa3a671056b1f15205bb765e7e54cfa4b5a01f172d845db925335ac78879336970039e04141a7
7
- data.tar.gz: 984fc480e714a55d799f2fd57c598e632ddb4477c6a1a5fdc862e50c99f2858a6069992ddcf36f104d47a460eba1ee659c26ec4aa942e5e5a5ae14632968a1c0
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
- class UserEntityMapper < Struct.new(:store, :user)
24
- def mapped
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 < Struct.new(:store, :catalog)
32
- def mapped
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(*catalog.products)
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 < Struct.new(:store, :product)
42
- def mapped
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
- class UserDataMapper
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
- reference = :"user_#{rand 100}"
53
- puts "user_insert[#{reference.inspect}] => #{data.inspect}"
54
- reference
83
+ id = next_id
84
+ @items[id] = dup(data)
85
+ id
55
86
  end
56
87
 
57
- def update(reference, data)
58
- puts "user_update[#{reference.inspect}] => #{data.inspect}"
59
- reference
88
+ def update(id, data)
89
+ @items[id] = dup(data)
90
+ id
60
91
  end
61
- end
62
92
 
63
- class CatalogDataMapper
64
- def insert(data)
65
- reference = :"catalog_#{rand 100}"
66
- puts "catalog_insert[#{reference.inspect}] => #{data.inspect}"
67
- reference
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 update(reference, data)
71
- puts "catalog_update[#{reference.inspect}] => #{data.inspect}"
72
- reference
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 ProductDataMapper
77
- def insert(data)
78
- reference = :"product_#{rand 100}"
79
- puts "product_insert[#{reference.inspect}] => #{data.inspect}"
80
- reference
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
- def update(reference, data)
84
- puts "product_update[#{reference.inspect}] => #{data.inspect}"
85
- reference
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
- store = Store.new(entities, data_mappers, entity_mappers)
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
- editor = store.build 'User', :name => 'Mr. Repo'
176
+ # Usage ########################################################################
111
177
 
112
- catalog = store.build 'Catalog', :name => 'Catalog 1', :editor => editor
178
+ editor = repo.build 'User', :name => 'Mr. Repo'
113
179
 
114
- catalog.add_product store.build('Product', :name => 'Pickaxe', :price => 100)
115
- catalog.add_product store.build('Product', :name => 'Scredriver', :price => 400)
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
- store.save(catalog)
186
+ repo.save(catalog)
119
187
 
120
188
  catalog.products[0].price = 24
121
189
 
122
190
  p 'save call'
123
- store.save(catalog)
191
+ repo.save(catalog)
192
+
193
+ product = catalog.products.delete_at(1)
194
+ repo.remove product
124
195
 
125
- catalog.products.delete_at(1)
196
+ p catalog
126
197
 
127
198
  p 'save call'
128
199
  # no implicit delete here. is this a problem?
129
- store.save(catalog)
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
@@ -0,0 +1,11 @@
1
+ class Store
2
+ class Query
3
+ attr_reader :entity, :name, :args
4
+
5
+ def initialize(entity, name, *args)
6
+ @entity = entity
7
+ @name = name
8
+ @args = args
9
+ end
10
+ end
11
+ end
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
@@ -1,3 +1,3 @@
1
1
  class Store
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
data/lib/store.rb CHANGED
@@ -1,10 +1,14 @@
1
- require 'set'
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(*entities)
22
- references = entities.map do |entity|
23
- next nil unless entity
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
- references
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
- entities_with_references = data_mapper.select(query) || {}
53
+ mapped_entities_with_references = data_mapper.select(query) || {}
57
54
 
58
- entities_with_references.map do |reference, entity|
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 raise_unless_data_mapper_found(entity, data_mapper)
66
- raise "No data_mapper for #{entity.inspect} found" unless data_mapper
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, entity).mapped
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.target
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, :entity) do
17
- def mapped
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
- assert_equal [:ref1, :ref2], store.save(e1, e2)
76
-
77
- assert data_mapper.verify
78
- end
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 query result' do
89
- data_mapper.expect(:select, {:ref1 => :e1, :ref2 => :e2}, [query])
90
- assert_equal [:e1, :e2], store.query(query)
91
- end
92
-
93
- it 'maps nil query result to empty collection' do
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) { Class.new do
193
- def initialize(store, entity)
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' => @entity.name
305
+ 'name' => entity.name
200
306
  }
201
307
  end
202
308
  end
203
309
  }
204
- let(:group_entity_mapper) { Class.new do
205
- def initialize(store, entity)
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' => @entity.name,
213
- 'users' => @store.save(*@entity.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
- group_data_mapper.expect :insert, nil, [{'name' => 'g1', 'users' => [:ref1, :ref2]}]
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
- group_data_mapper.expect :insert, nil, [{'name' => 'g1', 'users' => [:ref1, :ref2]}]
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.3
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