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
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "lutaml/store/http_cache"
|
|
5
|
+
|
|
6
|
+
# Mock W3C API components for testing
|
|
7
|
+
module MockW3cApi
|
|
8
|
+
class MockClient
|
|
9
|
+
def get(url)
|
|
10
|
+
{ "data" => "test response", "url" => url }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def get_with_headers(url, headers)
|
|
14
|
+
response = { "data" => "test response with headers", "url" => url }
|
|
15
|
+
# Simulate ETag header
|
|
16
|
+
response["etag"] = '"test-etag-123"' if headers["If-None-Match"]
|
|
17
|
+
response
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def get_by_url(url)
|
|
21
|
+
{ "data" => "test response by url", "url" => url }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def api_url
|
|
25
|
+
"https://api.w3.org"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class MockModel
|
|
30
|
+
def self.from_json(json)
|
|
31
|
+
data = JSON.parse(json)
|
|
32
|
+
new(data)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def initialize(data)
|
|
36
|
+
@data = data
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
attr_reader :data
|
|
40
|
+
|
|
41
|
+
def to_json(*_args)
|
|
42
|
+
@data.to_json
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class MockParameter
|
|
47
|
+
attr_reader :name, :location, :required, :default_value
|
|
48
|
+
|
|
49
|
+
def initialize(name, location: :query, required: false, default_value: nil)
|
|
50
|
+
@name = name.to_s
|
|
51
|
+
@location = location
|
|
52
|
+
@required = required
|
|
53
|
+
@default_value = default_value
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def validate!
|
|
57
|
+
raise ArgumentError, "Parameter name cannot be empty" if @name.nil? || @name.empty?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def path_parameter?
|
|
61
|
+
@location == :path
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def query_parameter?
|
|
65
|
+
@location == :query
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def validate_value(value)
|
|
69
|
+
!value.nil?
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
RSpec.describe "Corrected HTTP Cache Integration", skip: "WIP: HTTP cache integration not yet implemented" do
|
|
75
|
+
let(:cache_config) do
|
|
76
|
+
{
|
|
77
|
+
adapter_type: :memory,
|
|
78
|
+
default_ttl: 3600,
|
|
79
|
+
respect_http_headers: true,
|
|
80
|
+
enable_conditional_requests: true
|
|
81
|
+
}
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
let(:cache) { Lutaml::Store::HttpCache.new(cache_config) }
|
|
85
|
+
let(:client) { MockW3cApi::MockClient.new }
|
|
86
|
+
let(:register) { Lutaml::Hal::ModelRegister.new(name: :test, client: client, cache: cache_config) }
|
|
87
|
+
|
|
88
|
+
before do
|
|
89
|
+
# Configure the register with HTTP cache
|
|
90
|
+
register.cache_store = cache
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe "transparent cache integration" do
|
|
94
|
+
it "integrates cache at ModelRegister.fetch() level" do
|
|
95
|
+
# Add a test endpoint
|
|
96
|
+
register.add_endpoint(
|
|
97
|
+
id: :test_endpoint,
|
|
98
|
+
type: :index,
|
|
99
|
+
url: "/test",
|
|
100
|
+
model: MockW3cApi::MockModel,
|
|
101
|
+
parameters: []
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# First request should hit the API
|
|
105
|
+
expect(client).to receive(:get).with("https://api.w3.org/test").and_call_original
|
|
106
|
+
result1 = register.fetch(:test_endpoint)
|
|
107
|
+
|
|
108
|
+
# Second request should hit the cache
|
|
109
|
+
expect(client).not_to receive(:get)
|
|
110
|
+
result2 = register.fetch(:test_endpoint)
|
|
111
|
+
|
|
112
|
+
# Results should be equivalent
|
|
113
|
+
expect(result1.data["url"]).to eq(result2.data["url"])
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "supports HTTP-aware caching with conditional requests" do
|
|
117
|
+
# Add a test endpoint
|
|
118
|
+
register.add_endpoint(
|
|
119
|
+
id: :test_conditional,
|
|
120
|
+
type: :index,
|
|
121
|
+
url: "/test-conditional",
|
|
122
|
+
model: MockW3cApi::MockModel,
|
|
123
|
+
parameters: []
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# First request
|
|
127
|
+
result1 = register.fetch(:test_conditional)
|
|
128
|
+
expect(result1).to be_a(MockW3cApi::MockModel)
|
|
129
|
+
|
|
130
|
+
# Second request should use conditional headers
|
|
131
|
+
result2 = register.fetch(:test_conditional)
|
|
132
|
+
expect(result2).to be_a(MockW3cApi::MockModel)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "handles query parameters correctly" do
|
|
136
|
+
# Add endpoint with query parameters
|
|
137
|
+
register.add_endpoint(
|
|
138
|
+
id: :test_with_params,
|
|
139
|
+
type: :index,
|
|
140
|
+
url: "/test-params",
|
|
141
|
+
model: MockW3cApi::MockModel,
|
|
142
|
+
parameters: [
|
|
143
|
+
MockW3cApi::MockParameter.new("page", location: :query),
|
|
144
|
+
MockW3cApi::MockParameter.new("items", location: :query)
|
|
145
|
+
]
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Test with different parameters
|
|
149
|
+
result1 = register.fetch(:test_with_params, page: 1, items: 10)
|
|
150
|
+
result2 = register.fetch(:test_with_params, page: 2, items: 10)
|
|
151
|
+
result3 = register.fetch(:test_with_params, page: 1, items: 10) # Should hit cache
|
|
152
|
+
|
|
153
|
+
expect(result1.data["url"]).to include("page=1")
|
|
154
|
+
expect(result2.data["url"]).to include("page=2")
|
|
155
|
+
expect(result3.data["url"]).to eq(result1.data["url"])
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it "handles path parameters correctly" do
|
|
159
|
+
# Add endpoint with path parameters
|
|
160
|
+
register.add_endpoint(
|
|
161
|
+
id: :test_with_path,
|
|
162
|
+
type: :resource,
|
|
163
|
+
url: "/test/{id}",
|
|
164
|
+
model: MockW3cApi::MockModel,
|
|
165
|
+
parameters: [
|
|
166
|
+
MockW3cApi::MockParameter.new("id", location: :path)
|
|
167
|
+
]
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
result = register.fetch(:test_with_path, id: "test-123")
|
|
171
|
+
expect(result.data["url"]).to include("/test/test-123")
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
describe "cache configuration methods" do
|
|
176
|
+
let(:mock_hal) do
|
|
177
|
+
hal = double("Hal")
|
|
178
|
+
mock_register = double("Register")
|
|
179
|
+
allow(hal).to receive(:register).and_return(mock_register)
|
|
180
|
+
allow(mock_register).to receive(:cache_store=)
|
|
181
|
+
allow(mock_register).to receive(:cache_info).and_return({
|
|
182
|
+
adapter_type: "Memory",
|
|
183
|
+
current_size: 5,
|
|
184
|
+
default_ttl: 3600
|
|
185
|
+
})
|
|
186
|
+
allow(mock_register).to receive(:clear_cache)
|
|
187
|
+
allow(mock_register).to receive(:cache_stats).and_return({
|
|
188
|
+
hits: 10,
|
|
189
|
+
misses: 5,
|
|
190
|
+
hit_rate: 0.67
|
|
191
|
+
})
|
|
192
|
+
hal
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it "provides configure_cache method" do
|
|
196
|
+
expect(mock_hal.register).to receive(:cache_store=).with(cache)
|
|
197
|
+
|
|
198
|
+
# Simulate the configure_cache method
|
|
199
|
+
mock_hal.register.cache_store = cache
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
it "provides cache_info method" do
|
|
203
|
+
info = mock_hal.register.cache_info
|
|
204
|
+
expect(info).to include(:adapter_type, :current_size, :default_ttl)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
it "provides clear_cache method" do
|
|
208
|
+
expect(mock_hal.register).to receive(:clear_cache)
|
|
209
|
+
mock_hal.register.clear_cache
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
it "provides cache_stats method" do
|
|
213
|
+
stats = mock_hal.register.cache_stats
|
|
214
|
+
expect(stats).to include(:hits, :misses, :hit_rate)
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
describe "HTTP semantics preservation" do
|
|
219
|
+
it "respects HTTP headers" do
|
|
220
|
+
expect(cache.config.respect_http_headers).to be true
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
it "enables conditional requests" do
|
|
224
|
+
expect(cache.config.enable_conditional_requests).to be true
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
it "handles ETags correctly" do
|
|
228
|
+
# This would be tested with actual HTTP responses
|
|
229
|
+
# For now, we verify the cache is configured to handle them
|
|
230
|
+
expect(cache.config.respect_http_headers).to be true
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
describe "zero API changes requirement" do
|
|
235
|
+
it "maintains existing ModelRegister interface" do
|
|
236
|
+
# Verify that cache integration doesn't break existing interface
|
|
237
|
+
expect(register).to respond_to(:fetch)
|
|
238
|
+
expect(register).to respond_to(:add_endpoint)
|
|
239
|
+
expect(register).to respond_to(:models)
|
|
240
|
+
expect(register).to respond_to(:client)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
it "supports optional cache configuration" do
|
|
244
|
+
# Register should work without cache
|
|
245
|
+
no_cache_register = Lutaml::Hal::ModelRegister.new(name: :no_cache, client: client)
|
|
246
|
+
expect(no_cache_register.cache_store).to be_nil
|
|
247
|
+
|
|
248
|
+
# And with cache
|
|
249
|
+
cached_register = Lutaml::Hal::ModelRegister.new(name: :cached, client: client, cache: cache_config)
|
|
250
|
+
expect(cached_register.cache_store).not_to be_nil
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
describe "architectural correctness" do
|
|
255
|
+
it "integrates at the correct layer (ModelRegister.fetch)" do
|
|
256
|
+
# Verify that cache is checked in fetch method
|
|
257
|
+
register.add_endpoint(
|
|
258
|
+
id: :arch_test,
|
|
259
|
+
type: :index,
|
|
260
|
+
url: "/arch-test",
|
|
261
|
+
model: MockW3cApi::MockModel,
|
|
262
|
+
parameters: []
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
# Mock the cache to verify it's being used
|
|
266
|
+
expect(cache).to receive(:fetch).and_call_original
|
|
267
|
+
register.fetch(:arch_test)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
it "does not bypass w3c_api gem architecture" do
|
|
271
|
+
# This test ensures we're not creating direct HTTP clients
|
|
272
|
+
# Instead, we use the existing client through ModelRegister
|
|
273
|
+
expect(register.client).to be_a(MockW3cApi::MockClient)
|
|
274
|
+
expect(register.client).to respond_to(:get)
|
|
275
|
+
expect(register.client).to respond_to(:get_with_headers)
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
describe "performance benefits" do
|
|
280
|
+
it "provides measurable cache improvements" do
|
|
281
|
+
register.add_endpoint(
|
|
282
|
+
id: :perf_test,
|
|
283
|
+
type: :index,
|
|
284
|
+
url: "/perf-test",
|
|
285
|
+
model: MockW3cApi::MockModel,
|
|
286
|
+
parameters: []
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
# First request (cache miss)
|
|
290
|
+
start_time = Time.now
|
|
291
|
+
register.fetch(:perf_test)
|
|
292
|
+
first_duration = Time.now - start_time
|
|
293
|
+
|
|
294
|
+
# Second request (cache hit)
|
|
295
|
+
start_time = Time.now
|
|
296
|
+
register.fetch(:perf_test)
|
|
297
|
+
second_duration = Time.now - start_time
|
|
298
|
+
|
|
299
|
+
# Cache hit should be faster (though in memory it might be negligible)
|
|
300
|
+
expect(second_duration).to be <= first_duration
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
describe "error handling" do
|
|
305
|
+
it "handles cache failures gracefully" do
|
|
306
|
+
# Mock cache failure
|
|
307
|
+
allow(cache).to receive(:fetch).and_raise(StandardError, "Cache error")
|
|
308
|
+
|
|
309
|
+
register.add_endpoint(
|
|
310
|
+
id: :error_test,
|
|
311
|
+
type: :index,
|
|
312
|
+
url: "/error-test",
|
|
313
|
+
model: MockW3cApi::MockModel,
|
|
314
|
+
parameters: []
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
# Should fall back to direct API call
|
|
318
|
+
expect { register.fetch(:error_test) }.not_to raise_error
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
it "handles missing cache gracefully" do
|
|
322
|
+
no_cache_register = Lutaml::Hal::ModelRegister.new(name: :no_cache, client: client)
|
|
323
|
+
|
|
324
|
+
no_cache_register.add_endpoint(
|
|
325
|
+
id: :no_cache_test,
|
|
326
|
+
type: :index,
|
|
327
|
+
url: "/no-cache-test",
|
|
328
|
+
model: MockW3cApi::MockModel,
|
|
329
|
+
parameters: []
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
# Should work without cache
|
|
333
|
+
expect { no_cache_register.fetch(:no_cache_test) }.not_to raise_error
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe Lutaml::Store, "custom serializer" do
|
|
6
|
+
let(:custom_serializer) do
|
|
7
|
+
Class.new do
|
|
8
|
+
def serialize(model)
|
|
9
|
+
{ "identifier" => model.identifier, "value" => model.value }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def deserialize(data, _model_class)
|
|
13
|
+
model = TestCustomModel.new
|
|
14
|
+
model.identifier = data["identifier"]
|
|
15
|
+
model.value = data["value"]
|
|
16
|
+
model
|
|
17
|
+
end
|
|
18
|
+
end.new
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
before do
|
|
22
|
+
stub_const("TestCustomModel", Class.new(Lutaml::Model::Serializable) do
|
|
23
|
+
attribute :identifier, :string
|
|
24
|
+
attribute :value, :string
|
|
25
|
+
attribute :computed, :string
|
|
26
|
+
|
|
27
|
+
def computed
|
|
28
|
+
"#{identifier}-#{value}"
|
|
29
|
+
end
|
|
30
|
+
end)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "uses custom serializer for save and fetch" do
|
|
34
|
+
store = described_class.new(
|
|
35
|
+
adapter: :memory,
|
|
36
|
+
models: [
|
|
37
|
+
{ model: TestCustomModel, key: :identifier, serializer: custom_serializer }
|
|
38
|
+
]
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
model = TestCustomModel.new
|
|
42
|
+
model.identifier = "abc"
|
|
43
|
+
model.value = "hello"
|
|
44
|
+
|
|
45
|
+
store.save(model)
|
|
46
|
+
|
|
47
|
+
fetched = store.fetch(model: TestCustomModel, identifier: "abc")
|
|
48
|
+
expect(fetched.identifier).to eq("abc")
|
|
49
|
+
expect(fetched.value).to eq("hello")
|
|
50
|
+
expect(fetched.computed).to eq("abc-hello")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "uses custom serializer for all" do
|
|
54
|
+
store = described_class.new(
|
|
55
|
+
adapter: :memory,
|
|
56
|
+
models: [
|
|
57
|
+
{ model: TestCustomModel, key: :identifier, serializer: custom_serializer }
|
|
58
|
+
]
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
3.times do |i|
|
|
62
|
+
model = TestCustomModel.new
|
|
63
|
+
model.identifier = "item-#{i}"
|
|
64
|
+
model.value = "val-#{i}"
|
|
65
|
+
store.save(model)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
all_items = store.all(model: TestCustomModel)
|
|
69
|
+
expect(all_items.size).to eq(3)
|
|
70
|
+
expect(all_items.map(&:value).sort).to eq(%w[val-0 val-1 val-2])
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "uses custom serializer for update" do
|
|
74
|
+
store = described_class.new(
|
|
75
|
+
adapter: :memory,
|
|
76
|
+
models: [
|
|
77
|
+
{ model: TestCustomModel, key: :identifier, serializer: custom_serializer }
|
|
78
|
+
]
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
model = TestCustomModel.new
|
|
82
|
+
model.identifier = "xyz"
|
|
83
|
+
model.value = "original"
|
|
84
|
+
store.save(model)
|
|
85
|
+
|
|
86
|
+
updated = store.update(
|
|
87
|
+
model: TestCustomModel,
|
|
88
|
+
identifier: "xyz",
|
|
89
|
+
attributes: { "value" => "updated" }
|
|
90
|
+
)
|
|
91
|
+
expect(updated.value).to eq("updated")
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it "falls back to default serialization when no custom serializer" do
|
|
95
|
+
store = described_class.new(
|
|
96
|
+
adapter: :memory,
|
|
97
|
+
models: [{ model: TestCustomModel, key: :identifier }]
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
model = TestCustomModel.new
|
|
101
|
+
model.identifier = "default"
|
|
102
|
+
model.value = "normal"
|
|
103
|
+
store.save(model)
|
|
104
|
+
|
|
105
|
+
fetched = store.fetch(model: TestCustomModel, identifier: "default")
|
|
106
|
+
expect(fetched.identifier).to eq("default")
|
|
107
|
+
end
|
|
108
|
+
end
|