hold 1.0.2 → 1.0.3

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.
@@ -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'