familia 2.1.0 → 2.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 +4 -4
- data/CHANGELOG.rst +37 -0
- data/Gemfile.lock +3 -3
- data/examples/relationships.rb +1 -1
- data/familia.gemspec +1 -1
- data/lib/familia/features/relationships/collection_operations.rb +7 -7
- data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +1 -1
- data/lib/familia/features/relationships/participation/participant_methods.rb +5 -3
- data/lib/familia/features/relationships/participation.rb +6 -2
- data/lib/familia/version.rb +1 -1
- data/try/features/relationships/participation_reverse_methods_try.rb +2 -1
- data/try/features/relationships/participation_target_class_resolution_try.rb +5 -4
- data/try/features/relationships/relationships_api_changes_try.rb +7 -5
- data/try/features/relationships/serialization_consistency_try.rb +292 -0
- metadata +13 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 923e4c76b9ee95ca39160a66ec9534462b21b16e273fc27c6e67e707647da1a4
|
|
4
|
+
data.tar.gz: c8dd58304412dd4d9ad57727a4044d8680c16f701869e3f8dd7d0e2a57ed221a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0716f0b0e47a4758933547b884da5265ce186ce3eba55def7672e2540823b8f15671791ed7b2906f6862d863faa273436fb9e52434cec0e10a91a9bd7614ae9e
|
|
7
|
+
data.tar.gz: 6e018ee75bb16fa2e1be7a7e812dbcd8d8ca9e8a3c82fe078ef19958e3736f01f8c5f34b925a9fc6a0fd3d7ca709604571a5915ee2b828f2a67539e61ac401a2
|
data/CHANGELOG.rst
CHANGED
|
@@ -7,6 +7,43 @@ The format is based on `Keep a Changelog <https://keepachangelog.com/en/1.1.0/>`
|
|
|
7
7
|
|
|
8
8
|
<!--scriv-insert-here-->
|
|
9
9
|
|
|
10
|
+
.. _changelog-2.1.1:
|
|
11
|
+
|
|
12
|
+
2.1.1 — 2026-02-02
|
|
13
|
+
==================
|
|
14
|
+
|
|
15
|
+
Added
|
|
16
|
+
-----
|
|
17
|
+
|
|
18
|
+
- Added ``serialization_consistency_try.rb`` regression tests verifying that
|
|
19
|
+
object-based lookups work consistently across relationships module and direct
|
|
20
|
+
DataType access for sorted sets, unsorted sets, and lists.
|
|
21
|
+
|
|
22
|
+
Fixed
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
- Fixed serialization mismatch in relationships module where extracting
|
|
26
|
+
``.identifier`` before passing to DataType methods caused cross-path lookup
|
|
27
|
+
failures. Items added via relationships couldn't be found via direct DataType
|
|
28
|
+
access because ``serialize_value(object)`` extracts raw identifiers while
|
|
29
|
+
``serialize_value(string)`` JSON-encodes them. Now passes Familia objects
|
|
30
|
+
directly to DataType methods. (`#212 <https://github.com/delano/familia/issues/212>`_)
|
|
31
|
+
|
|
32
|
+
Documentation
|
|
33
|
+
-------------
|
|
34
|
+
|
|
35
|
+
- Documented known limitation: string identifier lookups get JSON-encoded by
|
|
36
|
+
design. Always use Familia objects instead of raw string identifiers for
|
|
37
|
+
DataType operations like ``member?()``, ``score()``, and ``remove()``.
|
|
38
|
+
|
|
39
|
+
AI Assistance
|
|
40
|
+
-------------
|
|
41
|
+
|
|
42
|
+
- Claude assisted with root cause analysis of the serialization mismatch,
|
|
43
|
+
identifying the 7 occurrences in ``collection_operations.rb`` where
|
|
44
|
+
``.identifier`` extraction needed to be removed, and writing comprehensive
|
|
45
|
+
regression tests covering all three collection types.
|
|
46
|
+
|
|
10
47
|
.. _changelog-2.1.0:
|
|
11
48
|
|
|
12
49
|
2.1.0 — 2026-02-01
|
data/Gemfile.lock
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
familia (2.1.
|
|
4
|
+
familia (2.1.1)
|
|
5
5
|
concurrent-ruby (~> 1.3)
|
|
6
|
-
connection_pool (
|
|
6
|
+
connection_pool (>= 2.4, < 4.0)
|
|
7
7
|
csv (~> 3.3)
|
|
8
8
|
logger (~> 1.7)
|
|
9
9
|
oj (~> 3.16)
|
|
@@ -19,7 +19,7 @@ GEM
|
|
|
19
19
|
benchmark (0.5.0)
|
|
20
20
|
bigdecimal (3.3.1)
|
|
21
21
|
concurrent-ruby (1.3.6)
|
|
22
|
-
connection_pool (
|
|
22
|
+
connection_pool (3.0.2)
|
|
23
23
|
csv (3.3.5)
|
|
24
24
|
date (3.5.0)
|
|
25
25
|
debug (1.11.0)
|
data/examples/relationships.rb
CHANGED
|
@@ -186,7 +186,7 @@ domain1.remove_from_customer_domains(customer)
|
|
|
186
186
|
puts "✓ Removed #{domain1.name} from customer domains"
|
|
187
187
|
|
|
188
188
|
# Remove from tracking collections
|
|
189
|
-
Domain.active_domains.remove(domain2
|
|
189
|
+
Domain.active_domains.remove(domain2)
|
|
190
190
|
puts "✓ Removed #{domain2.name} from active domains"
|
|
191
191
|
|
|
192
192
|
# Verify cleanup
|
data/familia.gemspec
CHANGED
|
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
|
|
|
20
20
|
spec.required_ruby_version = Gem::Requirement.new('>= 3.2')
|
|
21
21
|
|
|
22
22
|
spec.add_dependency 'concurrent-ruby', '~> 1.3'
|
|
23
|
-
spec.add_dependency 'connection_pool', '
|
|
23
|
+
spec.add_dependency 'connection_pool', '>= 2.4', '< 4.0'
|
|
24
24
|
spec.add_dependency 'csv', '~> 3.3'
|
|
25
25
|
spec.add_dependency 'logger', '~> 1.7'
|
|
26
26
|
spec.add_dependency 'oj', '~> 3.16'
|
|
@@ -31,13 +31,13 @@ module Familia
|
|
|
31
31
|
when :sorted_set
|
|
32
32
|
# Ensure score is never nil for sorted sets
|
|
33
33
|
score ||= calculate_item_score(item, target_class, collection_name)
|
|
34
|
-
collection.add(item
|
|
34
|
+
collection.add(item, score)
|
|
35
35
|
when :list
|
|
36
36
|
# Lists use push/unshift operations
|
|
37
|
-
collection.add(item
|
|
37
|
+
collection.add(item)
|
|
38
38
|
when :set
|
|
39
39
|
# Sets use simple add
|
|
40
|
-
collection.add(item
|
|
40
|
+
collection.add(item)
|
|
41
41
|
else
|
|
42
42
|
raise ArgumentError, "Unknown collection type: #{type}"
|
|
43
43
|
end
|
|
@@ -49,7 +49,7 @@ module Familia
|
|
|
49
49
|
# @param type [Symbol] Collection type
|
|
50
50
|
def remove_from_collection(collection, item, type: nil)
|
|
51
51
|
# All collection types support remove/delete
|
|
52
|
-
collection.remove(item
|
|
52
|
+
collection.remove(item)
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
# Check if an item is a member of a collection
|
|
@@ -57,7 +57,7 @@ module Familia
|
|
|
57
57
|
# @param item [Object] The item to check (must respond to identifier)
|
|
58
58
|
# @return [Boolean] True if item is in collection
|
|
59
59
|
def member_of_collection?(collection, item)
|
|
60
|
-
collection.member?(item
|
|
60
|
+
collection.member?(item)
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
# Bulk add items to a collection using DataType methods
|
|
@@ -72,12 +72,12 @@ module Familia
|
|
|
72
72
|
# Add items one by one for sorted sets to ensure proper scoring
|
|
73
73
|
items.each do |item|
|
|
74
74
|
score = calculate_item_score(item, target_class, collection_name)
|
|
75
|
-
collection.add(item
|
|
75
|
+
collection.add(item, score)
|
|
76
76
|
end
|
|
77
77
|
when :set, :list
|
|
78
78
|
# For sets and lists, add items one by one using DataType methods
|
|
79
79
|
items.each do |item|
|
|
80
|
-
collection.add(item
|
|
80
|
+
collection.add(item)
|
|
81
81
|
end
|
|
82
82
|
else
|
|
83
83
|
raise ArgumentError, "Unknown collection type: #{type}"
|
|
@@ -401,7 +401,7 @@ module Familia
|
|
|
401
401
|
collection_name = participation.collection_name
|
|
402
402
|
scope_collection = scope_instance.send(collection_name)
|
|
403
403
|
# Filter to only objects that belong to this scope
|
|
404
|
-
objects = objects.select { |obj| scope_collection.member?(obj
|
|
404
|
+
objects = objects.select { |obj| scope_collection.member?(obj) }
|
|
405
405
|
end
|
|
406
406
|
end
|
|
407
407
|
|
|
@@ -29,7 +29,7 @@ module Familia
|
|
|
29
29
|
# └── position_in_employee_domains(employee) # Get my position (list only)
|
|
30
30
|
#
|
|
31
31
|
# Note: To update scores, use the DataType API directly:
|
|
32
|
-
# employee.domains.add(domain
|
|
32
|
+
# employee.domains.add(domain, new_score, xx: true)
|
|
33
33
|
#
|
|
34
34
|
module Builder
|
|
35
35
|
extend CollectionOperations
|
|
@@ -296,7 +296,7 @@ module Familia
|
|
|
296
296
|
# Creates: domain.score_in_customer_domains(customer)
|
|
297
297
|
#
|
|
298
298
|
# Note: Score updates use DataType API directly:
|
|
299
|
-
# customer.domains.add(domain
|
|
299
|
+
# customer.domains.add(domain, new_score, xx: true)
|
|
300
300
|
def self.build_score_methods(participant_class, target_name, collection_name)
|
|
301
301
|
# Get score method
|
|
302
302
|
score_method = "score_in_#{target_name}_#{collection_name}"
|
|
@@ -305,7 +305,9 @@ module Familia
|
|
|
305
305
|
|
|
306
306
|
# Use Horreum's DataType accessor instead of manual key construction
|
|
307
307
|
collection = target_instance.send(collection_name)
|
|
308
|
-
|
|
308
|
+
# Pass self (Familia object) not identifier (string) for correct serialization
|
|
309
|
+
# See GitHub issue #212: serialize_value handles objects vs strings differently
|
|
310
|
+
collection.score(self)
|
|
309
311
|
end
|
|
310
312
|
end
|
|
311
313
|
|
|
@@ -691,14 +691,18 @@ module Familia
|
|
|
691
691
|
|
|
692
692
|
case config.type
|
|
693
693
|
when :sorted_set
|
|
694
|
-
|
|
694
|
+
# Pass self (Familia object) not identifier (string) for correct serialization
|
|
695
|
+
# See GitHub issue #212: serialize_value handles objects vs strings differently
|
|
696
|
+
score = collection.score(self)
|
|
695
697
|
next unless score
|
|
696
698
|
|
|
697
699
|
decoded_score = decode_score(score) if respond_to?(:decode_score)
|
|
698
700
|
when :set
|
|
699
|
-
|
|
701
|
+
# Pass self (Familia object) not identifier (string) for correct serialization
|
|
702
|
+
is_member = collection.member?(self)
|
|
700
703
|
next unless is_member
|
|
701
704
|
when :list
|
|
705
|
+
# List uses to_a which returns deserialized values, so identifier is correct here
|
|
702
706
|
position = collection.to_a.index(identifier)
|
|
703
707
|
next unless position
|
|
704
708
|
end
|
data/lib/familia/version.rb
CHANGED
|
@@ -210,8 +210,9 @@ user1_team_ids.include?(@team1.identifier)
|
|
|
210
210
|
#=> true
|
|
211
211
|
|
|
212
212
|
## Test multiple users in same team - add user2
|
|
213
|
+
# Use object, not string identifier, for correct serialization (see issue #212)
|
|
213
214
|
@team1.add_members_instance(@user2)
|
|
214
|
-
@team1.members.member?(@user2
|
|
215
|
+
@team1.members.member?(@user2)
|
|
215
216
|
#=> true
|
|
216
217
|
|
|
217
218
|
## Test team1 now has two members
|
|
@@ -94,8 +94,9 @@ end
|
|
|
94
94
|
|
|
95
95
|
## Test Symbol target class resolution works
|
|
96
96
|
# This is the primary regression test - it should not raise NoMethodError
|
|
97
|
+
# Use object, not string identifier, for correct serialization (see issue #212)
|
|
97
98
|
@customer.add_domains_instance(@domain)
|
|
98
|
-
@customer.domains.member?(@domain
|
|
99
|
+
@customer.domains.member?(@domain)
|
|
99
100
|
#=> true
|
|
100
101
|
|
|
101
102
|
## Test domain bidirectional methods were created correctly
|
|
@@ -109,7 +110,7 @@ end
|
|
|
109
110
|
## Test domain can add itself using generated method
|
|
110
111
|
@customer.domains.remove(@domain)
|
|
111
112
|
@domain.add_to_symbol_resolution_customer_domains(@customer)
|
|
112
|
-
@customer.domains.member?(@domain
|
|
113
|
+
@customer.domains.member?(@domain)
|
|
113
114
|
#=> true
|
|
114
115
|
|
|
115
116
|
## Test domain score calculation works with Symbol target class
|
|
@@ -124,7 +125,7 @@ end
|
|
|
124
125
|
## Test String target class resolution works
|
|
125
126
|
# This is the secondary regression test
|
|
126
127
|
@customer.add_tags_instance(@tag)
|
|
127
|
-
@customer.tags.member?(@tag
|
|
128
|
+
@customer.tags.member?(@tag)
|
|
128
129
|
#=> true
|
|
129
130
|
|
|
130
131
|
## Test tag bidirectional methods were created correctly
|
|
@@ -138,7 +139,7 @@ end
|
|
|
138
139
|
## Test tag can add itself using generated method
|
|
139
140
|
@customer.tags.remove(@tag)
|
|
140
141
|
@tag.add_to_symbol_resolution_customer_tags(@customer)
|
|
141
|
-
@customer.tags.member?(@tag
|
|
142
|
+
@customer.tags.member?(@tag)
|
|
142
143
|
#=> true
|
|
143
144
|
|
|
144
145
|
## Test tag membership check works with String target class (sets don't have scores)
|
|
@@ -123,23 +123,24 @@ ApiTestUser.all_users.class.name
|
|
|
123
123
|
|
|
124
124
|
## Automatic tracking addition works on save
|
|
125
125
|
@user.save
|
|
126
|
-
ApiTestUser.all_users.member?(@user
|
|
126
|
+
ApiTestUser.all_users.member?(@user)
|
|
127
127
|
#=> true
|
|
128
128
|
|
|
129
129
|
## Score calculation works for simple field scores
|
|
130
|
-
|
|
130
|
+
# Use object, not string identifier, for correct serialization (see issue #212)
|
|
131
|
+
score = ApiTestUser.all_users.score(@user)
|
|
131
132
|
score.is_a?(Float) && score > 0
|
|
132
133
|
#=> true
|
|
133
134
|
|
|
134
135
|
## Score calculation works for lambda scores with active user
|
|
135
136
|
ApiTestUser.add_to_active_users(@user) # Explicit addition to active_users collection
|
|
136
|
-
active_score = ApiTestUser.active_users.score(@user
|
|
137
|
+
active_score = ApiTestUser.active_users.score(@user)
|
|
137
138
|
active_score > 0
|
|
138
139
|
#=> true
|
|
139
140
|
|
|
140
141
|
## Score calculation works for lambda scores with inactive user
|
|
141
142
|
ApiTestUser.add_to_active_users(@inactive_user) # Explicit addition to active_users collection
|
|
142
|
-
ApiTestUser.active_users.member?(@inactive_user
|
|
143
|
+
ApiTestUser.active_users.member?(@inactive_user)
|
|
143
144
|
#=> true
|
|
144
145
|
|
|
145
146
|
# =============================================
|
|
@@ -285,7 +286,8 @@ membership_meta.scope_class
|
|
|
285
286
|
|
|
286
287
|
## Class tracking and indexing work together automatically on save
|
|
287
288
|
@user.save # Should automatically update both tracking and indexing
|
|
288
|
-
|
|
289
|
+
# Use object for member?, not string identifier (see issue #212)
|
|
290
|
+
ApiTestUser.all_users.member?(@user) && ApiTestUser.email_lookup.get(@user.email) == @user.user_id
|
|
289
291
|
#=> true
|
|
290
292
|
|
|
291
293
|
## Parent-based relationships work with tracking
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# try/features/relationships/serialization_consistency_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# Regression tests for GitHub issue #212: Serialization mismatch in collection_operations.rb
|
|
6
|
+
#
|
|
7
|
+
# BUG SUMMARY:
|
|
8
|
+
# The relationships module was extracting `.identifier` before passing to DataType methods.
|
|
9
|
+
# The serialize_value method handles Familia objects differently from raw strings:
|
|
10
|
+
#
|
|
11
|
+
# serialize_value(familia_object) => extracts identifier (raw string): "id123"
|
|
12
|
+
# serialize_value("id123") => JSON encodes (adds quotes): "\"id123\""
|
|
13
|
+
#
|
|
14
|
+
# This causes cross-path inconsistencies when data added via relationships module
|
|
15
|
+
# cannot be found via DataType methods using string identifiers, and vice versa.
|
|
16
|
+
#
|
|
17
|
+
# FIX APPLIED (collection_operations.rb + participant_methods.rb):
|
|
18
|
+
# Pass the full Familia object to DataType methods instead of extracting the identifier
|
|
19
|
+
# first. Let serialize_value handle the object correctly. This ensures:
|
|
20
|
+
# - add(item) stores raw identifier
|
|
21
|
+
# - remove(item) removes raw identifier
|
|
22
|
+
# - member?(item) finds raw identifier
|
|
23
|
+
# - score(item) queries with raw identifier
|
|
24
|
+
#
|
|
25
|
+
# TEST STRUCTURE:
|
|
26
|
+
# 1. Tests verify that issue #212 is fixed - object-based lookups work across paths
|
|
27
|
+
# 2. Tests marked "KNOWN LIMITATION" document that string identifier lookups get
|
|
28
|
+
# JSON-encoded, causing mismatches. This is by design - always use Familia
|
|
29
|
+
# objects instead of raw string identifiers for DataType operations.
|
|
30
|
+
|
|
31
|
+
require_relative '../../support/helpers/test_helpers'
|
|
32
|
+
|
|
33
|
+
# Test classes for serialization consistency testing
|
|
34
|
+
class SerConsistencyOwner < Familia::Horreum
|
|
35
|
+
feature :relationships
|
|
36
|
+
|
|
37
|
+
identifier_field :owner_id
|
|
38
|
+
field :owner_id
|
|
39
|
+
field :name
|
|
40
|
+
|
|
41
|
+
# All three collection types to test
|
|
42
|
+
sorted_set :members_zset
|
|
43
|
+
set :members_set
|
|
44
|
+
list :members_list
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class SerConsistencyMember < Familia::Horreum
|
|
48
|
+
feature :relationships
|
|
49
|
+
|
|
50
|
+
identifier_field :member_id
|
|
51
|
+
field :member_id
|
|
52
|
+
field :email
|
|
53
|
+
field :created_at
|
|
54
|
+
|
|
55
|
+
# Participates in all three collection types
|
|
56
|
+
participates_in SerConsistencyOwner, :members_zset, score: :created_at, type: :sorted_set
|
|
57
|
+
participates_in SerConsistencyOwner, :members_set, type: :set
|
|
58
|
+
participates_in SerConsistencyOwner, :members_list, type: :list
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
@owner = SerConsistencyOwner.new(owner_id: 'owner-abc123', name: 'Test Owner')
|
|
62
|
+
@member1 = SerConsistencyMember.new(
|
|
63
|
+
member_id: 'member-def456',
|
|
64
|
+
email: 'member1@example.com',
|
|
65
|
+
created_at: Familia.now.to_i
|
|
66
|
+
)
|
|
67
|
+
@member2 = SerConsistencyMember.new(
|
|
68
|
+
member_id: 'member-ghi789',
|
|
69
|
+
email: 'member2@example.com',
|
|
70
|
+
created_at: (Familia.now + 100).to_i
|
|
71
|
+
)
|
|
72
|
+
@owner.save
|
|
73
|
+
@member1.save
|
|
74
|
+
@member2.save
|
|
75
|
+
|
|
76
|
+
# =============================================================================
|
|
77
|
+
# 1. SORTED SET (zset) - Cross-Path Consistency
|
|
78
|
+
# =============================================================================
|
|
79
|
+
|
|
80
|
+
## Add via relationships module, verify raw identifier is stored correctly
|
|
81
|
+
@member1.add_to_ser_consistency_owner_members_zset(@owner)
|
|
82
|
+
@owner.members_zset.membersraw.include?('member-def456')
|
|
83
|
+
#=> true
|
|
84
|
+
|
|
85
|
+
## Add via relationships module, lookup via DataType.member? with object
|
|
86
|
+
@owner.members_zset.member?(@member1)
|
|
87
|
+
#=> true
|
|
88
|
+
|
|
89
|
+
## KNOWN LIMITATION: DataType.member? with string identifier
|
|
90
|
+
# After add via relationships, lookup with raw string identifier.
|
|
91
|
+
# Currently fails: serialize_value("member-def456") => "\"member-def456\""
|
|
92
|
+
# but stored value is "member-def456" (raw, no quotes).
|
|
93
|
+
# This is documented behavior - use objects instead of string identifiers.
|
|
94
|
+
@owner.members_zset.member?(@member1.member_id)
|
|
95
|
+
#=> false
|
|
96
|
+
|
|
97
|
+
## Score retrieval: relationships module matches DataType with object
|
|
98
|
+
@rel_score = @member1.score_in_ser_consistency_owner_members_zset(@owner)
|
|
99
|
+
@dt_score = @owner.members_zset.score(@member1)
|
|
100
|
+
@rel_score == @dt_score && @rel_score.is_a?(Float)
|
|
101
|
+
#=> true
|
|
102
|
+
|
|
103
|
+
## KNOWN LIMITATION: Score retrieval via DataType with string identifier
|
|
104
|
+
# Same serialization asymmetry: score("member-def456") queries for wrong value.
|
|
105
|
+
# Returns nil because the JSON-encoded string doesn't match the stored raw identifier.
|
|
106
|
+
@dt_score_str = @owner.members_zset.score(@member1.member_id)
|
|
107
|
+
@dt_score_str.nil?
|
|
108
|
+
#=> true
|
|
109
|
+
|
|
110
|
+
## Remove via relationships module, verify removed
|
|
111
|
+
@member1.remove_from_ser_consistency_owner_members_zset(@owner)
|
|
112
|
+
@owner.members_zset.member?(@member1)
|
|
113
|
+
#=> false
|
|
114
|
+
|
|
115
|
+
## Add via DataType directly with object, lookup via relationships module
|
|
116
|
+
@owner.members_zset.add(@member2, 200.0)
|
|
117
|
+
@member2.in_ser_consistency_owner_members_zset?(@owner)
|
|
118
|
+
#=> true
|
|
119
|
+
|
|
120
|
+
## KNOWN LIMITATION: DataType add + DataType string lookup
|
|
121
|
+
# When added via DataType.add(object), stored as raw identifier.
|
|
122
|
+
# DataType.member?(string) still JSON-encodes, causing mismatch.
|
|
123
|
+
# Use objects instead of string identifiers.
|
|
124
|
+
@owner.members_zset.member?(@member2.member_id)
|
|
125
|
+
#=> false
|
|
126
|
+
|
|
127
|
+
## Remove via DataType, verify removed via relationships module
|
|
128
|
+
@owner.members_zset.remove(@member2)
|
|
129
|
+
@member2.in_ser_consistency_owner_members_zset?(@owner)
|
|
130
|
+
#=> false
|
|
131
|
+
|
|
132
|
+
# =============================================================================
|
|
133
|
+
# 2. UNSORTED SET - Cross-Path Consistency
|
|
134
|
+
# =============================================================================
|
|
135
|
+
|
|
136
|
+
## Add via relationships module to unsorted set
|
|
137
|
+
@member1.add_to_ser_consistency_owner_members_set(@owner)
|
|
138
|
+
@owner.members_set.member?(@member1)
|
|
139
|
+
#=> true
|
|
140
|
+
|
|
141
|
+
## KNOWN LIMITATION: Unsorted set string identifier lookup
|
|
142
|
+
# Use objects instead of string identifiers.
|
|
143
|
+
@owner.members_set.member?(@member1.member_id)
|
|
144
|
+
#=> false
|
|
145
|
+
|
|
146
|
+
## Verify raw identifier storage in unsorted set
|
|
147
|
+
@owner.members_set.membersraw.include?('member-def456')
|
|
148
|
+
#=> true
|
|
149
|
+
|
|
150
|
+
## Remove from unsorted set via relationships module
|
|
151
|
+
@member1.remove_from_ser_consistency_owner_members_set(@owner)
|
|
152
|
+
@owner.members_set.member?(@member1)
|
|
153
|
+
#=> false
|
|
154
|
+
|
|
155
|
+
## Add via DataType to unsorted set, lookup via relationships module
|
|
156
|
+
@owner.members_set.add(@member2)
|
|
157
|
+
@member2.in_ser_consistency_owner_members_set?(@owner)
|
|
158
|
+
#=> true
|
|
159
|
+
|
|
160
|
+
## Clean up unsorted set
|
|
161
|
+
@owner.members_set.remove(@member2)
|
|
162
|
+
@member2.in_ser_consistency_owner_members_set?(@owner)
|
|
163
|
+
#=> false
|
|
164
|
+
|
|
165
|
+
# =============================================================================
|
|
166
|
+
# 3. LIST - Cross-Path Consistency
|
|
167
|
+
# =============================================================================
|
|
168
|
+
|
|
169
|
+
## Add via relationships module to list
|
|
170
|
+
@member1.add_to_ser_consistency_owner_members_list(@owner)
|
|
171
|
+
@owner.members_list.member?(@member1)
|
|
172
|
+
#=> true
|
|
173
|
+
|
|
174
|
+
## KNOWN LIMITATION: List string identifier lookup
|
|
175
|
+
# Use objects instead of string identifiers.
|
|
176
|
+
@owner.members_list.member?(@member1.member_id)
|
|
177
|
+
#=> false
|
|
178
|
+
|
|
179
|
+
## Verify raw identifier storage in list
|
|
180
|
+
@owner.members_list.membersraw.include?('member-def456')
|
|
181
|
+
#=> true
|
|
182
|
+
|
|
183
|
+
## Remove from list via relationships module
|
|
184
|
+
@member1.remove_from_ser_consistency_owner_members_list(@owner)
|
|
185
|
+
@owner.members_list.member?(@member1)
|
|
186
|
+
#=> false
|
|
187
|
+
|
|
188
|
+
## Add via DataType to list, lookup via relationships module
|
|
189
|
+
@owner.members_list.add(@member2)
|
|
190
|
+
@member2.in_ser_consistency_owner_members_list?(@owner)
|
|
191
|
+
#=> true
|
|
192
|
+
|
|
193
|
+
## Clean up list
|
|
194
|
+
@owner.members_list.remove(@member2)
|
|
195
|
+
@member2.in_ser_consistency_owner_members_list?(@owner)
|
|
196
|
+
#=> false
|
|
197
|
+
|
|
198
|
+
# =============================================================================
|
|
199
|
+
# 4. SERIALIZATION CONSISTENCY VERIFICATION
|
|
200
|
+
# =============================================================================
|
|
201
|
+
|
|
202
|
+
## Serialization of Familia object extracts raw identifier (no JSON encoding)
|
|
203
|
+
@owner.members_zset.send(:serialize_value, @member1)
|
|
204
|
+
#=> "member-def456"
|
|
205
|
+
|
|
206
|
+
## Serialization of raw string JSON-encodes it (adds quotes)
|
|
207
|
+
@owner.members_zset.send(:serialize_value, "member-def456")
|
|
208
|
+
#=> "\"member-def456\""
|
|
209
|
+
|
|
210
|
+
## Object lookup works, string lookup is a known limitation
|
|
211
|
+
# Add using object stores raw identifier
|
|
212
|
+
@owner.members_zset.add(@member1, 100.0)
|
|
213
|
+
# Object lookup works; string lookup returns false (known limitation)
|
|
214
|
+
@obj_lookup = @owner.members_zset.member?(@member1)
|
|
215
|
+
@str_lookup = @owner.members_zset.member?(@member1.member_id)
|
|
216
|
+
[@obj_lookup, @str_lookup]
|
|
217
|
+
#=> [true, false]
|
|
218
|
+
|
|
219
|
+
## Clean up after serialization verification
|
|
220
|
+
@owner.members_zset.remove(@member1)
|
|
221
|
+
@owner.members_zset.member?(@member1)
|
|
222
|
+
#=> false
|
|
223
|
+
|
|
224
|
+
# =============================================================================
|
|
225
|
+
# 5. ROUND-TRIP CONSISTENCY (ADD + REMOVE via mixed paths)
|
|
226
|
+
# =============================================================================
|
|
227
|
+
|
|
228
|
+
## Add via relationships, remove via DataType (sorted_set)
|
|
229
|
+
@member1.add_to_ser_consistency_owner_members_zset(@owner)
|
|
230
|
+
@owner.members_zset.remove(@member1)
|
|
231
|
+
@member1.in_ser_consistency_owner_members_zset?(@owner)
|
|
232
|
+
#=> false
|
|
233
|
+
|
|
234
|
+
## Add via DataType, remove via relationships (sorted_set)
|
|
235
|
+
@owner.members_zset.add(@member1, 100.0)
|
|
236
|
+
@member1.remove_from_ser_consistency_owner_members_zset(@owner)
|
|
237
|
+
@owner.members_zset.member?(@member1)
|
|
238
|
+
#=> false
|
|
239
|
+
|
|
240
|
+
## KNOWN LIMITATION: Remove via DataType using string identifier
|
|
241
|
+
# Add via relationships stores raw identifier, remove with string fails
|
|
242
|
+
# because remove("id") serializes to "\"id\"" which doesn't match stored "id".
|
|
243
|
+
# Use objects instead of string identifiers.
|
|
244
|
+
@member2.add_to_ser_consistency_owner_members_zset(@owner)
|
|
245
|
+
@owner.members_zset.remove(@member2.member_id) # This won't actually remove it
|
|
246
|
+
@member2.in_ser_consistency_owner_members_zset?(@owner)
|
|
247
|
+
#=> true
|
|
248
|
+
|
|
249
|
+
## Add via relationships, remove via DataType (unsorted set)
|
|
250
|
+
@member1.add_to_ser_consistency_owner_members_set(@owner)
|
|
251
|
+
@owner.members_set.remove(@member1)
|
|
252
|
+
@member1.in_ser_consistency_owner_members_set?(@owner)
|
|
253
|
+
#=> false
|
|
254
|
+
|
|
255
|
+
## Add via DataType, remove via relationships (unsorted set)
|
|
256
|
+
@owner.members_set.add(@member1)
|
|
257
|
+
@member1.remove_from_ser_consistency_owner_members_set(@owner)
|
|
258
|
+
@owner.members_set.member?(@member1)
|
|
259
|
+
#=> false
|
|
260
|
+
|
|
261
|
+
## Add via relationships, remove via DataType (list)
|
|
262
|
+
@member1.add_to_ser_consistency_owner_members_list(@owner)
|
|
263
|
+
@owner.members_list.remove(@member1)
|
|
264
|
+
@member1.in_ser_consistency_owner_members_list?(@owner)
|
|
265
|
+
#=> false
|
|
266
|
+
|
|
267
|
+
## Add via DataType, remove via relationships (list)
|
|
268
|
+
@owner.members_list.add(@member1)
|
|
269
|
+
@member1.remove_from_ser_consistency_owner_members_list(@owner)
|
|
270
|
+
@owner.members_list.member?(@member1)
|
|
271
|
+
#=> false
|
|
272
|
+
|
|
273
|
+
# =============================================================================
|
|
274
|
+
# CLEANUP
|
|
275
|
+
# =============================================================================
|
|
276
|
+
|
|
277
|
+
## Clean up all test data
|
|
278
|
+
begin
|
|
279
|
+
# Clean up collections first
|
|
280
|
+
@owner.members_zset.delete!
|
|
281
|
+
@owner.members_set.delete!
|
|
282
|
+
@owner.members_list.delete!
|
|
283
|
+
# Then destroy objects
|
|
284
|
+
[@owner, @member1, @member2].each do |obj|
|
|
285
|
+
obj.destroy if obj.respond_to?(:destroy) && obj.exists?
|
|
286
|
+
end
|
|
287
|
+
true
|
|
288
|
+
rescue => e
|
|
289
|
+
puts "Cleanup warning: #{e.message}"
|
|
290
|
+
false
|
|
291
|
+
end
|
|
292
|
+
#=> true
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: familia
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.1.
|
|
4
|
+
version: 2.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Delano Mandelbaum
|
|
@@ -27,16 +27,22 @@ dependencies:
|
|
|
27
27
|
name: connection_pool
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
|
-
- - "
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.4'
|
|
33
|
+
- - "<"
|
|
31
34
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '
|
|
35
|
+
version: '4.0'
|
|
33
36
|
type: :runtime
|
|
34
37
|
prerelease: false
|
|
35
38
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
39
|
requirements:
|
|
37
|
-
- - "
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '2.4'
|
|
43
|
+
- - "<"
|
|
38
44
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '
|
|
45
|
+
version: '4.0'
|
|
40
46
|
- !ruby/object:Gem::Dependency
|
|
41
47
|
name: csv
|
|
42
48
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -390,6 +396,7 @@ files:
|
|
|
390
396
|
- try/features/relationships/relationships_performance_try.rb
|
|
391
397
|
- try/features/relationships/relationships_performance_working_try.rb
|
|
392
398
|
- try/features/relationships/relationships_try.rb
|
|
399
|
+
- try/features/relationships/serialization_consistency_try.rb
|
|
393
400
|
- try/features/safe_dump/safe_dump_advanced_try.rb
|
|
394
401
|
- try/features/safe_dump/safe_dump_try.rb
|
|
395
402
|
- try/features/schema_registry_try.rb
|
|
@@ -582,7 +589,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
582
589
|
- !ruby/object:Gem::Version
|
|
583
590
|
version: '0'
|
|
584
591
|
requirements: []
|
|
585
|
-
rubygems_version: 3.
|
|
592
|
+
rubygems_version: 3.6.9
|
|
586
593
|
specification_version: 4
|
|
587
594
|
summary: An ORM for Valkey-compatible databases in Ruby.
|
|
588
595
|
test_files: []
|