flexor 0.1.0

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,426 @@
1
+ Flexor.new:
2
+ with no arguments:
3
+ creates an empty store
4
+ is a root Flexor
5
+ with an empty hash:
6
+ creates an empty store
7
+ with a flat hash:
8
+ stores all key-value pairs
9
+ allows method access on each key
10
+ allows bracket access on each key
11
+ with a nested hash:
12
+ recursively converts nested hashes into Flexors
13
+ allows method access at every level
14
+ allows bracket access at every level
15
+ with a deeply nested hash (3+ levels):
16
+ allows method access at every level
17
+ allows bracket access at every level
18
+ with a hash containing arrays:
19
+ preserves arrays of scalars
20
+ converts hashes inside arrays into Flexors
21
+ preserves non-hash elements in mixed arrays
22
+ with a hash containing nil values:
23
+ stores the nil value directly
24
+ with a non-hash argument:
25
+ raises ArgumentError
26
+ root vs non-root:
27
+ defaults to root: true:
28
+ is a root Flexor
29
+ when root: false:
30
+ is a non-root Flexor
31
+
32
+ Flexor.from_json:
33
+ with valid flat JSON:
34
+ creates a Flexor with symbolized keys
35
+ allows method access on parsed values
36
+ with nested JSON:
37
+ recursively converts nested objects
38
+ allows method chaining on nested values
39
+ with JSON containing arrays:
40
+ preserves arrays
41
+ converts objects inside arrays into Flexors
42
+ with invalid JSON:
43
+ raises a parse error
44
+
45
+ reading a level 1 property via method:
46
+ the property does exist:
47
+ reads the correct property
48
+ the property does NOT exist:
49
+ returns a value where nil? is true
50
+ returns a value that == nil
51
+ does not return the nil singleton
52
+ returns a Flexor
53
+
54
+ reading a level 1 property via hash accessor:
55
+ the property does exist:
56
+ reads the correct property
57
+ the property does NOT exist:
58
+ returns a value where nil? is true
59
+ returns a value that == nil
60
+ does not return the nil singleton
61
+ returns a Flexor
62
+
63
+ reading a level 2 property via method:
64
+ the level 1 property does exist:
65
+ the level 2 property does exist:
66
+ reads the correct property
67
+ the level 2 property does NOT exist:
68
+ returns a value where nil? is true
69
+ the level 1 property does NOT exist:
70
+ returns a value where nil? is true
71
+ the returned value is itself a Flexor that supports further chaining
72
+
73
+ reading a level 2 property via hash accessor:
74
+ the level 1 property does exist:
75
+ the level 2 property does exist:
76
+ reads the correct property
77
+ the level 2 property does NOT exist:
78
+ returns a value where nil? is true
79
+ the level 1 property does NOT exist:
80
+ returns a value where nil? is true
81
+ the returned value is itself a Flexor that supports further chaining
82
+
83
+ reading at arbitrary depth (3+ levels):
84
+ all levels set:
85
+ reads the correct property
86
+ no levels set:
87
+ returns a value where nil? is true at every intermediate level
88
+ supports chaining to any depth without error
89
+
90
+ method vs bracket access equivalence:
91
+ reading the same key via method and bracket returns the same value:
92
+ for a set property
93
+ for an unset property
94
+ writing via method and reading via bracket returns the written value
95
+ writing via bracket and reading via method returns the written value
96
+
97
+ writing a level 1 property via method:
98
+ writes and reads the written property
99
+
100
+ writing a level 1 property via hash accessor:
101
+ writes and reads the written property
102
+
103
+ writing a level 2 property via method:
104
+ the level 1 property has been set:
105
+ writes and reads the written property
106
+ the level 1 property has NOT been set:
107
+ vivifies the level 1 property
108
+ writes and reads the written property
109
+
110
+ writing a level 2 property via hash accessor:
111
+ the level 1 property has been set:
112
+ writes and reads the written property
113
+ the level 1 property has NOT been set:
114
+ vivifies the level 1 property
115
+ writes and reads the written property
116
+
117
+ writing at arbitrary depth (3+ levels):
118
+ no intermediate levels set:
119
+ vivifies every intermediate level
120
+ writes and reads the written property
121
+
122
+ assigning a plain hash via setter:
123
+ auto-converts the hash to a Flexor:
124
+ bracket access returns a Flexor, not a Hash
125
+ method chaining works on the assigned value
126
+
127
+ assigning an array via setter:
128
+ with an array of scalars:
129
+ stores and retrieves the array
130
+ with an array of hashes:
131
+ auto-converts inner hashes to Flexors
132
+
133
+ Flexor#set_raw:
134
+ stores a hash without vivification
135
+ stores an array of hashes without vivification
136
+ stores scalars normally
137
+
138
+ Flexor#merge:
139
+ returns a new Flexor with the merged contents
140
+ does not modify the original
141
+ overwrites scalar values
142
+ deep merges nested hashes
143
+ deep merges multiple levels
144
+ replaces a nested subtree with a scalar when incoming is scalar
145
+ replaces a scalar with a nested hash
146
+ accepts a Flexor as argument
147
+ vivifies hashes in the merged result
148
+
149
+ Flexor#merge!:
150
+ modifies the receiver in place
151
+ returns self
152
+ deep merges nested hashes in place
153
+ accepts a Flexor as argument
154
+
155
+ Flexor#delete:
156
+ removes the key and returns the value
157
+ returns nil for a missing key
158
+ removes nested Flexors
159
+ does not affect other keys
160
+
161
+ Flexor#to_json:
162
+ serializes scalar values
163
+ serializes nested Flexors
164
+ serializes arrays
165
+ round-trips with from_json
166
+ serializes an empty Flexor as an empty object
167
+
168
+ overwriting:
169
+ replacing a nested subtree with a scalar:
170
+ the scalar is stored
171
+ the old subtree is gone
172
+ replacing a scalar with a nested write:
173
+ vivifies the new path
174
+ replacing a scalar with nil:
175
+ the property now reads as nil
176
+
177
+ Flexor#nil?:
178
+ on an unset property (empty Flexor):
179
+ is expected to be truthy
180
+ on a property with a value:
181
+ is expected to be falsey
182
+ on a property set explicitly to nil:
183
+ # This accesses the real nil singleton, not a Flexor.
184
+ # Documenting end-to-end behavior, not Flexor#nil?.
185
+ is expected to be truthy (via NilClass#nil?, not Flexor#nil?)
186
+ on a root Flexor with no data:
187
+ is expected to be truthy
188
+ on a root Flexor with data:
189
+ is expected to be falsey
190
+
191
+ Flexor#==:
192
+ when comparing an unset property (empty Flexor):
193
+ against nil:
194
+ is expected to be truthy
195
+ against a non-nil value:
196
+ is expected to be falsey
197
+ when comparing two Flexors:
198
+ when both have identical contents:
199
+ is expected to be truthy
200
+ when contents differ:
201
+ is expected to be falsey
202
+ when both are empty:
203
+ is expected to be truthy
204
+ when comparing a Flexor against a Hash:
205
+ when the keys and values are identical:
206
+ is expected to be truthy
207
+ when the keys and values are NOT identical:
208
+ is expected to be falsey
209
+ when comparing a property set explicitly to nil:
210
+ # This accesses the real nil singleton, not a Flexor.
211
+ # Documenting end-to-end behavior, not Flexor#==.
212
+ against nil:
213
+ is expected to be truthy (via NilClass#==, not Flexor#==)
214
+ against a non-nil value:
215
+ is expected to be falsey (via NilClass#==, not Flexor#==)
216
+ when comparing a scalar value:
217
+ # store.name = "alice"; store.name == "alice"
218
+ # This accesses the real String, not a Flexor.
219
+ # Documenting end-to-end behavior, not Flexor#==.
220
+ against an identical scalar:
221
+ is expected to be truthy (via String#==, not Flexor#==)
222
+ against a different scalar:
223
+ is expected to be falsey (via String#==, not Flexor#==)
224
+ symmetry:
225
+ nil == empty Flexor:
226
+ # NilClass#== does not know about Flexor, so this may be false
227
+ # even though empty_flexor == nil is true
228
+ documents whether equality is symmetric
229
+
230
+ Flexor#===:
231
+ when used in a case/when on a Flexor value:
232
+ matching against a scalar:
233
+ matches when values are equal
234
+ matching against nil:
235
+ matches when property is unset
236
+
237
+ Flexor.===:
238
+ when used as a class in case/when:
239
+ matches a Flexor instance
240
+ does not match a plain Hash
241
+ does not match nil
242
+
243
+ Flexor#to_s:
244
+ when the property has been set:
245
+ to a scalar value:
246
+ returns the string representation of the store
247
+ to a nested value:
248
+ returns the string representation of the store
249
+ when the property has NOT been set:
250
+ returns an empty string to match nil.to_s behavior
251
+ returns an empty string, not actual nil
252
+ is indistinguishable from nil in string interpolation
253
+ is indistinguishable from nil when passed to puts
254
+ at multiple levels of depth:
255
+ returns empty string at every unset level
256
+ is indistinguishable from nil in puts at any depth
257
+ root vs non-root:
258
+ root with data:
259
+ returns the store's string representation
260
+ root without data:
261
+ returns empty string
262
+ non-root with data:
263
+ returns the store's string representation
264
+ non-root without data:
265
+ returns empty string
266
+
267
+ Flexor#inspect:
268
+ on a root Flexor with values:
269
+ returns the hash inspection
270
+ on a root Flexor without values:
271
+ returns the empty hash inspection
272
+ on an unset property (empty non-root Flexor):
273
+ is indistinguishable from inspecting nil
274
+ on a non-root Flexor with values:
275
+ returns the hash inspection
276
+
277
+ Flexor#to_ary:
278
+ always returns nil:
279
+ prevents puts from treating Flexor as an array
280
+ prevents splat expansion from treating Flexor as an array
281
+
282
+ Flexor#to_h:
283
+ with scalar values:
284
+ returns a plain hash with scalar values
285
+ with nested Flexors:
286
+ recursively converts Flexors back to hashes
287
+ with arrays:
288
+ preserves arrays of scalars
289
+ recursively converts Flexors inside arrays
290
+ with empty nested Flexors:
291
+ converts empty Flexors to nil
292
+ round-trip:
293
+ flat hash survives new -> to_h unchanged
294
+ nested hash survives new -> to_h unchanged
295
+ deeply nested hash survives new -> to_h unchanged
296
+ hash with arrays survives new -> to_h unchanged
297
+ with autovivified but never written paths:
298
+ does not include phantom keys
299
+
300
+ Flexor#deconstruct_keys:
301
+ with a Flexor containing values:
302
+ supports pattern matching via case/in
303
+ extracts matching keys
304
+ returns only requested keys
305
+ with nested Flexors:
306
+ supports nested pattern matching
307
+ with no keys requested:
308
+ returns the entire store
309
+ on an empty Flexor:
310
+ returns an empty hash
311
+
312
+ Flexor#deconstruct:
313
+ returns the values of the store for array-style pattern matching
314
+ supports variable binding in array patterns
315
+ returns an empty array for an empty Flexor
316
+ preserves nested Flexors for recursive pattern matching
317
+
318
+ hash-like query methods:
319
+ empty?:
320
+ when the store has no data:
321
+ is expected to be truthy
322
+ when the store has data:
323
+ is expected to be falsey
324
+ does not autovivify a key named :empty?
325
+ keys:
326
+ returns the keys of the store
327
+ does not autovivify a key named :keys
328
+ values:
329
+ returns the values of the store
330
+ does not autovivify a key named :values
331
+ size / length:
332
+ returns the number of keys in the store
333
+ does not autovivify a key named :size or :length
334
+ key? / has_key?:
335
+ when the key exists:
336
+ is expected to be truthy
337
+ when the key does not exist:
338
+ is expected to be falsey
339
+ does not autovivify the queried key
340
+
341
+ symbol vs string keys:
342
+ method access uses symbol keys:
343
+ store.foo writes to and reads from :foo
344
+ bracket access with a symbol:
345
+ reads and writes using the symbol key
346
+ bracket access with a string:
347
+ reads and writes using the string key
348
+ cross-access:
349
+ store.foo and store[:foo] access the same value
350
+ store.foo and store["foo"] do NOT access the same value
351
+
352
+ method name collisions:
353
+ methods defined on Object (class, freeze, hash, object_id, send, display):
354
+ are not intercepted by method_missing
355
+ values stored under those keys are accessible via bracket
356
+ methods defined on Flexor (to_h, to_s, nil?, ==):
357
+ are not intercepted by method_missing
358
+ values stored under those keys are accessible via bracket
359
+
360
+ method_missing edge cases:
361
+ calling a method with arguments (store.foo(1)):
362
+ raises NoMethodError via super
363
+ calling a method with a block:
364
+ raises NoMethodError via super
365
+ setter with too many arguments (store.foo = 1, 2):
366
+ raises NoMethodError via super
367
+
368
+ respond_to?:
369
+ for any arbitrary method name:
370
+ is expected to be truthy
371
+ for method names that also exist on Object:
372
+ is expected to be truthy
373
+ with include_private: true:
374
+ is expected to be truthy
375
+
376
+ respond_to_missing?:
377
+ returns true for any method name:
378
+ is expected to be truthy
379
+
380
+ array handling end-to-end:
381
+ via constructor:
382
+ hashes inside arrays are converted to Flexors
383
+ scalars inside arrays are preserved
384
+ nested arrays of hashes are converted recursively
385
+ via direct assignment:
386
+ assigning an array of hashes auto-converts inner hashes
387
+ reading from arrays stored in Flexor:
388
+ array elements are accessible via standard array methods
389
+
390
+ autovivification side effects:
391
+ reading an unset property:
392
+ creates the key in the store (default_proc behavior)
393
+ the created key holds an empty Flexor
394
+ chaining reads on unset properties:
395
+ creates keys at every intermediate level
396
+ implications for to_h after read-only access:
397
+ # This ties back to the phantom keys exclusion above
398
+ reads do not leave traces in to_h
399
+
400
+ thread safety:
401
+ concurrent reads on the same Flexor:
402
+ do not raise
403
+ concurrent writes to different keys:
404
+ documents the expected behavior
405
+ concurrent autovivification:
406
+ documents the expected behavior
407
+
408
+ dup and clone:
409
+ duping a Flexor:
410
+ returns a new Flexor with the same contents
411
+ modifications to the dup do not affect the original
412
+ cloning a Flexor:
413
+ returns a new Flexor with the same contents
414
+ modifications to the clone do not affect the original
415
+ deep nesting:
416
+ dup is shallow (nested Flexors are shared)
417
+
418
+ freeze:
419
+ freezing a Flexor:
420
+ prevents further writes
421
+ reads still work
422
+ autovivification raises on frozen store
423
+
424
+ enumeration:
425
+ each / map / select:
426
+ delegates to the underlying store