familia 2.0.0.pre6 → 2.0.0.pre7

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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/claude-code-review.yml +57 -0
  3. data/.github/workflows/claude.yml +71 -0
  4. data/.gitignore +5 -1
  5. data/.rubocop.yml +3 -0
  6. data/CLAUDE.md +32 -13
  7. data/Gemfile +2 -2
  8. data/Gemfile.lock +2 -2
  9. data/docs/wiki/Feature-System-Guide.md +36 -5
  10. data/docs/wiki/Home.md +30 -20
  11. data/docs/wiki/Relationships-Guide.md +684 -0
  12. data/examples/bit_encoding_integration.rb +237 -0
  13. data/examples/redis_command_validation_example.rb +231 -0
  14. data/examples/relationships_basic.rb +273 -0
  15. data/lib/familia/connection.rb +3 -3
  16. data/lib/familia/data_type.rb +7 -4
  17. data/lib/familia/features/encrypted_fields/concealed_string.rb +21 -23
  18. data/lib/familia/features/encrypted_fields.rb +413 -4
  19. data/lib/familia/features/expiration.rb +319 -33
  20. data/lib/familia/features/quantization.rb +385 -44
  21. data/lib/familia/features/relationships/cascading.rb +438 -0
  22. data/lib/familia/features/relationships/indexing.rb +370 -0
  23. data/lib/familia/features/relationships/membership.rb +503 -0
  24. data/lib/familia/features/relationships/permission_management.rb +264 -0
  25. data/lib/familia/features/relationships/querying.rb +620 -0
  26. data/lib/familia/features/relationships/redis_operations.rb +274 -0
  27. data/lib/familia/features/relationships/score_encoding.rb +442 -0
  28. data/lib/familia/features/relationships/tracking.rb +379 -0
  29. data/lib/familia/features/relationships.rb +466 -0
  30. data/lib/familia/features/transient_fields.rb +192 -10
  31. data/lib/familia/features.rb +2 -1
  32. data/lib/familia/horreum/subclass/definition.rb +1 -1
  33. data/lib/familia/validation/command_recorder.rb +336 -0
  34. data/lib/familia/validation/expectations.rb +519 -0
  35. data/lib/familia/validation/test_helpers.rb +443 -0
  36. data/lib/familia/validation/validator.rb +412 -0
  37. data/lib/familia/validation.rb +140 -0
  38. data/lib/familia/version.rb +1 -1
  39. data/try/edge_cases/hash_symbolization_try.rb +1 -0
  40. data/try/edge_cases/reserved_keywords_try.rb +1 -0
  41. data/try/edge_cases/string_coercion_try.rb +2 -0
  42. data/try/encryption/encryption_core_try.rb +3 -1
  43. data/try/features/categorical_permissions_try.rb +515 -0
  44. data/try/features/encryption_fields/concealed_string_core_try.rb +3 -0
  45. data/try/features/encryption_fields/context_isolation_try.rb +1 -0
  46. data/try/features/relationships_edge_cases_try.rb +145 -0
  47. data/try/features/relationships_performance_minimal_try.rb +132 -0
  48. data/try/features/relationships_performance_simple_try.rb +155 -0
  49. data/try/features/relationships_performance_try.rb +420 -0
  50. data/try/features/relationships_performance_working_try.rb +144 -0
  51. data/try/features/relationships_try.rb +237 -0
  52. data/try/features/safe_dump_try.rb +3 -0
  53. data/try/features/transient_fields/redacted_string_try.rb +2 -0
  54. data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
  55. data/try/helpers/test_helpers.rb +1 -1
  56. data/try/horreum/base_try.rb +14 -8
  57. data/try/horreum/enhanced_conflict_handling_try.rb +2 -0
  58. data/try/horreum/relations_try.rb +1 -1
  59. data/try/validation/atomic_operations_try.rb.disabled +320 -0
  60. data/try/validation/command_validation_try.rb.disabled +207 -0
  61. data/try/validation/performance_validation_try.rb.disabled +324 -0
  62. data/try/validation/real_world_scenarios_try.rb.disabled +390 -0
  63. metadata +32 -4
  64. data/docs/wiki/RelatableObjects-Guide.md +0 -563
  65. data/lib/familia/features/relatable_objects.rb +0 -125
  66. data/try/features/relatable_objects_try.rb +0 -220
