lutaml-store 0.1.1
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 +7 -0
- data/.github/workflows/main.yml +27 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.rubocop.yml +10 -0
- data/.rubocop_todo.yml +450 -0
- data/CLAUDE.md +57 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/CORRECTED_HTTP_CACHE_IMPLEMENTATION.md +209 -0
- data/CORRECTED_HTTP_CACHE_PLAN.md +164 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +220 -0
- data/README.adoc +1430 -0
- data/Rakefile +12 -0
- data/TODO.impl/0-lutaml-store-self-quality.md +112 -0
- data/TODO.impl/1-lutaml-hal-migration.md +60 -0
- data/TODO.impl/2-glossarist-migration.md +359 -0
- data/TODO.impl/3-lutaml-jsonschema-migration.md +273 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/demo/Gemfile +15 -0
- data/demo/Gemfile.lock +61 -0
- data/demo/README.adoc +301 -0
- data/demo/data/vcards/co/contact_10_thompson.data +1 -0
- data/demo/data/vcards/co/contact_10_thompson.meta +1 -0
- data/demo/data/vcards/co/contact_1_doe.data +1 -0
- data/demo/data/vcards/co/contact_1_doe.meta +1 -0
- data/demo/data/vcards/co/contact_2_smith.data +1 -0
- data/demo/data/vcards/co/contact_2_smith.meta +1 -0
- data/demo/data/vcards/co/contact_3_johnson.data +1 -0
- data/demo/data/vcards/co/contact_3_johnson.meta +1 -0
- data/demo/data/vcards/co/contact_4_garcia.data +1 -0
- data/demo/data/vcards/co/contact_4_garcia.meta +1 -0
- data/demo/data/vcards/co/contact_5_wilson.data +1 -0
- data/demo/data/vcards/co/contact_5_wilson.meta +1 -0
- data/demo/data/vcards/co/contact_6_brown.data +1 -0
- data/demo/data/vcards/co/contact_6_brown.meta +1 -0
- data/demo/data/vcards/co/contact_7_davis.data +1 -0
- data/demo/data/vcards/co/contact_7_davis.meta +1 -0
- data/demo/data/vcards/co/contact_8_anderson.data +1 -0
- data/demo/data/vcards/co/contact_8_anderson.meta +1 -0
- data/demo/data/vcards/co/contact_9_taylor.data +1 -0
- data/demo/data/vcards/co/contact_9_taylor.meta +1 -0
- data/demo/data/vcards.db +0 -0
- data/demo/pottery_class_demo.rb +164 -0
- data/demo/vcard_models.rb +140 -0
- data/demo/vcard_store_demo.rb +526 -0
- data/lib/lutaml/store/adapter/base.rb +65 -0
- data/lib/lutaml/store/adapter/filesystem.rb +288 -0
- data/lib/lutaml/store/adapter/memory.rb +225 -0
- data/lib/lutaml/store/adapter/sqlite.rb +193 -0
- data/lib/lutaml/store/adapter.rb +12 -0
- data/lib/lutaml/store/attribute_updater.rb +198 -0
- data/lib/lutaml/store/basic_store.rb +190 -0
- data/lib/lutaml/store/cache.rb +108 -0
- data/lib/lutaml/store/cache_store.rb +282 -0
- data/lib/lutaml/store/composite_model_handler.rb +169 -0
- data/lib/lutaml/store/compression.rb +137 -0
- data/lib/lutaml/store/config.rb +178 -0
- data/lib/lutaml/store/database_store.rb +425 -0
- data/lib/lutaml/store/events.rb +92 -0
- data/lib/lutaml/store/format/base.rb +33 -0
- data/lib/lutaml/store/format/json.rb +25 -0
- data/lib/lutaml/store/format/jsonl.rb +37 -0
- data/lib/lutaml/store/format/marshal_format.rb +37 -0
- data/lib/lutaml/store/format/yaml.rb +29 -0
- data/lib/lutaml/store/format/yamls.rb +35 -0
- data/lib/lutaml/store/format.rb +33 -0
- data/lib/lutaml/store/http_cache.rb +279 -0
- data/lib/lutaml/store/http_cache_config.rb +53 -0
- data/lib/lutaml/store/http_cache_entry.rb +69 -0
- data/lib/lutaml/store/http_header_processor.rb +175 -0
- data/lib/lutaml/store/integrity.rb +102 -0
- data/lib/lutaml/store/model_registration.rb +75 -0
- data/lib/lutaml/store/model_registry.rb +123 -0
- data/lib/lutaml/store/model_serializer.rb +69 -0
- data/lib/lutaml/store/monitor.rb +192 -0
- data/lib/lutaml/store/storage_key.rb +40 -0
- data/lib/lutaml/store/version.rb +7 -0
- data/lib/lutaml/store.rb +41 -0
- data/lutaml-store.gemspec +35 -0
- data/plan.adoc +606 -0
- data/sig/lutaml/store.rbs +6 -0
- data/spec/lutaml/store/adapter_interface_spec.rb +89 -0
- data/spec/lutaml/store/anti_pattern_guard_spec.rb +35 -0
- data/spec/lutaml/store/anti_pattern_spec.rb +78 -0
- data/spec/lutaml/store/autoload_spec.rb +34 -0
- data/spec/lutaml/store/cache_store_spec.rb +271 -0
- data/spec/lutaml/store/compression_spec.rb +78 -0
- data/spec/lutaml/store/config_enhanced_spec.rb +158 -0
- data/spec/lutaml/store/corrected_http_cache_integration_spec.rb +336 -0
- data/spec/lutaml/store/custom_serializer_spec.rb +108 -0
- data/spec/lutaml/store/database_store_spec.rb +279 -0
- data/spec/lutaml/store/file_io_spec.rb +219 -0
- data/spec/lutaml/store/format_round_trip_spec.rb +110 -0
- data/spec/lutaml/store/format_spec.rb +70 -0
- data/spec/lutaml/store/http_cache_entry_spec.rb +203 -0
- data/spec/lutaml/store/http_cache_hal_integration_spec.rb +404 -0
- data/spec/lutaml/store/http_cache_spec.rb +422 -0
- data/spec/lutaml/store/http_header_processor_spec.rb +290 -0
- data/spec/lutaml/store/import_spec.rb +90 -0
- data/spec/lutaml/store/integrity_spec.rb +157 -0
- data/spec/lutaml/store/key_collision_serializer_spec.rb +98 -0
- data/spec/lutaml/store/load_save_spec.rb +107 -0
- data/spec/lutaml/store/lutaml_model_integration_spec.rb +291 -0
- data/spec/lutaml/store/model_serializer_spec.rb +140 -0
- data/spec/lutaml/store/store_spec.rb +182 -0
- data/spec/lutaml/store_spec.rb +21 -0
- data/spec/spec_helper.rb +16 -0
- metadata +166 -0
data/plan.adoc
ADDED
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
= Lutaml::Store database-style API implementation plan
|
|
2
|
+
|
|
3
|
+
== Overview
|
|
4
|
+
|
|
5
|
+
This document outlines the implementation plan for transforming Lutaml::Store
|
|
6
|
+
from a basic key-value store into a sophisticated store-centric database-style
|
|
7
|
+
API with model registry, polymorphic support, and composite model relationships.
|
|
8
|
+
|
|
9
|
+
== Current state analysis
|
|
10
|
+
|
|
11
|
+
=== Existing implementation
|
|
12
|
+
|
|
13
|
+
The current Lutaml::Store provides:
|
|
14
|
+
|
|
15
|
+
* Basic key-value operations (get, set, delete, exists)
|
|
16
|
+
|
|
17
|
+
* Multiple storage adapters (memory, filesystem, sqlite)
|
|
18
|
+
|
|
19
|
+
* ModelStore class with basic model serialization
|
|
20
|
+
|
|
21
|
+
* Caching, monitoring, and event systems
|
|
22
|
+
|
|
23
|
+
* Thread-safe operations
|
|
24
|
+
|
|
25
|
+
=== Limitations
|
|
26
|
+
|
|
27
|
+
The current implementation lacks:
|
|
28
|
+
|
|
29
|
+
* Model registry system with configurable key fields
|
|
30
|
+
|
|
31
|
+
* Polymorphic model support with inheritance handling
|
|
32
|
+
|
|
33
|
+
* Composite model relationships (nested registered models stored independently)
|
|
34
|
+
|
|
35
|
+
* Database-style CRUD operations (fetch, save, update, destroy)
|
|
36
|
+
|
|
37
|
+
* Dot notation for nested updates ("studio.location")
|
|
38
|
+
|
|
39
|
+
* Block-based and hash-based update patterns
|
|
40
|
+
|
|
41
|
+
== Target API specification
|
|
42
|
+
|
|
43
|
+
Based on sanity.rb requirements, the new API must support:
|
|
44
|
+
|
|
45
|
+
[source,ruby]
|
|
46
|
+
----
|
|
47
|
+
# Store initialization with model registry
|
|
48
|
+
store = Lutaml::Store.new(
|
|
49
|
+
adapter: :memory,
|
|
50
|
+
models: [
|
|
51
|
+
{ model: PotteryClass, key: :class_id },
|
|
52
|
+
{ model: Studio, key: :studio_key, polymorphic_class_key: :_class }
|
|
53
|
+
]
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Save operations
|
|
57
|
+
store.save(pottery_classes_array)
|
|
58
|
+
|
|
59
|
+
# Fetch operations
|
|
60
|
+
pottery_class = store.fetch(model: PotteryClass, class_id: "pottery_class")
|
|
61
|
+
|
|
62
|
+
# Update operations with attributes
|
|
63
|
+
store.update(
|
|
64
|
+
model: PotteryClass,
|
|
65
|
+
class_id: "clay_class",
|
|
66
|
+
attributes: [
|
|
67
|
+
{ key: :description, value: "Updated description" },
|
|
68
|
+
{ key: "studio.location", value: "Downtown" }
|
|
69
|
+
]
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Polymorphic model updates
|
|
73
|
+
store.save(
|
|
74
|
+
model: PotteryClass,
|
|
75
|
+
class_id: "pottery_class",
|
|
76
|
+
attributes: [
|
|
77
|
+
{
|
|
78
|
+
key: :studio,
|
|
79
|
+
value: CeramicStudio.new(
|
|
80
|
+
studio_key: "pottery_studio",
|
|
81
|
+
name: "Ceramic studio",
|
|
82
|
+
clay_type: "Stoneware"
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
)
|
|
87
|
+
----
|
|
88
|
+
|
|
89
|
+
== Implementation phases
|
|
90
|
+
|
|
91
|
+
=== Phase 1: Core architecture redesign
|
|
92
|
+
|
|
93
|
+
==== 1.1 New store entry point
|
|
94
|
+
|
|
95
|
+
Create new main entry point that accepts model registry:
|
|
96
|
+
|
|
97
|
+
* Modify `lib/lutaml/store.rb` to provide `Lutaml::Store.new` class method
|
|
98
|
+
|
|
99
|
+
* Accept `adapter` and `models` parameters
|
|
100
|
+
|
|
101
|
+
* Delegate to enhanced ModelStore for model operations
|
|
102
|
+
|
|
103
|
+
* Maintain backward compatibility with existing Store class
|
|
104
|
+
|
|
105
|
+
==== 1.2 Model registry system
|
|
106
|
+
|
|
107
|
+
Implement model registration and metadata management:
|
|
108
|
+
|
|
109
|
+
* Create `lib/lutaml/store/model_registry.rb`
|
|
110
|
+
|
|
111
|
+
* Create `lib/lutaml/store/model_registration.rb`
|
|
112
|
+
|
|
113
|
+
* Support model class, key field, and polymorphic_class_key configuration
|
|
114
|
+
|
|
115
|
+
* Validate model registrations (key fields must exist)
|
|
116
|
+
|
|
117
|
+
* Handle polymorphic inheritance chains
|
|
118
|
+
|
|
119
|
+
==== 1.3 Composite model detection
|
|
120
|
+
|
|
121
|
+
Implement composite model relationship handling:
|
|
122
|
+
|
|
123
|
+
* Create `lib/lutaml/store/composite_model_handler.rb`
|
|
124
|
+
|
|
125
|
+
* Detect when registered models are nested within other models
|
|
126
|
+
|
|
127
|
+
* Track model relationships and references
|
|
128
|
+
|
|
129
|
+
* Store composite models independently while maintaining references
|
|
130
|
+
|
|
131
|
+
=== Phase 2: Database-style operations
|
|
132
|
+
|
|
133
|
+
==== 2.1 CRUD operations
|
|
134
|
+
|
|
135
|
+
Implement database-style model operations:
|
|
136
|
+
|
|
137
|
+
* `fetch(model:, key_value)` - retrieve model by key
|
|
138
|
+
|
|
139
|
+
* `save(model_or_array)` - save single model or array of models
|
|
140
|
+
|
|
141
|
+
* `update(model:, key_value, attributes:)` - update with attributes or block
|
|
142
|
+
|
|
143
|
+
* `destroy(model:, key_value)` - delete model
|
|
144
|
+
|
|
145
|
+
* Support both single models and arrays
|
|
146
|
+
|
|
147
|
+
==== 2.2 Update mechanisms
|
|
148
|
+
|
|
149
|
+
Implement flexible update patterns:
|
|
150
|
+
|
|
151
|
+
* Create `lib/lutaml/store/attribute_updater.rb`
|
|
152
|
+
|
|
153
|
+
* Support attribute arrays with key/value pairs
|
|
154
|
+
|
|
155
|
+
* Support dot notation for nested updates ("studio.location")
|
|
156
|
+
|
|
157
|
+
* Support block-based updates with model yielding
|
|
158
|
+
|
|
159
|
+
* Handle polymorphic model updates (changing model types)
|
|
160
|
+
|
|
161
|
+
==== 2.3 Key generation and management
|
|
162
|
+
|
|
163
|
+
Implement model key handling:
|
|
164
|
+
|
|
165
|
+
* Extract key values from models using configured key fields
|
|
166
|
+
|
|
167
|
+
* Generate storage keys with model namespacing
|
|
168
|
+
|
|
169
|
+
* Handle polymorphic class keys for inheritance
|
|
170
|
+
|
|
171
|
+
* Ensure unique keys across model types
|
|
172
|
+
|
|
173
|
+
=== Phase 3: Advanced features
|
|
174
|
+
|
|
175
|
+
==== 3.1 Polymorphic support
|
|
176
|
+
|
|
177
|
+
Implement inheritance and polymorphic handling:
|
|
178
|
+
|
|
179
|
+
* Detect polymorphic relationships using polymorphic_class_key
|
|
180
|
+
|
|
181
|
+
* Store and retrieve correct subclass instances
|
|
182
|
+
|
|
183
|
+
* Handle polymorphic updates that change model types
|
|
184
|
+
|
|
185
|
+
* Maintain polymorphic class information in storage
|
|
186
|
+
|
|
187
|
+
==== 3.2 Nested attribute updates
|
|
188
|
+
|
|
189
|
+
Implement dot notation and nested updates:
|
|
190
|
+
|
|
191
|
+
* Parse dot notation ("studio.location") into nested attribute paths
|
|
192
|
+
|
|
193
|
+
* Navigate object graphs to update nested attributes
|
|
194
|
+
|
|
195
|
+
* Support updates on composite registered models
|
|
196
|
+
|
|
197
|
+
* Handle deep nesting and complex object structures
|
|
198
|
+
|
|
199
|
+
==== 3.3 Reference management
|
|
200
|
+
|
|
201
|
+
Implement model relationship tracking:
|
|
202
|
+
|
|
203
|
+
* Track relationships between composite models
|
|
204
|
+
|
|
205
|
+
* Update references when composite models change
|
|
206
|
+
|
|
207
|
+
* Maintain referential integrity
|
|
208
|
+
|
|
209
|
+
* Handle cascading updates for related models
|
|
210
|
+
|
|
211
|
+
=== Phase 4: Integration and testing
|
|
212
|
+
|
|
213
|
+
==== 4.1 Adapter integration
|
|
214
|
+
|
|
215
|
+
Ensure compatibility with existing adapters:
|
|
216
|
+
|
|
217
|
+
* Test all adapters (memory, filesystem, sqlite) with new operations
|
|
218
|
+
|
|
219
|
+
* Maintain existing adapter interfaces
|
|
220
|
+
|
|
221
|
+
* Update adapter implementations if needed
|
|
222
|
+
|
|
223
|
+
* Preserve performance characteristics
|
|
224
|
+
|
|
225
|
+
==== 4.2 Event system integration
|
|
226
|
+
|
|
227
|
+
Integrate with existing event system:
|
|
228
|
+
|
|
229
|
+
* Emit appropriate events for model operations
|
|
230
|
+
|
|
231
|
+
* Maintain existing event patterns for compatibility
|
|
232
|
+
|
|
233
|
+
* Add new events for model-specific operations
|
|
234
|
+
|
|
235
|
+
* Support both sync and async event handling
|
|
236
|
+
|
|
237
|
+
==== 4.3 Comprehensive testing
|
|
238
|
+
|
|
239
|
+
Implement thorough test coverage:
|
|
240
|
+
|
|
241
|
+
* Test all sanity.rb scenarios
|
|
242
|
+
|
|
243
|
+
* Test polymorphic inheritance patterns
|
|
244
|
+
|
|
245
|
+
* Test composite model relationships
|
|
246
|
+
|
|
247
|
+
* Test nested updates with dot notation
|
|
248
|
+
|
|
249
|
+
* Test error conditions and edge cases
|
|
250
|
+
|
|
251
|
+
== Detailed implementation specifications
|
|
252
|
+
|
|
253
|
+
=== Core classes to implement
|
|
254
|
+
|
|
255
|
+
==== Lutaml::Store (main entry point)
|
|
256
|
+
|
|
257
|
+
[source,ruby]
|
|
258
|
+
----
|
|
259
|
+
module Lutaml
|
|
260
|
+
module Store
|
|
261
|
+
def self.new(adapter:, models: [])
|
|
262
|
+
# Create enhanced ModelStore with registry
|
|
263
|
+
ModelStore.new(adapter: adapter, models: models)
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
----
|
|
268
|
+
|
|
269
|
+
==== Lutaml::Store::ModelRegistry
|
|
270
|
+
|
|
271
|
+
[source,ruby]
|
|
272
|
+
----
|
|
273
|
+
class ModelRegistry
|
|
274
|
+
def initialize(model_configs)
|
|
275
|
+
@registrations = {}
|
|
276
|
+
register_models(model_configs)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def register(model_class, key_field, options = {})
|
|
280
|
+
# Create ModelRegistration instance
|
|
281
|
+
# Validate key field exists on model
|
|
282
|
+
# Handle polymorphic_class_key option
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def find_registration(model_class)
|
|
286
|
+
# Find registration for model class or superclass
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def registered?(model_class)
|
|
290
|
+
# Check if model is registered
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
----
|
|
294
|
+
|
|
295
|
+
==== Lutaml::Store::ModelRegistration
|
|
296
|
+
|
|
297
|
+
[source,ruby]
|
|
298
|
+
----
|
|
299
|
+
class ModelRegistration
|
|
300
|
+
attr_reader :model_class, :key_field, :polymorphic_class_key
|
|
301
|
+
|
|
302
|
+
def initialize(model_class, key_field, options = {})
|
|
303
|
+
@model_class = model_class
|
|
304
|
+
@key_field = key_field
|
|
305
|
+
@polymorphic_class_key = options[:polymorphic_class_key]
|
|
306
|
+
validate!
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def extract_key(model)
|
|
310
|
+
# Extract key value from model instance
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def polymorphic?
|
|
314
|
+
# Check if registration supports polymorphism
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
----
|
|
318
|
+
|
|
319
|
+
==== Lutaml::Store::CompositeModelHandler
|
|
320
|
+
|
|
321
|
+
[source,ruby]
|
|
322
|
+
----
|
|
323
|
+
class CompositeModelHandler
|
|
324
|
+
def initialize(registry)
|
|
325
|
+
@registry = registry
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def extract_composite_models(model)
|
|
329
|
+
# Find nested registered models
|
|
330
|
+
# Return hash of composite models with their keys
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def store_composite_models(composite_models, store)
|
|
334
|
+
# Store each composite model independently
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def resolve_composite_models(model, store)
|
|
338
|
+
# Replace composite model references with actual instances
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
----
|
|
342
|
+
|
|
343
|
+
==== Lutaml::Store::AttributeUpdater
|
|
344
|
+
|
|
345
|
+
[source,ruby]
|
|
346
|
+
----
|
|
347
|
+
class AttributeUpdater
|
|
348
|
+
def initialize(registry, store)
|
|
349
|
+
@registry = registry
|
|
350
|
+
@store = store
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def update_attributes(model, attributes)
|
|
354
|
+
# Apply attribute updates to model
|
|
355
|
+
# Handle dot notation for nested updates
|
|
356
|
+
# Support polymorphic model changes
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def parse_nested_path(path)
|
|
360
|
+
# Parse "studio.location" into ["studio", "location"]
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def apply_nested_update(model, path_parts, value)
|
|
364
|
+
# Navigate object graph and apply update
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
----
|
|
368
|
+
|
|
369
|
+
==== Enhanced Lutaml::Store::ModelStore
|
|
370
|
+
|
|
371
|
+
[source,ruby]
|
|
372
|
+
----
|
|
373
|
+
class ModelStore
|
|
374
|
+
def initialize(adapter:, models: [])
|
|
375
|
+
@store = Store.new(adapter: adapter)
|
|
376
|
+
@registry = ModelRegistry.new(models)
|
|
377
|
+
@composite_handler = CompositeModelHandler.new(@registry)
|
|
378
|
+
@attribute_updater = AttributeUpdater.new(@registry, @store)
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def save(model_or_array)
|
|
382
|
+
# Handle single model or array
|
|
383
|
+
# Extract and store composite models
|
|
384
|
+
# Generate storage keys
|
|
385
|
+
# Serialize and store models
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def fetch(model:, **key_params)
|
|
389
|
+
# Extract key value from parameters
|
|
390
|
+
# Generate storage key
|
|
391
|
+
# Retrieve and deserialize model
|
|
392
|
+
# Resolve composite model references
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def update(model:, attributes: nil, **key_params, &block)
|
|
396
|
+
# Fetch existing model
|
|
397
|
+
# Apply updates (attributes or block)
|
|
398
|
+
# Handle composite model updates
|
|
399
|
+
# Store updated model
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def destroy(model:, **key_params)
|
|
403
|
+
# Generate storage key
|
|
404
|
+
# Delete model from store
|
|
405
|
+
# Handle composite model cleanup
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
----
|
|
409
|
+
|
|
410
|
+
=== Storage key generation
|
|
411
|
+
|
|
412
|
+
Model storage keys will follow this pattern:
|
|
413
|
+
|
|
414
|
+
* Simple models: `"#{model_class_name}:#{key_value}"`
|
|
415
|
+
|
|
416
|
+
* Polymorphic models: `"#{polymorphic_class}:#{key_value}"`
|
|
417
|
+
|
|
418
|
+
* Composite models: Stored with their own keys, referenced by parent
|
|
419
|
+
|
|
420
|
+
=== Serialization strategy
|
|
421
|
+
|
|
422
|
+
* Use existing Serializer class for model serialization
|
|
423
|
+
|
|
424
|
+
* Store polymorphic class information with model data
|
|
425
|
+
|
|
426
|
+
* Handle composite model references during serialization/deserialization
|
|
427
|
+
|
|
428
|
+
* Maintain compatibility with existing serialization formats
|
|
429
|
+
|
|
430
|
+
=== Error handling
|
|
431
|
+
|
|
432
|
+
Define specific error types:
|
|
433
|
+
|
|
434
|
+
* `ModelNotRegisteredError` - when trying to operate on unregistered model
|
|
435
|
+
|
|
436
|
+
* `InvalidKeyError` - when key field doesn't exist or is nil
|
|
437
|
+
|
|
438
|
+
* `PolymorphicUpdateError` - when polymorphic update fails
|
|
439
|
+
|
|
440
|
+
* `CompositeModelError` - when composite model handling fails
|
|
441
|
+
|
|
442
|
+
== Migration strategy
|
|
443
|
+
|
|
444
|
+
=== Backward compatibility
|
|
445
|
+
|
|
446
|
+
* Maintain existing Store and ModelStore classes
|
|
447
|
+
|
|
448
|
+
* Provide new entry point through `Lutaml::Store.new`
|
|
449
|
+
|
|
450
|
+
* Existing code continues to work unchanged
|
|
451
|
+
|
|
452
|
+
* Gradual migration path for users
|
|
453
|
+
|
|
454
|
+
=== Deprecation plan
|
|
455
|
+
|
|
456
|
+
* Mark old interfaces as deprecated in documentation
|
|
457
|
+
|
|
458
|
+
* Provide migration examples in README
|
|
459
|
+
|
|
460
|
+
* Plan removal in future major version
|
|
461
|
+
|
|
462
|
+
* Clear upgrade path for all use cases
|
|
463
|
+
|
|
464
|
+
== Testing strategy
|
|
465
|
+
|
|
466
|
+
=== Unit tests
|
|
467
|
+
|
|
468
|
+
* Test each new class in isolation
|
|
469
|
+
|
|
470
|
+
* Mock dependencies for focused testing
|
|
471
|
+
|
|
472
|
+
* Cover all error conditions
|
|
473
|
+
|
|
474
|
+
* Test edge cases and boundary conditions
|
|
475
|
+
|
|
476
|
+
=== Integration tests
|
|
477
|
+
|
|
478
|
+
* Test complete workflows from sanity.rb
|
|
479
|
+
|
|
480
|
+
* Test all adapter types with new operations
|
|
481
|
+
|
|
482
|
+
* Test polymorphic inheritance scenarios
|
|
483
|
+
|
|
484
|
+
* Test composite model relationships
|
|
485
|
+
|
|
486
|
+
=== Performance tests
|
|
487
|
+
|
|
488
|
+
* Benchmark new operations against current implementation
|
|
489
|
+
|
|
490
|
+
* Ensure no performance regression
|
|
491
|
+
|
|
492
|
+
* Test with large datasets
|
|
493
|
+
|
|
494
|
+
* Profile memory usage
|
|
495
|
+
|
|
496
|
+
== Documentation updates
|
|
497
|
+
|
|
498
|
+
=== README.adoc
|
|
499
|
+
|
|
500
|
+
Complete rewrite following documentation formatting rules:
|
|
501
|
+
|
|
502
|
+
* Sentence-case headings
|
|
503
|
+
|
|
504
|
+
* Proper list formatting with blank lines
|
|
505
|
+
|
|
506
|
+
* Example blocks with `[example]` and `====`
|
|
507
|
+
|
|
508
|
+
* Source code blocks with `[source,{lang}]` and `----`
|
|
509
|
+
|
|
510
|
+
* Line wrapping at 80 characters
|
|
511
|
+
|
|
512
|
+
* Comprehensive API documentation with examples
|
|
513
|
+
|
|
514
|
+
=== API documentation
|
|
515
|
+
|
|
516
|
+
* Document all new classes and methods
|
|
517
|
+
|
|
518
|
+
* Provide usage examples for each operation
|
|
519
|
+
|
|
520
|
+
* Document error conditions and handling
|
|
521
|
+
|
|
522
|
+
* Include migration guide from old API
|
|
523
|
+
|
|
524
|
+
== Implementation timeline
|
|
525
|
+
|
|
526
|
+
=== Week 1: Core architecture
|
|
527
|
+
|
|
528
|
+
* Implement ModelRegistry and ModelRegistration
|
|
529
|
+
|
|
530
|
+
* Create new Store entry point
|
|
531
|
+
|
|
532
|
+
* Basic model save/fetch operations
|
|
533
|
+
|
|
534
|
+
=== Week 2: Update mechanisms
|
|
535
|
+
|
|
536
|
+
* Implement AttributeUpdater
|
|
537
|
+
|
|
538
|
+
* Support dot notation updates
|
|
539
|
+
|
|
540
|
+
* Block-based updates
|
|
541
|
+
|
|
542
|
+
* Polymorphic model updates
|
|
543
|
+
|
|
544
|
+
=== Week 3: Composite models
|
|
545
|
+
|
|
546
|
+
* Implement CompositeModelHandler
|
|
547
|
+
|
|
548
|
+
* Nested model detection and storage
|
|
549
|
+
|
|
550
|
+
* Reference resolution
|
|
551
|
+
|
|
552
|
+
* Relationship management
|
|
553
|
+
|
|
554
|
+
=== Week 4: Integration and testing
|
|
555
|
+
|
|
556
|
+
* Comprehensive test suite
|
|
557
|
+
|
|
558
|
+
* Documentation updates
|
|
559
|
+
|
|
560
|
+
* Performance optimization
|
|
561
|
+
|
|
562
|
+
* Bug fixes and refinements
|
|
563
|
+
|
|
564
|
+
== Success criteria
|
|
565
|
+
|
|
566
|
+
The implementation will be considered successful when:
|
|
567
|
+
|
|
568
|
+
* All sanity.rb scenarios work correctly
|
|
569
|
+
|
|
570
|
+
* Existing tests continue to pass
|
|
571
|
+
|
|
572
|
+
* New comprehensive test suite passes
|
|
573
|
+
|
|
574
|
+
* Documentation is complete and accurate
|
|
575
|
+
|
|
576
|
+
* Performance is maintained or improved
|
|
577
|
+
|
|
578
|
+
* Migration path is clear and documented
|
|
579
|
+
|
|
580
|
+
== Risk mitigation
|
|
581
|
+
|
|
582
|
+
=== Technical risks
|
|
583
|
+
|
|
584
|
+
* **Complexity**: Break down into small, testable components
|
|
585
|
+
|
|
586
|
+
* **Performance**: Benchmark and optimize critical paths
|
|
587
|
+
|
|
588
|
+
* **Compatibility**: Maintain existing interfaces during transition
|
|
589
|
+
|
|
590
|
+
=== Project risks
|
|
591
|
+
|
|
592
|
+
* **Scope creep**: Stick to sanity.rb requirements
|
|
593
|
+
|
|
594
|
+
* **Timeline**: Prioritize core functionality first
|
|
595
|
+
|
|
596
|
+
* **Quality**: Comprehensive testing at each phase
|
|
597
|
+
|
|
598
|
+
== Conclusion
|
|
599
|
+
|
|
600
|
+
This implementation plan provides a clear roadmap for transforming Lutaml::Store
|
|
601
|
+
into a sophisticated database-style API while maintaining backward compatibility
|
|
602
|
+
and ensuring high quality through comprehensive testing and documentation.
|
|
603
|
+
|
|
604
|
+
The phased approach allows for incremental development and testing, reducing
|
|
605
|
+
risk and ensuring each component works correctly before moving to the next
|
|
606
|
+
phase.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe Lutaml::Store::Adapter::Base do
|
|
6
|
+
subject(:adapter) { described_class.new }
|
|
7
|
+
|
|
8
|
+
it "raises NotImplementedError for get" do
|
|
9
|
+
expect { adapter.get("key") }.to raise_error(NotImplementedError)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "raises NotImplementedError for set" do
|
|
13
|
+
expect { adapter.set("key", "value") }.to raise_error(NotImplementedError)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "raises NotImplementedError for delete" do
|
|
17
|
+
expect { adapter.delete("key") }.to raise_error(NotImplementedError)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "raises NotImplementedError for exists?" do
|
|
21
|
+
expect { adapter.exists?("key") }.to raise_error(NotImplementedError)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "raises NotImplementedError for keys" do
|
|
25
|
+
expect { adapter.keys }.to raise_error(NotImplementedError)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "raises NotImplementedError for all" do
|
|
29
|
+
expect { adapter.all }.to raise_error(NotImplementedError)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "raises NotImplementedError for clear" do
|
|
33
|
+
expect { adapter.clear }.to raise_error(NotImplementedError)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "raises NotImplementedError for size" do
|
|
37
|
+
expect { adapter.size }.to raise_error(NotImplementedError)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "provides default close that does not raise" do
|
|
41
|
+
expect { adapter.close }.not_to raise_error
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
describe "default bulk operations" do
|
|
45
|
+
let(:concrete_adapter) do
|
|
46
|
+
Class.new(described_class) do
|
|
47
|
+
def initialize
|
|
48
|
+
super
|
|
49
|
+
@data = {}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def get(key)
|
|
53
|
+
@data[key]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def set(key, value)
|
|
57
|
+
@data[key] = value
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def delete(key)
|
|
61
|
+
@data.delete(key)
|
|
62
|
+
end
|
|
63
|
+
end.new
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "bulk_get iterates over get" do
|
|
67
|
+
concrete_adapter.set("a", 1)
|
|
68
|
+
concrete_adapter.set("b", 2)
|
|
69
|
+
expect(concrete_adapter.bulk_get(%w[a b c])).to eq("a" => 1, "b" => 2, "c" => nil)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "bulk_set iterates over set" do
|
|
73
|
+
concrete_adapter.bulk_set("a" => 1, "b" => 2)
|
|
74
|
+
expect(concrete_adapter.get("a")).to eq(1)
|
|
75
|
+
expect(concrete_adapter.get("b")).to eq(2)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "bulk_delete iterates over delete" do
|
|
79
|
+
concrete_adapter.set("a", 1)
|
|
80
|
+
concrete_adapter.set("b", 2)
|
|
81
|
+
result = concrete_adapter.bulk_delete(%w[a c])
|
|
82
|
+
expect(result).to eq("a" => 1, "c" => nil)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it "provides default stats" do
|
|
87
|
+
expect(adapter.stats).to eq({ adapter: described_class.name })
|
|
88
|
+
end
|
|
89
|
+
end
|