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.
- checksums.yaml +4 -4
- data/.github/workflows/claude-code-review.yml +57 -0
- data/.github/workflows/claude.yml +71 -0
- data/.gitignore +5 -1
- data/.rubocop.yml +3 -0
- data/CLAUDE.md +32 -13
- data/Gemfile +2 -2
- data/Gemfile.lock +2 -2
- data/docs/wiki/Feature-System-Guide.md +36 -5
- data/docs/wiki/Home.md +30 -20
- data/docs/wiki/Relationships-Guide.md +684 -0
- data/examples/bit_encoding_integration.rb +237 -0
- data/examples/redis_command_validation_example.rb +231 -0
- data/examples/relationships_basic.rb +273 -0
- data/lib/familia/connection.rb +3 -3
- data/lib/familia/data_type.rb +7 -4
- data/lib/familia/features/encrypted_fields/concealed_string.rb +21 -23
- data/lib/familia/features/encrypted_fields.rb +413 -4
- data/lib/familia/features/expiration.rb +319 -33
- data/lib/familia/features/quantization.rb +385 -44
- data/lib/familia/features/relationships/cascading.rb +438 -0
- data/lib/familia/features/relationships/indexing.rb +370 -0
- data/lib/familia/features/relationships/membership.rb +503 -0
- data/lib/familia/features/relationships/permission_management.rb +264 -0
- data/lib/familia/features/relationships/querying.rb +620 -0
- data/lib/familia/features/relationships/redis_operations.rb +274 -0
- data/lib/familia/features/relationships/score_encoding.rb +442 -0
- data/lib/familia/features/relationships/tracking.rb +379 -0
- data/lib/familia/features/relationships.rb +466 -0
- data/lib/familia/features/transient_fields.rb +192 -10
- data/lib/familia/features.rb +2 -1
- data/lib/familia/horreum/subclass/definition.rb +1 -1
- data/lib/familia/validation/command_recorder.rb +336 -0
- data/lib/familia/validation/expectations.rb +519 -0
- data/lib/familia/validation/test_helpers.rb +443 -0
- data/lib/familia/validation/validator.rb +412 -0
- data/lib/familia/validation.rb +140 -0
- data/lib/familia/version.rb +1 -1
- data/try/edge_cases/hash_symbolization_try.rb +1 -0
- data/try/edge_cases/reserved_keywords_try.rb +1 -0
- data/try/edge_cases/string_coercion_try.rb +2 -0
- data/try/encryption/encryption_core_try.rb +3 -1
- data/try/features/categorical_permissions_try.rb +515 -0
- data/try/features/encryption_fields/concealed_string_core_try.rb +3 -0
- data/try/features/encryption_fields/context_isolation_try.rb +1 -0
- data/try/features/relationships_edge_cases_try.rb +145 -0
- data/try/features/relationships_performance_minimal_try.rb +132 -0
- data/try/features/relationships_performance_simple_try.rb +155 -0
- data/try/features/relationships_performance_try.rb +420 -0
- data/try/features/relationships_performance_working_try.rb +144 -0
- data/try/features/relationships_try.rb +237 -0
- data/try/features/safe_dump_try.rb +3 -0
- data/try/features/transient_fields/redacted_string_try.rb +2 -0
- data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
- data/try/helpers/test_helpers.rb +1 -1
- data/try/horreum/base_try.rb +14 -8
- data/try/horreum/enhanced_conflict_handling_try.rb +2 -0
- data/try/horreum/relations_try.rb +1 -1
- data/try/validation/atomic_operations_try.rb.disabled +320 -0
- data/try/validation/command_validation_try.rb.disabled +207 -0
- data/try/validation/performance_validation_try.rb.disabled +324 -0
- data/try/validation/real_world_scenarios_try.rb.disabled +390 -0
- metadata +32 -4
- data/docs/wiki/RelatableObjects-Guide.md +0 -563
- data/lib/familia/features/relatable_objects.rb +0 -125
- 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
|