familia 2.0.0.pre10 → 2.0.0.pre13
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/.rubocop_todo.yml +2 -3
- data/CHANGELOG.rst +507 -0
- data/CLAUDE.md +5 -55
- data/Gemfile +1 -6
- data/Gemfile.lock +13 -7
- data/changelog.d/README.md +45 -34
- data/changelog.d/scriv.ini +5 -0
- data/docs/archive/FAMILIA_RELATIONSHIPS.md +1 -1
- data/docs/archive/FAMILIA_UPDATE.md +1 -1
- data/docs/archive/README.md +15 -19
- data/docs/guides/Feature-System-Autoloading.md +228 -0
- data/docs/guides/Home.md +1 -1
- data/docs/guides/Implementation-Guide.md +1 -1
- data/docs/guides/relationships-methods.md +1 -1
- data/docs/guides/time-utilities.md +221 -0
- data/docs/migrating/.gitignore +2 -0
- data/docs/migrating/v2.0.0-pre.md +84 -0
- data/docs/migrating/v2.0.0-pre11.md +253 -0
- data/docs/migrating/v2.0.0-pre12.md +306 -0
- data/docs/migrating/v2.0.0-pre13.md +329 -0
- data/docs/migrating/v2.0.0-pre5.md +110 -0
- data/docs/migrating/v2.0.0-pre6.md +154 -0
- data/docs/migrating/v2.0.0-pre7.md +222 -0
- data/docs/overview.md +6 -7
- data/{examples/redis_command_validation_example.rb → docs/reference/auditing_database_commands.rb} +29 -32
- data/examples/autoloader/mega_customer/safe_dump_fields.rb +6 -0
- data/examples/autoloader/mega_customer.rb +17 -0
- data/examples/{bit_encoding_integration.rb → permissions.rb} +30 -27
- data/examples/{relationships_basic.rb → relationships.rb} +2 -3
- data/examples/safe_dump.rb +281 -0
- data/familia.gemspec +5 -4
- data/lib/familia/autoloader.rb +53 -0
- data/lib/familia/base.rb +57 -0
- data/lib/familia/data_type.rb +4 -0
- data/lib/familia/encryption/encrypted_data.rb +4 -4
- data/lib/familia/encryption/manager.rb +6 -4
- data/lib/familia/{encryption_request_cache.rb → encryption/request_cache.rb} +1 -1
- data/lib/familia/encryption.rb +1 -1
- data/lib/familia/errors.rb +5 -0
- data/lib/familia/features/autoloadable.rb +113 -0
- data/lib/familia/features/encrypted_fields/concealed_string.rb +4 -2
- data/lib/familia/features/expiration.rb +4 -0
- data/lib/familia/features/external_identifier.rb +310 -0
- data/lib/familia/features/object_identifier.rb +307 -0
- data/lib/familia/features/quantization.rb +5 -0
- data/lib/familia/features/safe_dump.rb +74 -73
- data/lib/familia/features.rb +109 -17
- data/lib/familia/field_type.rb +2 -0
- data/lib/familia/horreum/core/serialization.rb +3 -3
- data/lib/familia/horreum/subclass/definition.rb +50 -7
- data/lib/familia/horreum.rb +2 -0
- data/lib/familia/json_serializer.rb +70 -0
- data/lib/familia/logging.rb +12 -10
- data/lib/familia/refinements/logger_trace.rb +57 -0
- data/lib/familia/refinements/snake_case.rb +40 -0
- data/lib/familia/refinements/time_utils.rb +248 -0
- data/lib/familia/refinements.rb +3 -49
- data/lib/familia/secure_identifier.rb +51 -75
- data/lib/familia/utils.rb +2 -0
- data/lib/familia/validation/{test_helpers.rb → validation_helpers.rb} +2 -2
- data/lib/familia/validation.rb +1 -1
- data/lib/familia/verifiable_identifier.rb +162 -0
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +15 -2
- data/try/core/autoloader_try.rb +112 -0
- data/try/core/extensions_try.rb +38 -21
- data/try/core/familia_extended_try.rb +4 -3
- data/try/core/secure_identifier_try.rb +47 -18
- data/try/core/time_utils_try.rb +130 -0
- data/try/core/verifiable_identifier_try.rb +171 -0
- data/try/data_types/datatype_base_try.rb +3 -2
- data/try/features/autoloadable/autoloadable_try.rb +61 -0
- data/try/features/encrypted_fields/concealed_string_core_try.rb +8 -3
- data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +59 -17
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +36 -12
- data/try/features/{external_identifiers/external_identifiers_try.rb → external_identifier/external_identifier_try.rb} +25 -28
- data/try/features/feature_improvements_try.rb +127 -0
- data/try/features/{object_identifiers/object_identifiers_integration_try.rb → object_identifier/object_identifier_integration_try.rb} +28 -30
- data/try/features/{object_identifiers/object_identifiers_try.rb → object_identifier/object_identifier_try.rb} +13 -13
- data/try/features/real_feature_integration_try.rb +8 -7
- data/try/features/safe_dump/safe_dump_autoloading_try.rb +111 -0
- data/try/features/safe_dump/safe_dump_try.rb +8 -9
- data/try/helpers/test_helpers.rb +41 -17
- data/try/integration/cross_component_try.rb +3 -1
- metadata +61 -26
- data/CHANGELOG.md +0 -184
- data/changelog.d/fragments/.keep +0 -0
- data/changelog.d/template.md.j2 +0 -29
- data/lib/familia/core_ext.rb +0 -135
- data/lib/familia/features/external_identifiers/external_identifier_field_type.rb +0 -120
- data/lib/familia/features/external_identifiers.rb +0 -111
- data/lib/familia/features/object_identifiers/object_identifier_field_type.rb +0 -91
- data/lib/familia/features/object_identifiers.rb +0 -194
- data/setup.cfg +0 -12
@@ -1,15 +1,15 @@
|
|
1
|
-
# try/features/
|
1
|
+
# try/features/external_identifier/external_identifier_try.rb
|
2
2
|
|
3
3
|
require_relative '../../helpers/test_helpers'
|
4
4
|
|
5
5
|
Familia.debug = false
|
6
6
|
|
7
|
-
# Test
|
7
|
+
# Test ExternalIdentifier feature functionality
|
8
8
|
|
9
|
-
# Basic class using
|
9
|
+
# Basic class using external_identifier
|
10
10
|
class ExternalIdTest < Familia::Horreum
|
11
|
-
feature :
|
12
|
-
feature :
|
11
|
+
feature :object_identifier
|
12
|
+
feature :external_identifier
|
13
13
|
identifier_field :id
|
14
14
|
field :id
|
15
15
|
field :name
|
@@ -17,8 +17,8 @@ end
|
|
17
17
|
|
18
18
|
# Class with custom prefix
|
19
19
|
class CustomPrefixTest < Familia::Horreum
|
20
|
-
feature :
|
21
|
-
feature :
|
20
|
+
feature :object_identifier
|
21
|
+
feature :external_identifier, prefix: 'cust'
|
22
22
|
identifier_field :id
|
23
23
|
field :id
|
24
24
|
field :name
|
@@ -26,8 +26,8 @@ end
|
|
26
26
|
|
27
27
|
# Class testing data integrity preservation
|
28
28
|
class ExternalDataIntegrityTest < Familia::Horreum
|
29
|
-
feature :
|
30
|
-
feature :
|
29
|
+
feature :object_identifier
|
30
|
+
feature :external_identifier
|
31
31
|
identifier_field :id
|
32
32
|
field :id
|
33
33
|
field :name
|
@@ -40,12 +40,12 @@ end
|
|
40
40
|
@lazy_obj = ExternalIdTest.new
|
41
41
|
@complex_obj = ExternalIdTest.new(id: 'complex_ext', name: 'Complex External')
|
42
42
|
|
43
|
-
## Feature depends on
|
44
|
-
ExternalIdTest.features_enabled.include?(:
|
43
|
+
## Feature depends on object_identifier
|
44
|
+
ExternalIdTest.features_enabled.include?(:object_identifier)
|
45
45
|
#==> true
|
46
46
|
|
47
|
-
## External
|
48
|
-
ExternalIdTest.features_enabled.include?(:
|
47
|
+
## External identifier feature is included
|
48
|
+
ExternalIdTest.features_enabled.include?(:external_identifier)
|
49
49
|
#==> true
|
50
50
|
|
51
51
|
## Class has extid field defined
|
@@ -57,16 +57,14 @@ obj = ExternalIdTest.new
|
|
57
57
|
obj.respond_to?(:extid)
|
58
58
|
#==> true
|
59
59
|
|
60
|
-
## External ID is generated from objid deterministically
|
60
|
+
## External ID is generated from objid deterministically for same object
|
61
61
|
obj = ExternalIdTest.new
|
62
62
|
obj.id = 'test_obj'
|
63
63
|
obj.name = 'Test Object'
|
64
64
|
objid = obj.objid
|
65
65
|
extid = obj.extid
|
66
|
-
#
|
67
|
-
|
68
|
-
obj2.instance_variable_set(:@objid, objid)
|
69
|
-
obj2.extid == extid
|
66
|
+
# Multiple calls to extid on same object should return same value
|
67
|
+
obj.extid == extid
|
70
68
|
#==> true
|
71
69
|
|
72
70
|
## External ID uses default 'ext' prefix
|
@@ -123,13 +121,12 @@ result = ExternalIdTest.find_by_extid('nonexistent')
|
|
123
121
|
result.is_a?(ExternalIdTest) || result.nil?
|
124
122
|
#==> true
|
125
123
|
|
126
|
-
## External ID is deterministic
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
obj1.extid == obj2.extid
|
124
|
+
## External ID is deterministic within same object
|
125
|
+
obj = ExternalIdTest.new
|
126
|
+
obj.id = 'deterministic_test'
|
127
|
+
first_extid = obj.extid
|
128
|
+
second_extid = obj.extid
|
129
|
+
first_extid == second_extid
|
133
130
|
#==> true
|
134
131
|
|
135
132
|
## External ID is different from objid
|
@@ -155,14 +152,14 @@ obj1.extid != obj2.extid
|
|
155
152
|
|
156
153
|
## extid field type is ExternalIdentifierFieldType
|
157
154
|
ExternalIdTest.field_types[:extid]
|
158
|
-
#=:> Familia::Features::
|
155
|
+
#=:> Familia::Features::ExternalIdentifier::ExternalIdentifierFieldType
|
159
156
|
|
160
157
|
## Feature options contain correct prefix
|
161
|
-
ExternalIdTest.feature_options(:
|
158
|
+
ExternalIdTest.feature_options(:external_identifier)[:prefix]
|
162
159
|
#=> "ext"
|
163
160
|
|
164
161
|
## Custom prefix feature options
|
165
|
-
CustomPrefixTest.feature_options(:
|
162
|
+
CustomPrefixTest.feature_options(:external_identifier)[:prefix]
|
166
163
|
#=> "cust"
|
167
164
|
|
168
165
|
## External ID is shorter than UUID objid
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# try/features/feature_improvements_try.rb
|
2
|
+
|
3
|
+
require_relative '../helpers/test_helpers'
|
4
|
+
|
5
|
+
# Test hierarchical feature registration
|
6
|
+
class ::TestClass
|
7
|
+
include Familia::Base
|
8
|
+
end
|
9
|
+
|
10
|
+
class TestSubClass < TestClass
|
11
|
+
end
|
12
|
+
|
13
|
+
# Create a simple test feature
|
14
|
+
module ::TestFeature
|
15
|
+
def test_method
|
16
|
+
"test feature working"
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.included(base)
|
20
|
+
base.extend(ClassMethods)
|
21
|
+
end
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
def class_test_method
|
25
|
+
"class method from feature"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Test SafeDump DSL improvements
|
31
|
+
class ::TestModelWithSafeDump
|
32
|
+
include Familia::Base
|
33
|
+
include Familia::Features::SafeDump
|
34
|
+
|
35
|
+
attr_accessor :id, :name, :email, :active
|
36
|
+
|
37
|
+
def initialize(attrs = {})
|
38
|
+
attrs.each { |k, v| send("#{k}=", v) }
|
39
|
+
end
|
40
|
+
|
41
|
+
def active?
|
42
|
+
@active == true
|
43
|
+
end
|
44
|
+
|
45
|
+
# Define safe dump fields using new DSL
|
46
|
+
safe_dump_field :id
|
47
|
+
safe_dump_field :name
|
48
|
+
safe_dump_field :status, ->(obj) { obj.active? ? 'active' : 'inactive' }
|
49
|
+
safe_dump_field :email
|
50
|
+
safe_dump_field :computed_field, ->(obj) { "#{obj.name}-computed" }
|
51
|
+
end
|
52
|
+
|
53
|
+
# Test field definitions in feature modules
|
54
|
+
module ::TestFieldFeature
|
55
|
+
def self.included(base)
|
56
|
+
base.extend ClassMethods
|
57
|
+
end
|
58
|
+
|
59
|
+
module ClassMethods
|
60
|
+
# This should work - field calls in ClassMethods should execute in the extending class context
|
61
|
+
def define_test_fields
|
62
|
+
# Assuming we have a field method available (this would come from Horreum)
|
63
|
+
# For this test, we'll just verify the method gets called in the right context
|
64
|
+
self.name + "_with_fields"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class ::TestFieldClass
|
70
|
+
include Familia::Base
|
71
|
+
include TestFieldFeature
|
72
|
+
end
|
73
|
+
|
74
|
+
## Test model-specific feature registration
|
75
|
+
# Register feature on TestClass
|
76
|
+
TestClass.add_feature TestFeature, :test_feature
|
77
|
+
#=> TestFeature
|
78
|
+
|
79
|
+
## TestClass should have the feature available
|
80
|
+
TestClass.features_available[:test_feature]
|
81
|
+
#=> TestFeature
|
82
|
+
|
83
|
+
## TestSubClass should inherit the feature from TestClass via ancestry chain
|
84
|
+
TestSubClass.find_feature(:test_feature)
|
85
|
+
#=> TestFeature
|
86
|
+
|
87
|
+
## Familia::Base should also be able to find features in the chain
|
88
|
+
Familia::Base.find_feature(:test_feature, TestSubClass)
|
89
|
+
#=> TestFeature
|
90
|
+
|
91
|
+
## Check that fields were registered correctly
|
92
|
+
TestModelWithSafeDump.safe_dump_field_names.sort
|
93
|
+
#=> [:computed_field, :email, :id, :name, :status]
|
94
|
+
|
95
|
+
## Test the safe_dump functionality
|
96
|
+
@test_model = TestModelWithSafeDump.new
|
97
|
+
@test_model.id = 123
|
98
|
+
@test_model.name = "Test User"
|
99
|
+
@test_model.email = "test@example.com"
|
100
|
+
@test_model.active = true
|
101
|
+
|
102
|
+
@result = @test_model.safe_dump
|
103
|
+
#=:> Hash
|
104
|
+
|
105
|
+
## Test safe_dump returns correct values
|
106
|
+
@result[:id]
|
107
|
+
#=> 123
|
108
|
+
|
109
|
+
## Test safe_dump name field
|
110
|
+
@result[:name]
|
111
|
+
#=> "Test User"
|
112
|
+
|
113
|
+
## Test safe_dump email field
|
114
|
+
@result[:email]
|
115
|
+
#=> "test@example.com"
|
116
|
+
|
117
|
+
## Test safe_dump status field with callable
|
118
|
+
@result[:status]
|
119
|
+
#=> "active"
|
120
|
+
|
121
|
+
## Test safe_dump computed field
|
122
|
+
@result[:computed_field]
|
123
|
+
#=> "Test User-computed"
|
124
|
+
|
125
|
+
## Test that ClassMethods execute in the right context
|
126
|
+
TestFieldClass.define_test_fields
|
127
|
+
#=> "TestFieldClass_with_fields"
|
@@ -1,15 +1,15 @@
|
|
1
|
-
# try/features/
|
1
|
+
# try/features/object_identifier/object_identifier_integration_try.rb
|
2
2
|
|
3
3
|
require_relative '../../helpers/test_helpers'
|
4
4
|
|
5
5
|
Familia.debug = false
|
6
6
|
|
7
|
-
# Integration test for
|
7
|
+
# Integration test for ObjectIdentifier and ExternalIdentifiers features together
|
8
8
|
|
9
9
|
# Class using both features with defaults
|
10
10
|
class IntegrationTest < Familia::Horreum
|
11
|
-
feature :
|
12
|
-
feature :
|
11
|
+
feature :object_identifier
|
12
|
+
feature :external_identifier # This depends on :object_identifier
|
13
13
|
identifier_field :id
|
14
14
|
field :id
|
15
15
|
field :name
|
@@ -18,8 +18,8 @@ end
|
|
18
18
|
|
19
19
|
# Class with custom configurations for both features
|
20
20
|
class CustomIntegrationTest < Familia::Horreum
|
21
|
-
feature :
|
22
|
-
feature :
|
21
|
+
feature :object_identifier, generator: :hex
|
22
|
+
feature :external_identifier, prefix: 'custom'
|
23
23
|
identifier_field :id
|
24
24
|
field :id
|
25
25
|
field :name
|
@@ -27,8 +27,8 @@ end
|
|
27
27
|
|
28
28
|
# Class testing full lifecycle with Redis persistence
|
29
29
|
class PersistenceTest < Familia::Horreum
|
30
|
-
feature :
|
31
|
-
feature :
|
30
|
+
feature :object_identifier
|
31
|
+
feature :external_identifier
|
32
32
|
identifier_field :id
|
33
33
|
field :id
|
34
34
|
field :name
|
@@ -39,12 +39,12 @@ end
|
|
39
39
|
@integration_obj = IntegrationTest.new(id: 'integration_1', name: 'Integration Test', email: 'test@example.com')
|
40
40
|
@custom_obj = CustomIntegrationTest.new(id: 'custom_1', name: 'Custom Test')
|
41
41
|
|
42
|
-
## Object
|
43
|
-
IntegrationTest.features_enabled.include?(:
|
42
|
+
## Object identifier feature is automatically included
|
43
|
+
IntegrationTest.features_enabled.include?(:object_identifier)
|
44
44
|
#==> true
|
45
45
|
|
46
|
-
## External
|
47
|
-
IntegrationTest.features_enabled.include?(:
|
46
|
+
## External identifier feature is included
|
47
|
+
IntegrationTest.features_enabled.include?(:external_identifier)
|
48
48
|
#==> true
|
49
49
|
|
50
50
|
## Object responds to objid accessor
|
@@ -70,14 +70,12 @@ obj = IntegrationTest.new
|
|
70
70
|
obj.objid != obj.extid
|
71
71
|
#==> true
|
72
72
|
|
73
|
-
## extid is deterministically generated from objid
|
73
|
+
## extid is deterministically generated from objid for same object
|
74
74
|
obj = IntegrationTest.new
|
75
75
|
original_objid = obj.objid
|
76
76
|
original_extid = obj.extid
|
77
|
-
#
|
78
|
-
|
79
|
-
obj2.instance_variable_set(:@objid, original_objid)
|
80
|
-
obj2.extid == original_extid
|
77
|
+
# Multiple calls on same object should return same extid
|
78
|
+
obj.extid == original_extid
|
81
79
|
#==> true
|
82
80
|
|
83
81
|
## Custom objid uses hex format (64 chars for 256-bit)
|
@@ -145,41 +143,41 @@ lazy_obj2.instance_variable_get(:@objid)
|
|
145
143
|
#=*> nil
|
146
144
|
|
147
145
|
## Check field types objid
|
148
|
-
IntegrationTest.field_types[:objid].is_a?(Familia::Features::
|
146
|
+
IntegrationTest.field_types[:objid].is_a?(Familia::Features::ObjectIdentifier::ObjectIdentifierFieldType)
|
149
147
|
#==> true
|
150
148
|
|
151
149
|
## ObjectIdentifier fields have correct types in field registry
|
152
|
-
IntegrationTest.field_types[:objid].class.ancestors.include?(Familia::Features::
|
150
|
+
IntegrationTest.field_types[:objid].class.ancestors.include?(Familia::Features::ObjectIdentifier::ObjectIdentifierFieldType)
|
153
151
|
#==> true
|
154
152
|
|
155
153
|
## ExternalIdentifier fields have correct types in field registry
|
156
|
-
IntegrationTest.field_types[:extid].class.ancestors.include?(Familia::Features::
|
154
|
+
IntegrationTest.field_types[:extid].class.ancestors.include?(Familia::Features::ExternalIdentifier::ExternalIdentifierFieldType)
|
157
155
|
#==> true
|
158
156
|
|
159
|
-
## Object
|
157
|
+
## Object identifier options are preserved
|
160
158
|
opts = IntegrationTest.feature_options
|
161
|
-
opts.key?(:
|
159
|
+
opts.key?(:object_identifier)
|
162
160
|
#==> true
|
163
161
|
|
164
|
-
## External
|
162
|
+
## External identifiersoptions are preserved
|
165
163
|
opts = IntegrationTest.feature_options
|
166
|
-
opts.key?(:
|
164
|
+
opts.key?(:external_identifier)
|
167
165
|
#==> true
|
168
166
|
|
169
167
|
## Generator default configuration is applied correctly
|
170
|
-
IntegrationTest.feature_options(:
|
168
|
+
IntegrationTest.feature_options(:object_identifier)[:generator]
|
171
169
|
#=> :uuid_v7
|
172
170
|
|
173
171
|
## Prefix default configuration is applied correctly
|
174
|
-
IntegrationTest.feature_options(:
|
172
|
+
IntegrationTest.feature_options(:external_identifier)[:prefix]
|
175
173
|
#=> "ext"
|
176
174
|
|
177
175
|
## Custom generator configuration is applied correctly
|
178
|
-
CustomIntegrationTest.feature_options(:
|
176
|
+
CustomIntegrationTest.feature_options(:object_identifier)[:generator]
|
179
177
|
#=> :hex
|
180
178
|
|
181
179
|
## Custom prefix configuration is applied correctly
|
182
|
-
CustomIntegrationTest.feature_options(:
|
180
|
+
CustomIntegrationTest.feature_options(:external_identifier)[:prefix]
|
183
181
|
#=> "custom"
|
184
182
|
|
185
183
|
## objid is URL-safe (UUID format)
|
@@ -265,9 +263,9 @@ second_extid = stability_obj.extid
|
|
265
263
|
first_extid == second_extid
|
266
264
|
#==> true
|
267
265
|
|
268
|
-
## Feature dependency is enforced (
|
266
|
+
## Feature dependency is enforced (external_identifier requires object_identifier)
|
269
267
|
# This is automatically handled by the feature system
|
270
|
-
IntegrationTest.features_enabled.include?(:
|
268
|
+
IntegrationTest.features_enabled.include?(:object_identifier)
|
271
269
|
#==> true
|
272
270
|
|
273
271
|
## Objects work with existing Horreum save pattern
|
@@ -1,14 +1,14 @@
|
|
1
|
-
# try/features/
|
1
|
+
# try/features/object_identifier/object_identifier_try.rb
|
2
2
|
|
3
3
|
require_relative '../../helpers/test_helpers'
|
4
4
|
|
5
5
|
Familia.debug = false
|
6
6
|
|
7
|
-
# Test
|
7
|
+
# Test ObjectIdentifier feature functionality
|
8
8
|
|
9
9
|
# Basic class using default UUID v7 generator
|
10
10
|
class BasicObjectTest < Familia::Horreum
|
11
|
-
feature :
|
11
|
+
feature :object_identifier
|
12
12
|
identifier_field :id
|
13
13
|
field :id
|
14
14
|
field :name
|
@@ -16,7 +16,7 @@ end
|
|
16
16
|
|
17
17
|
# Class using UUID v4 generator
|
18
18
|
class UuidV4Test < Familia::Horreum
|
19
|
-
feature :
|
19
|
+
feature :object_identifier, generator: :uuid_v4
|
20
20
|
identifier_field :id
|
21
21
|
field :id
|
22
22
|
field :name
|
@@ -24,7 +24,7 @@ end
|
|
24
24
|
|
25
25
|
# Class using hex generator
|
26
26
|
class HexTest < Familia::Horreum
|
27
|
-
feature :
|
27
|
+
feature :object_identifier, generator: :hex
|
28
28
|
identifier_field :id
|
29
29
|
field :id
|
30
30
|
field :name
|
@@ -32,7 +32,7 @@ end
|
|
32
32
|
|
33
33
|
# Class using custom proc generator
|
34
34
|
class CustomProcTest < Familia::Horreum
|
35
|
-
feature :
|
35
|
+
feature :object_identifier, generator: -> { "custom_#{SecureRandom.hex(4)}" }
|
36
36
|
identifier_field :id
|
37
37
|
field :id
|
38
38
|
field :name
|
@@ -40,7 +40,7 @@ end
|
|
40
40
|
|
41
41
|
# Class testing data integrity preservation
|
42
42
|
class DataIntegrityTest < Familia::Horreum
|
43
|
-
feature :
|
43
|
+
feature :object_identifier
|
44
44
|
identifier_field :id
|
45
45
|
field :id
|
46
46
|
field :name
|
@@ -50,7 +50,7 @@ end
|
|
50
50
|
@existing_obj = DataIntegrityTest.new(id: 'test_id', objid: 'preset_id_123', name: 'Preset Object')
|
51
51
|
|
52
52
|
## Feature is available on class
|
53
|
-
BasicObjectTest.features_enabled.include?(:
|
53
|
+
BasicObjectTest.features_enabled.include?(:object_identifier)
|
54
54
|
#==> true
|
55
55
|
|
56
56
|
## Class has objid field defined
|
@@ -162,22 +162,22 @@ basic_obj.objid.include?('-') && !hex_obj.objid.include?('-')
|
|
162
162
|
|
163
163
|
## objid field type is ObjectIdentifierFieldType
|
164
164
|
BasicObjectTest.field_types[:objid]
|
165
|
-
#=:> Familia::Features::
|
165
|
+
#=:> Familia::Features::ObjectIdentifier::ObjectIdentifierFieldType
|
166
166
|
|
167
167
|
## Generator configuration is accessible through feature options
|
168
|
-
BasicObjectTest.feature_options(:
|
168
|
+
BasicObjectTest.feature_options(:object_identifier)[:generator]
|
169
169
|
#=> :uuid_v7
|
170
170
|
|
171
171
|
## UUID v4 class has correct generator configured
|
172
|
-
UuidV4Test.feature_options(:
|
172
|
+
UuidV4Test.feature_options(:object_identifier)[:generator]
|
173
173
|
#=> :uuid_v4
|
174
174
|
|
175
175
|
## Hex class has correct generator configured
|
176
|
-
HexTest.feature_options(:
|
176
|
+
HexTest.feature_options(:object_identifier)[:generator]
|
177
177
|
#=> :hex
|
178
178
|
|
179
179
|
## Custom proc class has proc generator
|
180
|
-
CustomProcTest.feature_options(:
|
180
|
+
CustomProcTest.feature_options(:object_identifier)[:generator]
|
181
181
|
#=:> Proc
|
182
182
|
|
183
183
|
## Empty initialization preserves nil objid for lazy generation
|
@@ -22,11 +22,10 @@ class SafeDumpCategoryTest < Familia::Horreum
|
|
22
22
|
|
23
23
|
feature :safe_dump
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
]
|
25
|
+
# Use new SafeDump DSL
|
26
|
+
safe_dump_field :id
|
27
|
+
safe_dump_field :public_name
|
28
|
+
safe_dump_field :email
|
30
29
|
end
|
31
30
|
|
32
31
|
# Combined features work together
|
@@ -39,7 +38,9 @@ class CombinedFeaturesTest < Familia::Horreum
|
|
39
38
|
feature :expiration
|
40
39
|
feature :safe_dump
|
41
40
|
|
42
|
-
|
41
|
+
# Use new SafeDump DSL
|
42
|
+
safe_dump_field :id
|
43
|
+
safe_dump_field :name
|
43
44
|
end
|
44
45
|
|
45
46
|
# Test that individual features can be queried
|
@@ -100,7 +101,7 @@ SafeDumpCategoryTest.features_enabled.include?(:safe_dump)
|
|
100
101
|
@safedump_result.keys.sort
|
101
102
|
#=> [:email, :id, :public_name]
|
102
103
|
|
103
|
-
## Safe dump respects
|
104
|
+
## Safe dump respects safe_dump_field configuration
|
104
105
|
@safedump_result.key?(:tryouts_cache_data)
|
105
106
|
#=> false
|
106
107
|
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# try/features/safe_dump/safe_dump_autoloading_try.rb
|
2
|
+
|
3
|
+
require_relative '../../../lib/familia'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'tmpdir'
|
6
|
+
|
7
|
+
# Create test directory structure for SafeDump autoloading testing
|
8
|
+
@test_dir = Dir.mktmpdir('familia_safe_dump_autoload_test')
|
9
|
+
@model_file = File.join(@test_dir, 'test_safe_dump_model.rb')
|
10
|
+
@extension_file = File.join(@test_dir, 'test_safe_dump_model', 'safe_dump_extensions.rb')
|
11
|
+
@extension_dir = File.join(@test_dir, 'test_safe_dump_model')
|
12
|
+
|
13
|
+
# Create directory structure
|
14
|
+
FileUtils.mkdir_p(@extension_dir)
|
15
|
+
|
16
|
+
# Write test model file that uses SafeDump
|
17
|
+
File.write(@model_file, <<~RUBY)
|
18
|
+
class TestSafeDumpModel < Familia::Horreum
|
19
|
+
field :name
|
20
|
+
field :email
|
21
|
+
field :secret
|
22
|
+
|
23
|
+
feature :safe_dump
|
24
|
+
end
|
25
|
+
RUBY
|
26
|
+
|
27
|
+
# Write extension file (pattern: model_name/safe_dump_*.rb)
|
28
|
+
File.write(@extension_file, <<~RUBY)
|
29
|
+
class TestSafeDumpModel
|
30
|
+
# Define safe dump fields
|
31
|
+
safe_dump_fields :name, :email
|
32
|
+
|
33
|
+
# Add method to verify autoloading worked
|
34
|
+
def extension_loaded?
|
35
|
+
true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
RUBY
|
39
|
+
|
40
|
+
## Test that SafeDump includes Autoloadable
|
41
|
+
Familia::Features::SafeDump.ancestors.include?(Familia::Features::Autoloadable)
|
42
|
+
#=> true
|
43
|
+
|
44
|
+
## Test that SafeDump has post_inclusion_autoload capability
|
45
|
+
Familia::Features::SafeDump.respond_to?(:post_inclusion_autoload)
|
46
|
+
#=> true
|
47
|
+
|
48
|
+
## Test SafeDump autoloading by loading model file
|
49
|
+
@model_instance = nil
|
50
|
+
|
51
|
+
begin
|
52
|
+
require @model_file
|
53
|
+
@model_instance = TestSafeDumpModel.new(
|
54
|
+
name: 'John Doe',
|
55
|
+
email: 'john@example.com',
|
56
|
+
secret: 'hidden data'
|
57
|
+
)
|
58
|
+
true
|
59
|
+
rescue => e
|
60
|
+
false
|
61
|
+
end
|
62
|
+
#=> true
|
63
|
+
|
64
|
+
## Test that autoloaded extension method is available
|
65
|
+
@model_instance.respond_to?(:extension_loaded?)
|
66
|
+
#=> true
|
67
|
+
|
68
|
+
## Test autoloaded extension method works
|
69
|
+
@model_instance.extension_loaded?
|
70
|
+
#=> true
|
71
|
+
|
72
|
+
## Test that model was created successfully
|
73
|
+
@model_instance.class.name
|
74
|
+
#=> "TestSafeDumpModel"
|
75
|
+
|
76
|
+
## Test that feature_options were set up correctly
|
77
|
+
TestSafeDumpModel.respond_to?(:feature_options)
|
78
|
+
#=> true
|
79
|
+
|
80
|
+
## Test that safe_dump fields were loaded from extension file
|
81
|
+
TestSafeDumpModel.safe_dump_field_names.sort
|
82
|
+
#=> [:email, :name]
|
83
|
+
|
84
|
+
## Test that safe_dump functionality works with autoloaded fields
|
85
|
+
@dump_result = @model_instance.safe_dump
|
86
|
+
@dump_result.keys.sort
|
87
|
+
#=> [:email, :name]
|
88
|
+
|
89
|
+
## Test that only safe fields are dumped
|
90
|
+
@dump_result[:name]
|
91
|
+
#=> "John Doe"
|
92
|
+
|
93
|
+
## Test that email field is included
|
94
|
+
@dump_result[:email]
|
95
|
+
#=> "john@example.com"
|
96
|
+
|
97
|
+
## Test that secret field is excluded (not in safe_dump_fields)
|
98
|
+
@dump_result.key?(:secret)
|
99
|
+
#=> false
|
100
|
+
|
101
|
+
## Test that feature_options can be retrieved
|
102
|
+
@options = TestSafeDumpModel.feature_options(:safe_dump)
|
103
|
+
@options.is_a?(Hash)
|
104
|
+
#=> true
|
105
|
+
|
106
|
+
## Test safe_dump feature is recognized
|
107
|
+
TestSafeDumpModel.features_enabled.include?(:safe_dump)
|
108
|
+
#=> true
|
109
|
+
|
110
|
+
# Cleanup test files and directories
|
111
|
+
FileUtils.rm_rf(@test_dir)
|
@@ -15,12 +15,11 @@ class SafeDumpTest < Familia::Horreum
|
|
15
15
|
field :email
|
16
16
|
field :secret_data
|
17
17
|
|
18
|
-
@safe_dump_fields
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
]
|
18
|
+
# Use new DSL instead of @safe_dump_fields
|
19
|
+
safe_dump_field :id
|
20
|
+
safe_dump_field :name
|
21
|
+
safe_dump_field :display_name, ->(obj) { "#{obj.name} (#{obj.id})" }
|
22
|
+
safe_dump_field :has_email, ->(obj) { !obj.email.nil? && !obj.email.empty? }
|
24
23
|
|
25
24
|
def active?
|
26
25
|
true
|
@@ -47,9 +46,9 @@ SafeDumpTest.respond_to?(:safe_dump_field_map)
|
|
47
46
|
#=> true
|
48
47
|
|
49
48
|
## safe_dump_fields returns field names only
|
50
|
-
fields = SafeDumpTest.
|
51
|
-
fields
|
52
|
-
#=> [:
|
49
|
+
fields = SafeDumpTest.safe_dump_field_names
|
50
|
+
fields.sort
|
51
|
+
#=> [:display_name, :has_email, :id, :name]
|
53
52
|
|
54
53
|
## safe_dump_field_map returns callable map
|
55
54
|
field_map = SafeDumpTest.safe_dump_field_map
|