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 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