hold 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,49 @@
1
+ module Hold
2
+ module InMemory
3
+ class IdentitySetRepository
4
+ include Hold::IdentitySetRepository
5
+
6
+ def initialize(allocates_ids=false)
7
+ @by_id = {}
8
+ @id_seq = 0 if allocates_ids
9
+ end
10
+
11
+ def allocates_ids?
12
+ !!@id_seq
13
+ end
14
+
15
+ def store(object)
16
+ id = object.id
17
+ object.send(:id=, id = @id_seq += 1) if @id_seq && !id
18
+ raise MissingIdentity unless id
19
+ @by_id[id] = object
20
+ end
21
+
22
+ def delete(object)
23
+ id = object.id or raise MissingIdentity
24
+ delete_id(id)
25
+ end
26
+
27
+ def contains?(object)
28
+ id = object.id or raise MissingIdentity
29
+ @by_id.include?(id)
30
+ end
31
+
32
+ def get_all
33
+ @by_id.values
34
+ end
35
+
36
+ def get_by_id(id)
37
+ value = @by_id[id] and value.dup
38
+ end
39
+
40
+ def delete_id(id)
41
+ @by_id.delete(id)
42
+ end
43
+
44
+ def contains_id?(id)
45
+ @by_id.include?(id)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,30 @@
1
+ module Hold
2
+ module InMemory
3
+ class InMemory::ObjectCell < InMemory::Cell
4
+ include Hold::ObjectCell
5
+
6
+ def get
7
+ @value && @value.dup
8
+ end
9
+
10
+ def get_property(property_name)
11
+ @value && @value[property_name]
12
+ end
13
+
14
+ def set_property(property_name, value)
15
+ raise EmptyConflict unless @value
16
+ @value[property_name] = value
17
+ end
18
+
19
+ def clear_property(property_name)
20
+ raise EmptyConflict unless @value
21
+ @value.delete(property_name)
22
+ end
23
+
24
+ def has_property?(property_name)
25
+ raise EmptyConflict unless @value
26
+ @value.has_key?(property_name)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,28 @@
1
+ module Hold
2
+ module InMemory
3
+ class SetRepository
4
+
5
+ include Hold::SetRepository
6
+
7
+ def initialize
8
+ @set = Set.new
9
+ end
10
+
11
+ def store(value)
12
+ @set << value
13
+ end
14
+
15
+ def delete(value)
16
+ @set.delete(value)
17
+ end
18
+
19
+ def contains?(value)
20
+ @set.include?(value)
21
+ end
22
+
23
+ def get_all
24
+ @set.to_a
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,441 +1,19 @@
1
1
  require 'thin_models/lazy_array'
2
2
 