@@ -1,125 +0,0 @@
1
- # apps/api/v2/models/features/relatable_object.rb
2
-
3
- module V2
4
- module Features
5
- class RelatableObjectError < Familia::Problem; end
6
-
7
- # RelatableObject
8
- #
9
- # Provides the standard core object fields and methods.
10
- #
11
- module RelatableObject
12
- def self.included(base)
13
- base.class_sorted_set :relatable_objids
14
- base.class_hashkey :owners
15
-
16
- # NOTE: we do not automatically assign the objid field as the
17
- # main identifier field. That's up to the implementing class.
18
- base.field :objid
19
- base.field :extid
20
- base.field :api_version
21
-
22
- base.extend(ClassMethods)
23
-
24
- # prepend ensures our methods execute BEFORE field-generated accessors
25
- # include would place them AFTER, but they'd never execute because
26
- # attr_reader doesn't call super - it just returns the instance variable
27
- #
28
- # Method lookup chain:
29
- # prepend: [InstanceMethods] → [Field Methods] → [Parent]
30
- # include: [Field Methods] → [InstanceMethods] → [Parent]
31
- # (stops here, no super) (never reached)
32
- #
33
- base.prepend(InstanceMethods)
34
- end
35
-
36
- module InstanceMethods
37
- # We lazily generate the object ID and external ID when they are first
38
- # accessed so that we can instantiate and load existing objects, without
39
- # eagerly generating them, only to be overridden by the storage layer.
40
- #
41
- def init
42
- super if defined?(super) # Only call if parent has init
43
-
44
- @api_version ||= 'v2'
45
- end
46
-
47
- def objid
48
- @objid ||= begin # lazy loader
49
- generated_id = self.class.generate_objid
50
- # Using the attr_writer method ensures any future Familia
51
- # enhancements to the setter are properly invoked (as opposed
52
- # to directly assigning @objid).
53
- self.objid = generated_id
54
- end
55
- end
56
- alias relatable_objid objid
57
-
58
- def extid
59
- @extid ||= begin # lazy loader
60
- generated_id = self.class.generate_extid
61
- self.extid = generated_id
62
- end
63
- end
64
- alias external_identifier extid
65
-
66
- # Check if the given customer is the owner of this domain
67
- #
68
- # @param cust [V2::Customer, String] The customer object or customer ID to check
69
- # @return [Boolean] true if the customer is the owner, false otherwise
70
- def owner?(related_object)
71
- self.class.relatable?(related_object) do
72
- # Check the hash (our objid => related_object's objid)
73
- owner_objid = self.class.owners.get(objid).to_s
74
- return false if owner_objid.empty?
75
-
76
- owner_objid.eql?(related_object.objid)
77
- end
78
- end
79
-
80
- def owned?
81
- # We can only have an owner if we are relatable ourselves.
82
- return false unless is_a?(RelatableObject)
83
-
84
- # If our object identifier is present, we have an owner
85
- self.class.owners.key?(objid)
86
- end
87
- end
88
-
89
- module ClassMethods
90
- def relatable?(obj, &)
91
- is_relatable = obj.is_a?(RelatableObject)
92
- err_klass = V2::Features::RelatableObjectError
93
- raise err_klass, 'Not relatable object' unless is_relatable
94
- raise err_klass, 'No self-ownership' if obj.class == self
95
-
96
- block_given? ? yield : is_relatable
97
- end
98
-
99
- def find_by_objid(objid)
100
- return nil if objid.to_s.empty?
101
-
102
- if Familia.debug?
103
- reference = caller(1..1).first
104
- Familia.trace :FIND_BY_OBJID, Familia.dbclient(uri), objkey, reference
105
- end
106
-
107
- find_by_key objkey
108
- end
109
-
110
- def generate_objid
111
- SecureRandom.uuid_v7
112
- end
113
-
114
- # Guaranteed length of 54
115
- def generate_extid
116
- format('ext_%s', Familia.generate_id)
117
- end
118
- end
119
- extend ClassMethods
120
-
121
- # Self-register the kids for martial arts classes
122
- Familia::Base.add_feature self, :relatable_object
123
- end
124
- end
125
- end
@@ -1,220 +0,0 @@
1
- # try/features/relatable_objects_try.rb
2
-
3
- # Test RelatableObject feature functionality
4
-
5
- require_relative '../helpers/test_helpers'
6
-
7
- Familia.debug = false
8
-
9
- class RelatableTest < Familia::Horreum
10
- feature :relatable_object
11
- identifier_field :id
12
- field :id
13
- field :name
14
- end
15
-
16
- class RelatedTest < Familia::Horreum
17
- feature :relatable_object
18
- identifier_field :id
19
- field :id
20
- field :name
21
- end
22
-
23
- class NonRelatableTest < Familia::Horreum
24
- identifier_field :id
25
- field :id
26
- field :name
27
- end
28
-
29
- # Setup test objects
30
- @relatable_obj = RelatableTest.new
31
- @relatable_obj.id = 'test_rel_1'
32
- @relatable_obj.name = 'Test Relatable 1'
33
-
34
- @related_obj = RelatedTest.new
35
- @related_obj.id = 'test_rel_2'
36
- @related_obj.name = 'Test Related 2'
37
-
38
- @non_relatable = NonRelatableTest.new
39
- @non_relatable.id = 'test_non_rel'
40
- @non_relatable.name = 'Non Relatable'
41
-
42
- ## Class has RelatableObject methods mixed in
43
- RelatableTest.respond_to?(:relatable_objids)
44
- #=> true
45
-
46
- ## Class has owners class method
47
- RelatableTest.respond_to?(:owners)
48
- #=> true
49
-
50
- ## Class has relatable? method
51
- RelatableTest.respond_to?(:relatable?)
52
- #=> true
53
-
54
- ## Class has generate_objid method
55
- RelatableTest.respond_to?(:generate_objid)
56
- #=> true
57
-
58
- ## Class has generate_extid method
59
- RelatableTest.respond_to?(:generate_extid)
60
- #=> true
61
-
62
- ## Class has find_by_objid method
63
- RelatableTest.respond_to?(:find_by_objid)
64
- #=> true
65
-
66
- ## Object has objid method
67
- @relatable_obj.respond_to?(:objid)
68
- #=> true
69
-
70
- ## Object has extid method
71
- @relatable_obj.respond_to?(:extid)
72
- #=> true
73
-
74
- ## Object has api_version field
75
- @relatable_obj.respond_to?(:api_version)
76
- #=> true
77
-
78
- ## Object has owner? method
79
- @relatable_obj.respond_to?(:owner?)
80
- #=> true
81
-
82
- ## Object has owned? method
83
- @relatable_obj.respond_to?(:owned?)
84
- #=> true
85
-
86
- ## Object has relatable_objid alias
87
- @relatable_obj.respond_to?(:relatable_objid)
88
- #=> true
89
-
90
- ## Object has external_identifier alias
91
- @relatable_obj.respond_to?(:external_identifier)
92
- #=> true
93
-
94
- ## objid is lazily generated on first access
95
- @relatable_obj.objid
96
- #=:> String
97
-
98
- ## objid is a UUID v7 format
99
- objid = @relatable_obj.objid
100
- objid.match?(/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i)
101
- #=> true
102
-
103
- ## objid is cached after first generation
104
- objid1 = @relatable_obj.objid
105
- objid2 = @related_obj.objid
106
- [objid1, objid2]
107
- #=/=> _[0].eql?(_[1])
108
-
109
- ## extid is lazily generated on first access
110
- @relatable_obj.extid
111
- #=:> String
112
-
113
- ## extid starts with 'ext_' prefix (from our mock)
114
- @relatable_obj.extid.start_with?('ext_')
115
- #=> true
116
-
117
- ## extid is cached after first generation
118
- extid1 = @relatable_obj.extid
119
- extid2 = @related_obj.extid
120
- [extid1, extid2]
121
- #=/=> _[0].eql?(_[1])
122
-
123
- ## api_version defaults to 'v2'
124
- @relatable_obj.api_version
125
- #=> 'v2'
126
-
127
- ## relatable_objid is alias for objid
128
- [@relatable_obj.relatable_objid, @relatable_obj.objid]
129
- #==> _[0].eql?(_[1])
130
-
131
- ## external_identifier is alias for extid
132
- [@relatable_obj.external_identifier, @relatable_obj.extid]
133
- #==> _[0].eql?(_[1])
134
-
135
- ## relatable? prevents self-ownership (same class)
136
- RelatableTest.relatable?(@relatable_obj)
137
- #=!> V2::Features::RelatableObjectError
138
-
139
- ## relatable? returns true for different relatable classes
140
- RelatableTest.relatable?(@related_obj)
141
- #=> true
142
-
143
- ## relatable? raises error for non-relatable objects
144
- RelatableTest.relatable?(@non_relatable)
145
- #=!> V2::Features::RelatableObjectError
146
-
147
-
148
- ## relatable? with block executes block for relatable objects
149
- result = nil
150
- RelatableTest.relatable?(@related_obj) do
151
- result = "executed"
152
- end
153
- result
154
- #=> "executed"
155
-
156
- ## owned? returns false when no owner is set
157
- @relatable_obj.owned?
158
- #=> false
159
-
160
- ## owner? returns false when objects are not related
161
- @relatable_obj.owner?(@related_obj)
162
- #=> false
163
-
164
- ## generate_objid creates UUID v7
165
- generated_id = RelatableTest.generate_objid
166
- generated_id.match?(/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i)
167
- #=> true
168
-
169
- ## generate_extid creates external ID
170
- RelatableTest.generate_extid
171
- #==> _.start_with?('ext_')
172
- #==> _.size == 54
173
-
174
- ## find_by_objid returns nil for empty objid
175
- result = RelatableTest.find_by_objid('')
176
- result.nil?
177
- #=> true
178
-
179
- ## find_by_objid returns nil for nil objid
180
- result = RelatableTest.find_by_objid(nil)
181
- result.nil?
182
- #=> true
183
-
184
- ## Class has relatable_objids sorted set
185
- objids_set = RelatableTest.relatable_objids
186
- objids_set.class.name
187
- #=> "Familia::SortedSet"
188
-
189
- ## Class has owners hash key
190
- owners_hash = RelatableTest.owners
191
- owners_hash.class.name
192
- #=> "Familia::HashKey"
193
-
194
- ## Objects can be persisted and retrieved
195
- @relatable_obj.save
196
- retrieved = RelatableTest.find(@relatable_obj.id)
197
- retrieved.id == @relatable_obj.id
198
- #=> true
199
-
200
- ## API version is preserved when persisting
201
- retrieved = RelatableTest.find(@relatable_obj.id)
202
- retrieved.api_version
203
- #=> 'v2'
204
-
205
- ## Objid is preserved when persisting
206
- original_objid = @relatable_obj.objid
207
- retrieved = RelatableTest.find(@relatable_obj.id)
208
- retrieved.objid == original_objid
209
- #=> true
210
-
211
- ## Extid is preserved when persisting
212
- original_extid = @relatable_obj.extid
213
- retrieved = RelatableTest.find(@relatable_obj.id)
214
- retrieved.extid == original_extid
215
- #=> true
216
-
217
- # Cleanup
218
- @relatable_obj.destroy! if @relatable_obj
219
- @related_obj.destroy! if @related_obj
220
- @non_relatable.destroy! if @non_relatable