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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e83013bddd8ff985ad1cd9ef73421146b5fea6c35cbb77aa0cf12656dd270f68
4
- data.tar.gz: ba7ebef4af806d823317fd41e1875733b12971b3c6321e841c9ecbf0c9d727a0
3
+ metadata.gz: 923e4c76b9ee95ca39160a66ec9534462b21b16e273fc27c6e67e707647da1a4
4
+ data.tar.gz: c8dd58304412dd4d9ad57727a4044d8680c16f701869e3f8dd7d0e2a57ed221a
5
5
  SHA512:
6
- metadata.gz: 126d79d60cad6c4ed4ed03a557f4435aec82d03ef2230ad64e4d2205f8c8856cc74b140c18fb5f1d567631f203f6b259e7d19d0bed8f36e09b5540fc004f4e6d
7
- data.tar.gz: cb5a52b4622c9f193d3858b0f2766795f49000f56fccbffdc630dc5b05e68db0a8c200ad25137bf543d9fc64ee2f53cab891517a5d9f91c2dd8debeb2119b7e7
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.0)
4
+ familia (2.1.1)
5
5
  concurrent-ruby (~> 1.3)
6
- connection_pool (~> 2.5)
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 (2.5.5)
22
+ connection_pool (3.0.2)
23
23
  csv (3.3.5)
24
24
  date (3.5.0)
25
25
  debug (1.11.0)
@@ -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.identifier)
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', '~> 2.5'
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.identifier, score)
34
+ collection.add(item, score)
35
35
  when :list
36
36
  # Lists use push/unshift operations
37
- collection.add(item.identifier)
37
+ collection.add(item)
38
38
  when :set
39
39
  # Sets use simple add
40
- collection.add(item.identifier)
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.identifier)
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.identifier)
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.identifier, score)
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.identifier)
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.identifier) }
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.identifier, new_score, xx: true)
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.identifier, new_score, xx: true)
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
- collection.score(identifier)
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
- score = collection.score(identifier)
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
- is_member = collection.member?(identifier)
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
@@ -4,5 +4,5 @@
4
4
 
5
5
  module Familia
6
6
  # Version information for the Familia
7
- VERSION = '2.1.0' unless defined?(Familia::VERSION)
7
+ VERSION = '2.1.1' unless defined?(Familia::VERSION)
8
8
  end
@@ -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.identifier)
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.identifier)
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.identifier)
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.identifier)
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.identifier)
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.identifier)
126
+ ApiTestUser.all_users.member?(@user)
127
127
  #=> true
128
128
 
129
129
  ## Score calculation works for simple field scores
130
- score = ApiTestUser.all_users.score(@user.identifier)
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.identifier)
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.identifier)
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
- ApiTestUser.all_users.member?(@user.identifier) && ApiTestUser.email_lookup.get(@user.email) == @user.user_id
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.0
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: '2.5'
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: '2.5'
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.7.2
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: []