familia 2.0.0.pre21 → 2.0.0.pre22
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 +4 -4
- data/.talismanrc +5 -1
- data/CHANGELOG.rst +43 -0
- data/Gemfile.lock +1 -1
- data/lib/familia/connection/operation_core.rb +1 -2
- data/lib/familia/connection/pipelined_core.rb +1 -3
- data/lib/familia/connection/transaction_core.rb +1 -2
- data/lib/familia/data_type/serialization.rb +76 -51
- data/lib/familia/data_type/types/sorted_set.rb +5 -10
- data/lib/familia/data_type/types/stringkey.rb +22 -0
- data/lib/familia/features/external_identifier.rb +29 -0
- data/lib/familia/features/object_identifier.rb +47 -0
- data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +15 -15
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +8 -0
- data/lib/familia/horreum/database_commands.rb +6 -1
- data/lib/familia/horreum/management.rb +141 -10
- data/lib/familia/horreum/persistence.rb +3 -0
- data/lib/familia/identifier_extractor.rb +1 -1
- data/lib/familia/version.rb +1 -1
- data/lib/multi_result.rb +59 -31
- data/try/features/count_any_edge_cases_try.rb +486 -0
- data/try/features/count_any_methods_try.rb +197 -0
- data/try/features/external_identifier/external_identifier_try.rb +134 -0
- data/try/features/object_identifier/object_identifier_try.rb +138 -0
- data/try/features/relationships/indexing_rebuild_try.rb +6 -0
- data/try/integration/data_types/datatype_pipelines_try.rb +5 -3
- data/try/integration/data_types/datatype_transactions_try.rb +13 -7
- data/try/integration/models/customer_try.rb +3 -3
- data/try/unit/data_types/boolean_try.rb +35 -22
- data/try/unit/data_types/hash_try.rb +2 -2
- data/try/unit/data_types/serialization_try.rb +386 -0
- data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +2 -1
- metadata +4 -7
- data/changelog.d/20251105_flexible_external_identifier_format.rst +0 -66
- data/changelog.d/20251107_112554_delano_179_participation_asymmetry.rst +0 -44
- data/changelog.d/20251107_213121_delano_fix_thread_safety_races_011CUumCP492Twxm4NLt2FvL.rst +0 -20
- data/changelog.d/20251107_fix_participates_in_symbol_resolution.rst +0 -91
- data/changelog.d/20251107_optimized_redis_exists_checks.rst +0 -94
- data/changelog.d/20251108_frozen_string_literal_pragma.rst +0 -44
|
@@ -387,3 +387,137 @@ found_no_prefix&.id
|
|
|
387
387
|
findable_custom.destroy! rescue nil
|
|
388
388
|
findable_hyphen.destroy! rescue nil
|
|
389
389
|
findable_no_prefix.destroy! rescue nil
|
|
390
|
+
|
|
391
|
+
# ========================================
|
|
392
|
+
# extid? Method Test Coverage
|
|
393
|
+
# ========================================
|
|
394
|
+
|
|
395
|
+
## extid? returns false for nil
|
|
396
|
+
ExternalIdTest.extid?(nil)
|
|
397
|
+
#=> false
|
|
398
|
+
|
|
399
|
+
## extid? returns false for empty string
|
|
400
|
+
ExternalIdTest.extid?('')
|
|
401
|
+
#=> false
|
|
402
|
+
|
|
403
|
+
## extid? returns false for whitespace-only string
|
|
404
|
+
ExternalIdTest.extid?(' ')
|
|
405
|
+
#=> false
|
|
406
|
+
|
|
407
|
+
## extid? returns true for valid extid with default format
|
|
408
|
+
# Valid format: ext_ prefix + exactly 25 alphanumeric characters
|
|
409
|
+
ExternalIdTest.extid?('ext_0123456789abcdefghijklmno')
|
|
410
|
+
#=> true
|
|
411
|
+
|
|
412
|
+
## extid? returns true for valid extid with uppercase characters
|
|
413
|
+
# The pattern allows [0-9a-zA-Z] for the ID part
|
|
414
|
+
ExternalIdTest.extid?('ext_0123456789ABCDEFGHIJKLMNO')
|
|
415
|
+
#=> true
|
|
416
|
+
|
|
417
|
+
## extid? returns true for valid extid with mixed case
|
|
418
|
+
ExternalIdTest.extid?('ext_0123456789AbCdEfGhIjKlMnO')
|
|
419
|
+
#=> true
|
|
420
|
+
|
|
421
|
+
## extid? returns true for generated extid
|
|
422
|
+
obj = ExternalIdTest.new
|
|
423
|
+
ExternalIdTest.extid?(obj.extid)
|
|
424
|
+
#=> true
|
|
425
|
+
|
|
426
|
+
## extid? returns false for wrong prefix
|
|
427
|
+
ExternalIdTest.extid?('xxx_0123456789abcdefghijklmno')
|
|
428
|
+
#=> false
|
|
429
|
+
|
|
430
|
+
## extid? returns false for missing prefix
|
|
431
|
+
ExternalIdTest.extid?('0123456789abcdefghijklmno')
|
|
432
|
+
#=> false
|
|
433
|
+
|
|
434
|
+
## extid? accepts ID part with 24 chars (within 20-32 range)
|
|
435
|
+
ExternalIdTest.extid?('ext_0123456789abcdefghijklmn')
|
|
436
|
+
#=> true
|
|
437
|
+
|
|
438
|
+
## extid? accepts ID part with 26 chars (within 20-32 range)
|
|
439
|
+
ExternalIdTest.extid?('ext_0123456789abcdefghijklmnop')
|
|
440
|
+
#=> true
|
|
441
|
+
|
|
442
|
+
## extid? returns false for ID part too short (19 chars, below minimum)
|
|
443
|
+
ExternalIdTest.extid?('ext_0123456789abcdefghi')
|
|
444
|
+
#=> false
|
|
445
|
+
|
|
446
|
+
## extid? returns false for ID part too long (33 chars, above maximum)
|
|
447
|
+
ExternalIdTest.extid?('ext_0123456789abcdefghijklmnopqrstuvw')
|
|
448
|
+
#=> false
|
|
449
|
+
|
|
450
|
+
## extid? returns false for invalid characters in ID (underscore)
|
|
451
|
+
ExternalIdTest.extid?('ext_0123456789abcdefghij_lmno')
|
|
452
|
+
#=> false
|
|
453
|
+
|
|
454
|
+
## extid? returns false for invalid characters in ID (hyphen)
|
|
455
|
+
ExternalIdTest.extid?('ext_0123456789abcdefghij-lmno')
|
|
456
|
+
#=> false
|
|
457
|
+
|
|
458
|
+
## extid? returns false for invalid characters in ID (special chars)
|
|
459
|
+
ExternalIdTest.extid?('ext_0123456789abcdefghij!@#$%')
|
|
460
|
+
#=> false
|
|
461
|
+
|
|
462
|
+
## extid? with custom prefix format returns true for valid extid
|
|
463
|
+
CustomPrefixTest.extid?('cust_0123456789abcdefghijklmno')
|
|
464
|
+
#=> true
|
|
465
|
+
|
|
466
|
+
## extid? with custom prefix format returns false for default prefix
|
|
467
|
+
CustomPrefixTest.extid?('ext_0123456789abcdefghijklmno')
|
|
468
|
+
#=> false
|
|
469
|
+
|
|
470
|
+
## extid? with custom prefix format returns true for generated extid
|
|
471
|
+
custom_obj = CustomPrefixTest.new
|
|
472
|
+
CustomPrefixTest.extid?(custom_obj.extid)
|
|
473
|
+
#=> true
|
|
474
|
+
|
|
475
|
+
## extid? with hyphen format returns true for valid extid
|
|
476
|
+
CustomFormatTest.extid?('ext-0123456789abcdefghijklmno')
|
|
477
|
+
#=> true
|
|
478
|
+
|
|
479
|
+
## extid? with hyphen format returns false for underscore format
|
|
480
|
+
CustomFormatTest.extid?('ext_0123456789abcdefghijklmno')
|
|
481
|
+
#=> false
|
|
482
|
+
|
|
483
|
+
## extid? with hyphen format returns true for generated extid
|
|
484
|
+
hyphen_obj = CustomFormatTest.new
|
|
485
|
+
CustomFormatTest.extid?(hyphen_obj.extid)
|
|
486
|
+
#=> true
|
|
487
|
+
|
|
488
|
+
## extid? with no-prefix format returns true for valid extid
|
|
489
|
+
NoPrefixFormatTest.extid?('api/0123456789abcdefghijklmno')
|
|
490
|
+
#=> true
|
|
491
|
+
|
|
492
|
+
## extid? with no-prefix format returns false for default format
|
|
493
|
+
NoPrefixFormatTest.extid?('ext_0123456789abcdefghijklmno')
|
|
494
|
+
#=> false
|
|
495
|
+
|
|
496
|
+
## extid? with no-prefix format returns true for generated extid
|
|
497
|
+
no_prefix_obj = NoPrefixFormatTest.new
|
|
498
|
+
NoPrefixFormatTest.extid?(no_prefix_obj.extid)
|
|
499
|
+
#=> true
|
|
500
|
+
|
|
501
|
+
## extid? returns false for partial match at start
|
|
502
|
+
ExternalIdTest.extid?('ext_0123456789abcdefghijklmno_extra')
|
|
503
|
+
#=> false
|
|
504
|
+
|
|
505
|
+
## extid? returns false for partial match with leading chars
|
|
506
|
+
ExternalIdTest.extid?('prefix_ext_0123456789abcdefghijklmno')
|
|
507
|
+
#=> false
|
|
508
|
+
|
|
509
|
+
## extid? returns false for spaces in ID
|
|
510
|
+
ExternalIdTest.extid?('ext_0123456789 bcdefghijklmno')
|
|
511
|
+
#=> false
|
|
512
|
+
|
|
513
|
+
## extid? returns false for just the prefix
|
|
514
|
+
ExternalIdTest.extid?('ext_')
|
|
515
|
+
#=> false
|
|
516
|
+
|
|
517
|
+
## extid? with Symbol input (symbols support =~ matching)
|
|
518
|
+
ExternalIdTest.extid?(:ext_0123456789abcdefghijklmno)
|
|
519
|
+
#=> true
|
|
520
|
+
|
|
521
|
+
## extid? with Symbol input returns false for invalid format
|
|
522
|
+
ExternalIdTest.extid?(:invalid_symbol)
|
|
523
|
+
#=> false
|
|
@@ -201,3 +201,141 @@ race_obj.save # Save so find_by_objid can locate
|
|
|
201
201
|
found = BasicObjectTest.find_by_objid(generated_objid)
|
|
202
202
|
found && found.id == "race_test_123"
|
|
203
203
|
#=> true
|
|
204
|
+
|
|
205
|
+
# ========================================
|
|
206
|
+
# objid? Method Test Coverage
|
|
207
|
+
# ========================================
|
|
208
|
+
|
|
209
|
+
## objid? returns false for nil
|
|
210
|
+
BasicObjectTest.objid?(nil)
|
|
211
|
+
#=> false
|
|
212
|
+
|
|
213
|
+
## objid? returns false for empty string
|
|
214
|
+
BasicObjectTest.objid?('')
|
|
215
|
+
#=> false
|
|
216
|
+
|
|
217
|
+
## objid? returns false for whitespace-only string
|
|
218
|
+
BasicObjectTest.objid?(' ')
|
|
219
|
+
#=> false
|
|
220
|
+
|
|
221
|
+
## objid? returns true for valid UUID v7 format
|
|
222
|
+
# UUID v7 format: xxxxxxxx-xxxx-7xxx-xxxx-xxxxxxxxxxxx (version char is '7')
|
|
223
|
+
BasicObjectTest.objid?('01234567-89ab-7def-89ab-0123456789ab')
|
|
224
|
+
#=> true
|
|
225
|
+
|
|
226
|
+
## objid? returns true for generated UUID v7 objid
|
|
227
|
+
obj = BasicObjectTest.new
|
|
228
|
+
BasicObjectTest.objid?(obj.objid)
|
|
229
|
+
#=> true
|
|
230
|
+
|
|
231
|
+
## objid? returns false for UUID v4 in UUID v7 class
|
|
232
|
+
# Version char is '4' instead of '7'
|
|
233
|
+
BasicObjectTest.objid?('01234567-89ab-4def-89ab-0123456789ab')
|
|
234
|
+
#=> false
|
|
235
|
+
|
|
236
|
+
## objid? returns false for wrong version char
|
|
237
|
+
BasicObjectTest.objid?('01234567-89ab-5def-89ab-0123456789ab')
|
|
238
|
+
#=> false
|
|
239
|
+
|
|
240
|
+
## objid? returns false for missing hyphens
|
|
241
|
+
BasicObjectTest.objid?('0123456789ab7def89ab0123456789ab')
|
|
242
|
+
#=> false
|
|
243
|
+
|
|
244
|
+
## objid? returns false for wrong length (too short)
|
|
245
|
+
BasicObjectTest.objid?('01234567-89ab-7def-89ab-012345678')
|
|
246
|
+
#=> false
|
|
247
|
+
|
|
248
|
+
## objid? returns false for wrong length (too long)
|
|
249
|
+
BasicObjectTest.objid?('01234567-89ab-7def-89ab-0123456789abcd')
|
|
250
|
+
#=> false
|
|
251
|
+
|
|
252
|
+
## objid? returns false for hyphens in wrong positions
|
|
253
|
+
BasicObjectTest.objid?('0123456-789ab-7def-89ab-0123456789ab')
|
|
254
|
+
#=> false
|
|
255
|
+
|
|
256
|
+
## objid? returns false for UUID with non-hex characters
|
|
257
|
+
# Validates that all characters are valid hexadecimal digits
|
|
258
|
+
BasicObjectTest.objid?('gggggggg-gggg-7ggg-gggg-gggggggggggg')
|
|
259
|
+
#=> false
|
|
260
|
+
|
|
261
|
+
## objid? with UUID v4 generator returns true for valid v4 format
|
|
262
|
+
UuidV4Test.objid?('01234567-89ab-4def-89ab-0123456789ab')
|
|
263
|
+
#=> true
|
|
264
|
+
|
|
265
|
+
## objid? with UUID v4 generator returns true for generated objid
|
|
266
|
+
v4_obj = UuidV4Test.new
|
|
267
|
+
UuidV4Test.objid?(v4_obj.objid)
|
|
268
|
+
#=> true
|
|
269
|
+
|
|
270
|
+
## objid? with UUID v4 generator returns false for v7 format
|
|
271
|
+
UuidV4Test.objid?('01234567-89ab-7def-89ab-0123456789ab')
|
|
272
|
+
#=> false
|
|
273
|
+
|
|
274
|
+
## objid? with hex generator returns true for valid hex string
|
|
275
|
+
HexTest.objid?('0123456789abcdef')
|
|
276
|
+
#=> true
|
|
277
|
+
|
|
278
|
+
## objid? with hex generator returns true for generated hex objid
|
|
279
|
+
hex_obj = HexTest.new
|
|
280
|
+
HexTest.objid?(hex_obj.objid)
|
|
281
|
+
#=> true
|
|
282
|
+
|
|
283
|
+
## objid? with hex generator returns true for uppercase hex
|
|
284
|
+
HexTest.objid?('0123456789ABCDEF')
|
|
285
|
+
#=> true
|
|
286
|
+
|
|
287
|
+
## objid? with hex generator returns true for mixed case hex
|
|
288
|
+
HexTest.objid?('0123456789AbCdEf')
|
|
289
|
+
#=> true
|
|
290
|
+
|
|
291
|
+
## objid? with hex generator returns false for non-hex chars
|
|
292
|
+
HexTest.objid?('0123456789ghijkl')
|
|
293
|
+
#=> false
|
|
294
|
+
|
|
295
|
+
## objid? with hex generator returns false for UUID format
|
|
296
|
+
HexTest.objid?('01234567-89ab-7def-89ab-0123456789ab')
|
|
297
|
+
#=> false
|
|
298
|
+
|
|
299
|
+
## objid? with hex generator returns true for short hex
|
|
300
|
+
HexTest.objid?('abc')
|
|
301
|
+
#=> true
|
|
302
|
+
|
|
303
|
+
## objid? with hex generator returns true for long hex
|
|
304
|
+
HexTest.objid?('0123456789abcdef0123456789abcdef')
|
|
305
|
+
#=> true
|
|
306
|
+
|
|
307
|
+
## objid? with custom proc generator returns false (unsupported)
|
|
308
|
+
CustomProcTest.objid?('custom_12345678')
|
|
309
|
+
#=> false
|
|
310
|
+
|
|
311
|
+
## objid? with custom proc generator returns false for any input
|
|
312
|
+
CustomProcTest.objid?('anything')
|
|
313
|
+
#=> false
|
|
314
|
+
|
|
315
|
+
## objid? UUID validation checks position 8 for hyphen
|
|
316
|
+
BasicObjectTest.objid?('01234567X89ab-7def-89ab-0123456789ab')
|
|
317
|
+
#=> false
|
|
318
|
+
|
|
319
|
+
## objid? UUID validation checks position 13 for hyphen
|
|
320
|
+
BasicObjectTest.objid?('01234567-89abX7def-89ab-0123456789ab')
|
|
321
|
+
#=> false
|
|
322
|
+
|
|
323
|
+
## objid? UUID validation checks position 18 for hyphen
|
|
324
|
+
BasicObjectTest.objid?('01234567-89ab-7defX89ab-0123456789ab')
|
|
325
|
+
#=> false
|
|
326
|
+
|
|
327
|
+
## objid? UUID validation checks position 23 for hyphen
|
|
328
|
+
BasicObjectTest.objid?('01234567-89ab-7def-89abX0123456789ab')
|
|
329
|
+
#=> false
|
|
330
|
+
|
|
331
|
+
## objid? returns false for partial UUID match
|
|
332
|
+
BasicObjectTest.objid?('01234567-89ab-7def')
|
|
333
|
+
#=> false
|
|
334
|
+
|
|
335
|
+
## objid? with Symbol input for UUID v7 (symbols support string comparison)
|
|
336
|
+
BasicObjectTest.objid?(:'01234567-89ab-7def-89ab-0123456789ab')
|
|
337
|
+
#=> true
|
|
338
|
+
|
|
339
|
+
## objid? with Symbol input returns false for invalid format
|
|
340
|
+
BasicObjectTest.objid?(:invalid_objid)
|
|
341
|
+
#=> false
|
|
@@ -553,6 +553,9 @@ found3 = @company.find_by_badge_number("BADGE003")
|
|
|
553
553
|
# The architecture prevents this via factory pattern, but guard provides explicit protection
|
|
554
554
|
begin
|
|
555
555
|
# Simulate calling rebuild_via_participation with multi-index cardinality
|
|
556
|
+
# Note: dept_index is a multi-index, so we need to use dept_index_for to get a DataType instance
|
|
557
|
+
# But for this test, we just need any HashKey-like object to pass for serialization
|
|
558
|
+
index_hashkey = @company.badge_index # Use a valid HashKey index
|
|
556
559
|
Familia::Features::Relationships::Indexing::RebuildStrategies.rebuild_via_participation(
|
|
557
560
|
@company,
|
|
558
561
|
RebuildTestEmployee,
|
|
@@ -560,6 +563,7 @@ begin
|
|
|
560
563
|
:add_to_rebuild_test_company_dept_index,
|
|
561
564
|
@company.employees,
|
|
562
565
|
:multi, # Wrong cardinality!
|
|
566
|
+
index_hashkey, # Added required parameter
|
|
563
567
|
batch_size: 100
|
|
564
568
|
)
|
|
565
569
|
"should have raised"
|
|
@@ -571,6 +575,7 @@ end
|
|
|
571
575
|
## Guard accepts correct cardinality (:unique)
|
|
572
576
|
begin
|
|
573
577
|
index_config = RebuildTestEmployee.indexing_relationships.find { |r| r.index_name == :badge_index }
|
|
578
|
+
index_hashkey = @company.badge_index # Get the index HashKey for serialization
|
|
574
579
|
Familia::Features::Relationships::Indexing::RebuildStrategies.rebuild_via_participation(
|
|
575
580
|
@company,
|
|
576
581
|
RebuildTestEmployee,
|
|
@@ -578,6 +583,7 @@ begin
|
|
|
578
583
|
:add_to_rebuild_test_company_badge_index,
|
|
579
584
|
@company.employees,
|
|
580
585
|
:unique, # Correct cardinality
|
|
586
|
+
index_hashkey, # Added required parameter
|
|
581
587
|
batch_size: 100
|
|
582
588
|
)
|
|
583
589
|
"no error"
|
|
@@ -81,10 +81,12 @@ result = @user.scores.pipelined { |pipe| }
|
|
|
81
81
|
#=> [true, true]
|
|
82
82
|
|
|
83
83
|
## Multiple DataType operations in single pipeline
|
|
84
|
+
# Note: Raw Redis commands bypass Familia's JSON serialization.
|
|
85
|
+
# Use serialize_value for values that will be looked up via Familia methods.
|
|
84
86
|
result = @user.scores.pipelined do |pipe|
|
|
85
|
-
pipe.zadd(@user.scores.dbkey, 500, 'multi')
|
|
86
|
-
pipe.hset(@user.profile.dbkey, 'multi', 'pipeline')
|
|
87
|
-
pipe.sadd(@user.tags.dbkey, 'multi_tag')
|
|
87
|
+
pipe.zadd(@user.scores.dbkey, 500, @user.scores.serialize_value('multi'))
|
|
88
|
+
pipe.hset(@user.profile.dbkey, 'multi', @user.profile.serialize_value('pipeline'))
|
|
89
|
+
pipe.sadd(@user.tags.dbkey, @user.tags.serialize_value('multi_tag'))
|
|
88
90
|
end
|
|
89
91
|
[
|
|
90
92
|
result.is_a?(MultiResult),
|
|
@@ -152,6 +152,8 @@ conn_class
|
|
|
152
152
|
#=> "Redis::MultiConnection"
|
|
153
153
|
|
|
154
154
|
## Transaction with direct_access works correctly
|
|
155
|
+
# Note: direct_access bypasses serialize_value, so raw 'true' string
|
|
156
|
+
# gets parsed as JSON boolean true on retrieval (Issue #190 behavior)
|
|
155
157
|
result = @user.profile.transaction do |trans_conn|
|
|
156
158
|
trans_conn.hset(@user.profile.dbkey, 'status', 'active')
|
|
157
159
|
|
|
@@ -162,7 +164,7 @@ result = @user.profile.transaction do |trans_conn|
|
|
|
162
164
|
end
|
|
163
165
|
end
|
|
164
166
|
[@user.profile['status'], @user.profile['verified']]
|
|
165
|
-
#=> ["active",
|
|
167
|
+
#=> ["active", true]
|
|
166
168
|
|
|
167
169
|
## Transaction atomicity - all commands succeed or none
|
|
168
170
|
test_zset = Familia::SortedSet.new('atomic:test')
|
|
@@ -182,11 +184,13 @@ end
|
|
|
182
184
|
#=> ["initial"]
|
|
183
185
|
|
|
184
186
|
## Nested transactions with parent-owned DataTypes work
|
|
187
|
+
# Note: Raw Redis commands bypass Familia's JSON serialization.
|
|
188
|
+
# Use serialize_value or check raw members for consistency.
|
|
185
189
|
outer_result = @user.scores.transaction do |outer_conn|
|
|
186
|
-
outer_conn.zadd(@user.scores.dbkey, 999, 'outer_member')
|
|
190
|
+
outer_conn.zadd(@user.scores.dbkey, 999, @user.scores.serialize_value('outer_member'))
|
|
187
191
|
|
|
188
192
|
inner_result = @user.tags.transaction do |inner_conn|
|
|
189
|
-
inner_conn.sadd(@user.tags.dbkey, 'nested_tag')
|
|
193
|
+
inner_conn.sadd(@user.tags.dbkey, @user.tags.serialize_value('nested_tag'))
|
|
190
194
|
end
|
|
191
195
|
|
|
192
196
|
inner_result.is_a?(MultiResult)
|
|
@@ -231,12 +235,14 @@ TransactionTestUser.logical_database
|
|
|
231
235
|
#=> 2
|
|
232
236
|
|
|
233
237
|
## Multiple DataType types in single transaction
|
|
238
|
+
# Note: Raw Redis commands bypass Familia's JSON serialization.
|
|
239
|
+
# Use serialize_value for values that will be looked up via Familia methods.
|
|
234
240
|
result = @user.scores.transaction do |conn|
|
|
235
241
|
# Can operate on different DataTypes using same connection
|
|
236
|
-
conn.zadd(@user.scores.dbkey, 777, 'multi_test')
|
|
237
|
-
conn.hset(@user.profile.dbkey, 'multi', 'yes')
|
|
238
|
-
conn.sadd(@user.tags.dbkey, 'multi_tag')
|
|
239
|
-
conn.rpush(@user.activity.dbkey, 'multi_action')
|
|
242
|
+
conn.zadd(@user.scores.dbkey, 777, @user.scores.serialize_value('multi_test'))
|
|
243
|
+
conn.hset(@user.profile.dbkey, 'multi', @user.profile.serialize_value('yes'))
|
|
244
|
+
conn.sadd(@user.tags.dbkey, @user.tags.serialize_value('multi_tag'))
|
|
245
|
+
conn.rpush(@user.activity.dbkey, @user.activity.serialize_value('multi_action'))
|
|
240
246
|
end
|
|
241
247
|
[
|
|
242
248
|
result.is_a?(MultiResult),
|
|
@@ -73,7 +73,8 @@ Customer.find_by_id(ident).planid
|
|
|
73
73
|
#=> true
|
|
74
74
|
|
|
75
75
|
## Customer can be added to class-level sorted set
|
|
76
|
-
|
|
76
|
+
# Note: Add the object directly so identifier extraction is consistent
|
|
77
|
+
Customer.instances.add(@customer)
|
|
77
78
|
Customer.instances.member?(@customer)
|
|
78
79
|
#=> true
|
|
79
80
|
|
|
@@ -97,11 +98,10 @@ multi_result = @customer.destroy!
|
|
|
97
98
|
cust = Customer.find_by_id('test@example.com')
|
|
98
99
|
exists = Customer.exists?('test@example.com')
|
|
99
100
|
[multi_result.results, cust.nil?, exists]
|
|
100
|
-
#=> [[1, 0, 1, 1, 1, 1, 1], true, false]
|
|
101
|
+
#=> [[1, 0, 1, 1, 1, 1, 1, true], true, false]
|
|
101
102
|
|
|
102
103
|
## Customer.destroy! can be called on an already destroyed object
|
|
103
104
|
@customer.destroy!
|
|
104
|
-
#=:> MultiResult
|
|
105
105
|
#==> result.successful?
|
|
106
106
|
#=*> result.results
|
|
107
107
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# frozen_string_literal: true
|
|
4
4
|
|
|
5
5
|
# try/data_types/boolean_try.rb
|
|
6
|
+
# Issue #190: Updated to reflect JSON serialization with type preservation
|
|
6
7
|
|
|
7
8
|
require_relative '../../support/helpers/test_helpers'
|
|
8
9
|
|
|
@@ -10,37 +11,49 @@ Familia.debug = false
|
|
|
10
11
|
|
|
11
12
|
@hashkey = Familia::HashKey.new 'key'
|
|
12
13
|
|
|
13
|
-
##
|
|
14
|
+
## String 'true' is stored and returned as string
|
|
14
15
|
@hashkey['test'] = 'true'
|
|
15
16
|
#=> "true"
|
|
16
17
|
|
|
17
|
-
##
|
|
18
|
+
## String values are returned as strings
|
|
18
19
|
@hashkey['test']
|
|
19
20
|
#=> "true"
|
|
20
21
|
|
|
21
|
-
##
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
rescue Familia::NotDistinguishableError => e
|
|
25
|
-
e.message
|
|
26
|
-
end
|
|
27
|
-
#=> "Cannot represent true<TrueClass> as a string"
|
|
22
|
+
## Boolean true is now stored with type preservation (Issue #190)
|
|
23
|
+
@hashkey['bool_true'] = true
|
|
24
|
+
#=> true
|
|
28
25
|
|
|
29
|
-
## Boolean
|
|
30
|
-
@hashkey['
|
|
31
|
-
#=>
|
|
26
|
+
## Boolean true is returned as TrueClass
|
|
27
|
+
@hashkey['bool_true']
|
|
28
|
+
#=> true
|
|
32
29
|
|
|
33
|
-
##
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
rescue Familia::NotDistinguishableError => e
|
|
37
|
-
e.message
|
|
38
|
-
end
|
|
39
|
-
#=> "Cannot represent <NilClass> as a string"
|
|
30
|
+
## Boolean true has correct class
|
|
31
|
+
@hashkey['bool_true'].class
|
|
32
|
+
#=> TrueClass
|
|
40
33
|
|
|
41
|
-
##
|
|
42
|
-
@hashkey['
|
|
43
|
-
#=>
|
|
34
|
+
## Boolean false is stored with type preservation
|
|
35
|
+
@hashkey['bool_false'] = false
|
|
36
|
+
#=> false
|
|
37
|
+
|
|
38
|
+
## Boolean false is returned as FalseClass
|
|
39
|
+
@hashkey['bool_false']
|
|
40
|
+
#=> false
|
|
41
|
+
|
|
42
|
+
## Boolean false has correct class
|
|
43
|
+
@hashkey['bool_false'].class
|
|
44
|
+
#=> FalseClass
|
|
45
|
+
|
|
46
|
+
## nil is stored with type preservation
|
|
47
|
+
@hashkey['nil_value'] = nil
|
|
48
|
+
#=> nil
|
|
49
|
+
|
|
50
|
+
## nil is returned as nil
|
|
51
|
+
@hashkey['nil_value']
|
|
52
|
+
#=> nil
|
|
53
|
+
|
|
54
|
+
## nil has correct class
|
|
55
|
+
@hashkey['nil_value'].class
|
|
56
|
+
#=> NilClass
|
|
44
57
|
|
|
45
58
|
## Clear the hash key
|
|
46
59
|
@hashkey.delete!
|
|
@@ -50,8 +50,8 @@ require_relative '../../support/helpers/test_helpers'
|
|
|
50
50
|
@a.props.decrement 'counter', 60
|
|
51
51
|
#=> 40
|
|
52
52
|
|
|
53
|
-
## Familia::HashKey#values_at
|
|
53
|
+
## Familia::HashKey#values_at (counter is integer from HINCRBY, others are strings)
|
|
54
54
|
@a.props.values_at 'fieldA', 'counter', 'fieldC'
|
|
55
|
-
#=> ['1',
|
|
55
|
+
#=> ['1', 40, '3']
|
|
56
56
|
|
|
57
57
|
@a.props.delete!
|