3
- module Hold
4
- class Error < ::RuntimeError; end
5
- class UnsupportedOperation < Error; end
6
- class EmptyConflict < Error; end
7
- class IdentityConflict < Error; end
8
- class MissingIdentity < Error; end
9
-
10
- # A set of interfaces for persistence based around an object model.
11
- #
12
- # We're expected to use various implementations of these interfaces,
13
- # including in-memory persistence, serialized persistence in a cache,
14
- # persistence via mapping to a relational database, and combined database /
15
- # cache lookup.
16
- #
17
- # They should also be quite easy to wrap in a restful resource layer, since
18
- # the resource structure may often correspond closely to an object model
19
- # persistence interface.
20
-
21
- # The most fundamental persistence interface. Just offers a storage slot
22
- # which stores a single instance, supporting get/set
23
- module Cell
24
- def get
25
- raise UnsupportedOperation
26
- end
27
-
28
- def set(value)
29
- raise UnsupportedOperation
30
- end
31
-
32
- # Cells may optionally be 'emptyable?', that is, admit a special state of
33
- # 'empty' which is different to the state of storing an instance.
34
- #
35
- # empty and nil are distinct states.
36
- #
37
- # empty: undefined / uninitialized / unknown / not persisted / key not
38
- # present in hash / missing nil: null / known to be nil / persisted
39
- # explicitly as being nil / key present in hash with value of nil
40
- #
41
- # Annoying as this may seem this is useful in a bunch of contexts with the
42
- # data models we're constrained to be using. Eg "row exists but value of
43
- # column is NULL" vs "row doesn't exist" in SQL, or "property missing" vs
44
- # "property present and equal to null" for JSON objects
45
-
46
- def empty?
47
- false
48
- end
49
-
50
- def clear
51
- raise UnsupportedOperation
52
- end
53
-
54
- def set_if_empty(value)
55
- raise EmptyConflict unless empty?
56
- set(value)
57
- end
58
-
59
- def set_unless_empty(value)
60
- raise EmptyConflict if empty?
61
- set(value)
62
- end
63
-
64
- def get_unless_empty
65
- raise EmptyConflict if empty?
66
- get
67
- end
68
- alias :get! :get_unless_empty
69
-
70
- # Can override to indicate if you only support getting/setting a particular
71
- # class or classes:
72
- def can_get_class?(klass); true; end
73
- def can_set_class?(klass); true; end
74
- end
75
-
76
- # Interface extending Cell which offers some array-specific persistence
77
- # methods for use only with Arrays.
78
- # Default implementations are in terms of get, but it's expected that you'd
79
- # override with more efficient implementations.
80
- module ArrayCell
81
- include Cell
82
-
83
- def get_slice(start, length)
84
- value = get() and value[start, length]
85
- end
86
-
87
- def get_length
88
- value = get() and value.length
89
- end
90
-
91
- # returns an instance of ThinModels::LazyArray which lazily computes slices
92
- # and length based on the get_length and get_slice methods you define.
93
- def get_lazy_array
94
- LazyArray.new(self)
95
- end
96
-
97
- def can_get_class?(klass); klass == Array; end
98
- def can_set_class?(klass); klass <= Array; end
99
-
100
- # Can override to indicate if you only support getting/setting arrays with
101
- # items of a particular class or classes:
102
- def can_get_item_class?(klass); true; end
103
- def can_set_item_class?(klass); true; end
104
-
105
- class LazyArray < ThinModels::LazyArray::Memoized
106
- def initialize(array_cell)
107
- @array_cell = array_cell
108
- end
109
-
110
- def _each(&b)
111
- @array_cell.get.each(&b)
112
- end
113
-
114
- def slice_from_start_and_length(start, length)
115
- @array_cell.get_slice(start, length)
116
- end
117
-
118
- def _length
119
- @array_cell.get_length
120
- end
121
- end
122
- end
123
-
124
- # Interface extending Cell which offers some object-property-specific
125
- # persistence methods for use only with Structs/Objects.
126
- # Default implementations are in terms of get and set, but it's expected that
127
- # you'd override with more efficient implementations.
128
- module ObjectCell
129
- include Cell
130
-
131
- # default implementation gets the entire object in order to get the
132
- # property in question. you might want to override with something more
133
- # efficient
134
- def get_property(property_name)
135
- value = get() and value[property_name]
136
- end
137
-
138
- # default implementation gets the entire object and replaces it with a
139
- # version with the property in question changed.
140
- # you might want to override with something more efficient.
141
- def set_property(property_name, value)
142
- object = get()
143
- object[property_name] = value
144
- set(object)
145
- end
146
-
147
- def clear_property(property_name)
148
- value = get()
149
- value.delete(property_name)
150
- set(value)
151
- end
152
-
153
- def has_property?(property_name)
154
- !get_property(property_name).nil?
155
- end
156
-
157
- def get_properties(*properties)
158
- properties.map {|p| get_property(p)}
159
- end
160
-
161
- # May return a Cell which allows get / set / potentially other operations
162
- # on a particular property of this object in the context of its parent
163
- # object.
164
- #
165
- # Be careful about the semantics if exposing property cells which allow
166
- # partial write operations (like set_property) on the property value in the
167
- # context of the parent object. If you do this it should only update the
168
- # property value in that context, not in all contexts.
169
- #
170
- # By analogy to normal ruby hashes, it should mean this:
171
- # a[:foo] = a[:foo].merge(:bar => 3)
172
- # rather than this:
173
- # a[:foo][:bar] = 3
174
- # which would have an effect visible to any other object holding a
175
- # reference to a[:foo].
176
- #
177
- # If you want the latter, you probably want to be updating a[:foo] in some
178
- # hold cell which is canonical for the identity of that object.
179
- #
180
- # If you don't want the former, don't return a PropertyCell which allows
181
- # partial updates. For simplicity's sake this is the stance taken by the
182
- # default PropertyCell implementation.
183
- def property_cell(property_name)
184
- PropertyCell.new(self, property_name)
185
- end
186
-
187
- # An implementation of the basic Cell interface designed to wrap a property
188
- # of an ObjectCell as a Cell itself.
189
- class PropertyCell
190
- include Cell
191
-
192
- def initialize(object_cell, property_name)
193
- @object_cell = object_cell
194
- @property_name = property_name
195
- end
196
-
197
- def get
198
- @object_cell.get_property(@property_name)
199
- end
200
-
201
- def set(value)
202
- @object_cell.set_property(@property_name, value)
203
- end
204
-
205
- def empty?
206
- !@object_cell.has_property?(@property_name)
207
- end
208
-
209
- def clear
210
- @object_cell.clear_property(@property_name)
211
- end
212
- end
213
-
214
- # These are here for you to use if you want to use them for Array
215
- # properties gotten via ObjectCells, although the default implementation of
216
- # property_cell doesn't do this
217
- class ArrayPropertyCell < PropertyCell
218
- include ArrayCell
219
- end
220
-
221
- class ObjectPropertyCell < PropertyCell
222
- include ObjectCell
223
- end
224
- end
225
-
226
-
227
- # Persists values in a key/value store
228
- module HashRepository
229
- def set_with_key(key, value)
230
- raise UnsupportedOperation
231
- end
232
-
233
- def get_with_key(key)
234
- raise UnsupportedOperation
235
- end
236
-
237
- # Gets multiple entities at a time by a list of keys.
238
- # May override with an efficient multi-get implementation.
239
- def get_many_with_keys(keys)
240
- keys.map {|key| get_with_key(key)}
241
- end
242
-
243
- def clear_key(key)
244
- raise UnsupportedOperation
245
- end
246
-
247
- def has_key?(key)
248
- raise UnsupportedOperation
249
- end
250
- alias_method :key?, :has_key?
251
-
252
- def key_cell(key)
253
- KeyCell.new(self, key)
254
- end
255
-
256
- # Can override to indicate if you only support getting/setting values of a
257
- # particular class or classes:
258
- def can_get_class?(klass); true; end
259
- def can_set_class?(klass); true; end
260
-
261
- class KeyCell
262
- include Cell
263
-
264
- def initialize(hash_repository, key)
265
- @hash_repository, @key = hash_repository, key
266
- end
267
-
268
- def get
269
- @hash_repository.get_with_key(@key)
270
- end
271
-
272
- def set(value)
273
- @hash_repository.set_with_key(@key, value)
274
- end
275
-
276
- def clear
277
- @hash_repository.clear_key(@key)
278
- end
279
-
280
- def empty?
281
- @hash_repository.has_key?(@key)
282
- end
283
-
284
- def can_get_class?(klass); @hash_repository.can_get_class?(klass); end
285
- def can_set_class?(klass); @hash_repository.can_set_class?(klass); end
286
- end
287
- end
288
-
289
- module SetRepository
290
- # Store the object in the persisted set. If the object is already in the
291
- # set, it may stay there untouched (in the case where the object's identity
292
- # is based on its entire contents), or get replaced by the newer version
293
- # (where the object's identity is only based on, say, some identity
294
- # property), but will never be duplicated (since this is a set)
295
- def store(object)
296
- raise UnsupportedOperation
297
- end
298
-
299
- # like store, but should raise IdentityConflict if the object (or one equal
300
- # to it) already exists in the set
301
- def store_new(object)
302
- raise IdentityConflict if contains?(object)
303
- store(object)
304
- end
305
-
306
- # Removes the object with this identity from the persisted set
307
- def delete(object)
308
- raise UnsupportedOperation
309
- end
310
-
311
- # Is this object in the persisted set?
312
- def contains?(object)
313
- raise UnsupportedOperation
314
- end
315
-
316
- # Returns an array of all persisted items in the set
317
- def get_all
318
- raise UnsupportedOperation
319
- end
320
-
321
- def can_get_class?(klass); true; end
322
- def can_set_class?(klass); true; end
323
- end
324
-
325
- # A special kind of SetRepository which stores Objects whose identities are
326
- # determined by an identity property, and supports indexed lookup by their
327
- # id.
328
- #
329
- # May allocate the IDs itself, or not.
330
- #
331
- # Exposes a somewhat more familiar CRUD-style persistence interface as a
332
- # result.
333
- #
334
- # Comes with default implementations for most of the extra interface
335
- module IdentitySetRepository
336
- include SetRepository
337
-
338
- # Either the repository allocates IDs, and you don't (in which case any
339
- # entity with an ID may be assumed to be already persisted in the repo), or
340
- # the repository doesn't allocate IDs (in which case you must always supply
341
- # one when persisting a new object).
342
- #
343
- # If you allocates_ids?, you should deal with an object without an identity
344
- # as an argument to store and store_new, and you should set the id property
345
- # on it before returning it.
346
- #
347
- # If you don't, you may raise MissingIdentity if passed an object without
348
- # one.
349
- def allocates_ids?
350
- false
351
- end
352
-
353
- # Looks up a persisted object by the value of its identity property
354
- def get_by_id(id)
355
- raise UnsupportedOperation
356
- end
357
-
358
- # deletes the object with the given identity where it exists in the repo
359
- def delete_id(id)
360
- delete(get_by_id(id))
361
- end
362
-
363
- # Loads a fresh instance of the given object by its id
364
- # Returns nil where the object is no longer present in the repository
365
- def reload(object)
366
- id = object.id or raise MissingIdentity
367
- get_by_id(id)
368
- end
369
-
370
- # Like reload, but updates the given instance in-place with the updated
371
- # data.
372
- # Returns nil where the object is no longer present in the repository
373
- def load(object)
374
- raise UnsupportedOperation unless object.respond_to?(:merge!)
375
- updated = reload(object) or return
376
- object.merge!(updated)
377
- object
378
- end
379
-
380
- # Applies an in-place update to the object, where it exists in the
381
- # repository
382
- def update(entity, update_entity)
383
- raise UnsupportedOperation unless entity.respond_to?(:merge!)
384
- load(entity) or return
385
- entity.merge!(update_entity)
386
- store(entity)
387
- end
388
-
389
- # Applies an in-place update to the object with the given identity, where
390
- # it exists in the repository
391
- def update_by_id(id, update_entity)
392
- entity = get_by_id(id) or return
393
- raise UnsupportedOperation unless entity.respond_to?(:merge!)
394
- entity.merge!(update_entity)
395
- store(entity)
396
- end
397
-
398
- def contains_id?(id)
399
- !!get_by_id(id)
400
- end
401
-
402
-
403
- def get_many_by_ids(ids)
404
- ids.map {|id| get_by_id(id)}
405
- end
406
-
407
- def id_cell(id)
408
- IdCell.new(self, id)
409
- end
410
-
411
- def cell(object)
412
- id = object.id or raise MissingIdentity
413
- id_cell(id)
414
- end
415
-
416
- class IdCell
417
- include ObjectCell
418
-
419
- def initialize(id_set_repo, id)
420
- @id_set_repo = id_set_repo
421
- @id = id
422
- end
423
-
424
- def get
425
- @id_set_repo.get_by_id(@id)
426
- end
427
-
428
- def set(value)
429
- @id_set_repo.update_by_id(@id, value)
430
- end
431
-
432
- def empty?
433
- !@id_set_repo.contains?(@id)
434
- end
435
-
436
- def clear
437
- @id_set_repo.delete_id(@id)
438
- end
439
- end
440
- end
441
- end
3
+ # A set of interfaces for persistence based around an object model.
4
+ #
5
+ # We're expected to use various implementations of these interfaces,
6
+ # including in-memory persistence, serialized persistence in a cache,
7
+ # persistence via mapping to a relational database, and combined database /
8
+ # cache lookup.
9
+ #
10
+ # They should also be quite easy to wrap in a restful resource layer, since
11
+ # the resource structure may often correspond closely to an object model
12
+ # persistence interface.
13
+
14
+ require 'hold/interfaces/cell'
15
+ require 'hold/interfaces/array_cell'
16
+ require 'hold/interfaces/object_cell'
17
+ require 'hold/interfaces/hash_repository'
18
+ require 'hold/interfaces/set_repository'
19
+ require 'hold/interfaces/identity_set_repository'