familia 2.0.0.pre14 → 2.0.0.pre15
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 +2 -2
- data/Gemfile.lock +1 -1
- data/docs/guides/Feature-System-Autoloading.md +3 -33
- data/docs/migrating/v2.0.0-pre11.md +2 -2
- data/docs/migrating/v2.0.0-pre13.md +1 -1
- data/lib/familia/{autoloader.rb → features/autoloader.rb} +33 -25
- data/lib/familia/features/encrypted_fields.rb +2 -2
- data/lib/familia/features/expiration/extensions.rb +61 -0
- data/lib/familia/features/expiration.rb +4 -61
- data/lib/familia/features/external_identifier.rb +3 -1
- data/lib/familia/features/object_identifier.rb +2 -1
- data/lib/familia/features/quantization.rb +2 -2
- data/lib/familia/features/relationships.rb +5 -6
- data/lib/familia/features/safe_dump.rb +3 -5
- data/lib/familia/features/transient_fields.rb +3 -1
- data/lib/familia/features.rb +20 -11
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +0 -1
- data/try/core/autoloader_try.rb +9 -9
- data/try/features/safe_dump/module_based_extensions_try.rb +100 -0
- data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -4
- metadata +4 -4
- data/lib/familia/features/autoloadable.rb +0 -113
- data/try/features/autoloadable/autoloadable_try.rb +0 -61
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 00416beac08786c1fffd75a0c2578669c6a0d18169824ca1a23e9122018a386b
|
4
|
+
data.tar.gz: c38f7bfe985a7d6151abf2ce4b6d7ca32ddb97adf8d069880f7e919662937851
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a3d6a60c86460578567fbd6f33e96c8ae0ee2dbfc80f3ffc6f123dc6fb84c6020efd359601122e63543c0128401d235587e9770df8acbbfa1f55cb908a012e07
|
7
|
+
data.tar.gz: 5062f93d7494df05a6d62dade395a0522ca8ab02afd4fdc5a00ee21db08b05ace8c3227d07739ef7cdd727fa72888ea4ec415491eb60028b25166d107d574e8e
|
data/CHANGELOG.rst
CHANGED
@@ -44,7 +44,7 @@ Added
|
|
44
44
|
|
45
45
|
- **Feature Autoloading System**: Features can now automatically discover and load extension files from your project directories. When you include a feature like ``safe_dump``, Familia searches for configuration files using conventional patterns like ``{model_name}/{feature_name}_*.rb``, enabling clean separation between core model definitions and feature-specific configurations. See ``docs/migrating/v2.0.0-pre13.md`` for migration details.
|
46
46
|
|
47
|
-
- **Consolidated autoloader architecture**: Introduced ``Familia::Autoloader`` as a shared utility for consistent file loading patterns across the framework, supporting both general-purpose and feature-specific autoloading scenarios.
|
47
|
+
- **Consolidated autoloader architecture**: Introduced ``Familia::Features::Autoloader`` as a shared utility for consistent file loading patterns across the framework, supporting both general-purpose and feature-specific autoloading scenarios.
|
48
48
|
|
49
49
|
- Added ``PER_MONTH`` constant (2,629,746 seconds = 30.437 days) derived from Gregorian year for consistent month calculations.
|
50
50
|
- Added ``months``, ``month``, and ``in_months`` conversion methods to Numeric refinement.
|
@@ -183,7 +183,7 @@ Added
|
|
183
183
|
with ancestry chain traversal for model-specific feature
|
184
184
|
registration. This enables better organization, standardized naming,
|
185
185
|
and automatic loading of project-specific features via the new
|
186
|
-
``Familia::Autoloader`` module.
|
186
|
+
``Familia::Features::Autoloader`` module.
|
187
187
|
- **Improved SafeDump DSL**: Replaced the internal
|
188
188
|
``@safe_dump_fields`` implementation with a cleaner, more robust DSL
|
189
189
|
using ``safe_dump_field`` and ``safe_dump_fields`` methods.
|
data/Gemfile.lock
CHANGED
@@ -44,7 +44,7 @@ end
|
|
44
44
|
|
45
45
|
The `post_inclusion_autoload` system works in **two phases**:
|
46
46
|
|
47
|
-
###
|
47
|
+
### Feature System Hook
|
48
48
|
|
49
49
|
In `lib/familia/features.rb`, after including the feature module:
|
50
50
|
|
@@ -61,29 +61,6 @@ def feature(feature_name, **options)
|
|
61
61
|
end
|
62
62
|
```
|
63
63
|
|
64
|
-
### Phase 2: Autoloadable Implementation
|
65
|
-
|
66
|
-
The `post_inclusion_autoload` method in `lib/familia/features/autoloadable.rb`:
|
67
|
-
|
68
|
-
1. **Gets the source location** of the user's class file using Ruby's introspection:
|
69
|
-
```ruby
|
70
|
-
location_info = Module.const_source_location(base.name)
|
71
|
-
source_location = location_info&.first # e.g., "/path/to/models/user.rb"
|
72
|
-
```
|
73
|
-
|
74
|
-
2. **Calculates extension file paths** based on conventions:
|
75
|
-
```ruby
|
76
|
-
base_dir = File.dirname(location_path) # "/path/to/models"
|
77
|
-
model_name = base.name.snake_case # "user"
|
78
|
-
|
79
|
-
# Look for files like:
|
80
|
-
patterns = [
|
81
|
-
"/path/to/models/user/safe_dump_*.rb",
|
82
|
-
"/path/to/models/user/features/safe_dump_*.rb",
|
83
|
-
"/path/to/models/features/safe_dump_*.rb"
|
84
|
-
]
|
85
|
-
```
|
86
|
-
|
87
64
|
3. **Loads any matching files** found in those locations
|
88
65
|
|
89
66
|
## Why This Timing Matters
|
@@ -199,17 +176,10 @@ The `post_inclusion_autoload` system provides a clean, automatic, and safe way t
|
|
199
176
|
|
200
177
|
## Implementation Details
|
201
178
|
|
202
|
-
###
|
179
|
+
### Autoloader
|
203
180
|
|
204
|
-
|
181
|
+
Looks for features files in models/features.rb, models/features/, models/model_name/features.rb, models/model_name/features/
|
205
182
|
|
206
|
-
```ruby
|
207
|
-
module Familia::Features::SafeDump
|
208
|
-
include Familia::Features::Autoloadable # ← Enables autoloading
|
209
|
-
|
210
|
-
# Feature implementation...
|
211
|
-
end
|
212
|
-
```
|
213
183
|
|
214
184
|
### Anonymous Class Handling
|
215
185
|
|
@@ -124,7 +124,7 @@ end
|
|
124
124
|
|
125
125
|
class Customer < Familia::Horreum
|
126
126
|
module Features
|
127
|
-
include Familia::Autoloader
|
127
|
+
include Familia::Features::Autoloader
|
128
128
|
# Automatically discovers and loads all *.rb files from customer/features/
|
129
129
|
end
|
130
130
|
end
|
@@ -210,7 +210,7 @@ If you have project-specific features, set up auto-loading:
|
|
210
210
|
module YourProject
|
211
211
|
class ModelName < Familia::Horreum
|
212
212
|
module Features
|
213
|
-
include Familia::Autoloader
|
213
|
+
include Familia::Features::Autoloader
|
214
214
|
end
|
215
215
|
end
|
216
216
|
end
|
@@ -68,7 +68,7 @@ Common issues:
|
|
68
68
|
|
69
69
|
The Feature Autoloading System consists of two key components:
|
70
70
|
|
71
|
-
### Familia::Autoloader
|
71
|
+
### Familia::Features::Autoloader
|
72
72
|
A utility module providing shared file loading functionality:
|
73
73
|
- Handles Dir.glob pattern matching and file loading
|
74
74
|
- Provides consistent debug logging across all autoloading scenarios
|
@@ -1,11 +1,41 @@
|
|
1
|
-
#
|
1
|
+
# lib/familia/features/autoloader.rb
|
2
2
|
|
3
|
-
module Familia
|
3
|
+
module Familia::Features
|
4
4
|
# Provides autoloading functionality for Ruby files based on patterns and conventions.
|
5
5
|
#
|
6
6
|
# Used by the Features module at library startup to load feature files, and available
|
7
7
|
# as a utility for other modules requiring file autoloading capabilities.
|
8
8
|
module Autoloader
|
9
|
+
using Familia::Refinements::SnakeCase
|
10
|
+
|
11
|
+
# Autoloads feature files when this module is included.
|
12
|
+
#
|
13
|
+
# Discovers and loads all Ruby files in the features/ directory relative to the
|
14
|
+
# including module's location. Typically used by Familia::Features.
|
15
|
+
#
|
16
|
+
# @param base [Module] the module including this autoloader
|
17
|
+
def self.included(base)
|
18
|
+
|
19
|
+
# Get the directory where the including module is defined
|
20
|
+
# This should be lib/familia for the Features module
|
21
|
+
base_path = File.dirname(caller_locations(1, 1).first.path)
|
22
|
+
model_name = base.name.snake_case
|
23
|
+
dir_patterns = [
|
24
|
+
File.join(base_path, 'features', '*.rb'),
|
25
|
+
File.join(base_path, model_name, 'features', '*.rb'),
|
26
|
+
File.join(base_path, model_name, 'features.rb'),
|
27
|
+
]
|
28
|
+
|
29
|
+
# Ensure the Features module exists within the base module
|
30
|
+
unless base.const_defined?(:Features) || model_name.eql?('features')
|
31
|
+
base.const_set(:Features, Module.new)
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
# Use the shared autoload_files method
|
36
|
+
autoload_files(dir_patterns, log_prefix: "Autoloader[#{model_name}]")
|
37
|
+
end
|
38
|
+
|
9
39
|
# Autoloads Ruby files matching the given patterns.
|
10
40
|
#
|
11
41
|
# @param patterns [String, Array<String>] file patterns to match (supports Dir.glob patterns)
|
@@ -15,6 +45,7 @@ module Familia
|
|
15
45
|
patterns = Array(patterns)
|
16
46
|
|
17
47
|
patterns.each do |pattern|
|
48
|
+
Familia.ld "[#{log_prefix}] Autoloader loading features from #{pattern}"
|
18
49
|
Dir.glob(pattern).each do |file_path|
|
19
50
|
basename = File.basename(file_path)
|
20
51
|
|
@@ -26,28 +57,5 @@ module Familia
|
|
26
57
|
end
|
27
58
|
end
|
28
59
|
end
|
29
|
-
|
30
|
-
# Autoloads feature files when this module is included.
|
31
|
-
#
|
32
|
-
# Discovers and loads all Ruby files in the features/ directory relative to the
|
33
|
-
# including module's location. Typically used by Familia::Features.
|
34
|
-
#
|
35
|
-
# @param base [Module] the module including this autoloader
|
36
|
-
def self.included(base)
|
37
|
-
# Get the directory where the including module is defined
|
38
|
-
# This should be lib/familia for the Features module
|
39
|
-
base_path = File.dirname(caller_locations(1, 1).first.path)
|
40
|
-
features_dir = File.join(base_path, 'features')
|
41
|
-
|
42
|
-
Familia.ld "[DEBUG] Autoloader loading features from #{features_dir}"
|
43
|
-
|
44
|
-
return unless Dir.exist?(features_dir)
|
45
|
-
|
46
|
-
# Use the shared autoload_files method
|
47
|
-
autoload_files(
|
48
|
-
File.join(features_dir, '*.rb'),
|
49
|
-
log_prefix: 'Autoloader'
|
50
|
-
)
|
51
|
-
end
|
52
60
|
end
|
53
61
|
end
|
@@ -255,6 +255,8 @@ module Familia
|
|
255
255
|
# - Insider threats with application access
|
256
256
|
#
|
257
257
|
module EncryptedFields
|
258
|
+
Familia::Base.add_feature self, :encrypted_fields
|
259
|
+
|
258
260
|
def self.included(base)
|
259
261
|
Familia.trace :LOADED, self, base, caller(1..1) if Familia.debug?
|
260
262
|
base.extend ClassMethods
|
@@ -430,8 +432,6 @@ module Familia
|
|
430
432
|
end
|
431
433
|
end
|
432
434
|
end
|
433
|
-
|
434
|
-
Familia::Base.add_feature self, :encrypted_fields
|
435
435
|
end
|
436
436
|
end
|
437
437
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# lib/familia/features/expiration/extensions.rb
|
2
|
+
|
3
|
+
module Familia
|
4
|
+
# Add a default update_expiration method for all classes that include
|
5
|
+
# Familia::Base. Since expiration is a core feature, we can confidently
|
6
|
+
# call `horreum_instance.update_expiration` without defensive programming
|
7
|
+
# even when expiration is not enabled for the horreum_instance class.
|
8
|
+
module Base
|
9
|
+
# Base implementation of update_expiration that maintains API compatibility
|
10
|
+
# with the :expiration feature's implementation.
|
11
|
+
#
|
12
|
+
# This is a no-op implementation that gets overridden by the :expiration
|
13
|
+
# feature. It accepts an optional default_expiration parameter to maintain
|
14
|
+
# interface compatibility with the overriding implementations.
|
15
|
+
#
|
16
|
+
# @param default_expiration [Numeric, nil] Time To Live in seconds
|
17
|
+
# @return [nil] Always returns nil for the base implementation
|
18
|
+
#
|
19
|
+
# @note This is a no-op implementation. Classes that need expiration
|
20
|
+
# functionality should include the :expiration feature.
|
21
|
+
#
|
22
|
+
# @example Enable expiration feature
|
23
|
+
# class MyModel < Familia::Horreum
|
24
|
+
# feature :expiration
|
25
|
+
# default_expiration 1.hour
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
def update_expiration(default_expiration: nil)
|
29
|
+
Familia.ld <<~LOG
|
30
|
+
[update_expiration] Expiration feature not enabled for #{self.class}.
|
31
|
+
Key: #{dbkey} Arg: #{default_expiration} (caller: #{caller(1..1)})
|
32
|
+
LOG
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
# Base implementation of ttl that returns -1 (no expiration set)
|
37
|
+
#
|
38
|
+
# @return [Integer] Always returns -1 for the base implementation
|
39
|
+
#
|
40
|
+
def ttl
|
41
|
+
-1
|
42
|
+
end
|
43
|
+
|
44
|
+
# Base implementation of expires? that returns false
|
45
|
+
#
|
46
|
+
# @return [Boolean] Always returns false for the base implementation
|
47
|
+
#
|
48
|
+
def expires?
|
49
|
+
false
|
50
|
+
end
|
51
|
+
|
52
|
+
# Base implementation of expired? that returns false
|
53
|
+
#
|
54
|
+
# @param threshold [Numeric] Ignored in base implementation
|
55
|
+
# @return [Boolean] Always returns false for the base implementation
|
56
|
+
#
|
57
|
+
def expired?(_threshold = 0)
|
58
|
+
false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# lib/familia/features/expiration.rb
|
2
2
|
|
3
|
+
require_relative 'expiration/extensions'
|
4
|
+
|
3
5
|
module Familia
|
4
6
|
module Features
|
5
7
|
# Expiration is a feature that provides Time To Live (TTL) management for Familia
|
@@ -149,6 +151,8 @@ module Familia
|
|
149
151
|
module Expiration
|
150
152
|
@default_expiration = nil
|
151
153
|
|
154
|
+
Familia::Base.add_feature self, :expiration
|
155
|
+
|
152
156
|
using Familia::Refinements::TimeLiterals
|
153
157
|
|
154
158
|
def self.included(base)
|
@@ -354,67 +358,6 @@ module Familia
|
|
354
358
|
redis.persist(dbkey)
|
355
359
|
end
|
356
360
|
|
357
|
-
Familia::Base.add_feature self, :expiration
|
358
|
-
end
|
359
|
-
end
|
360
|
-
end
|
361
|
-
|
362
|
-
module Familia
|
363
|
-
# Add a default update_expiration method for all classes that include
|
364
|
-
# Familia::Base. Since expiration is a core feature, we can confidently
|
365
|
-
# call `horreum_instance.update_expiration` without defensive programming
|
366
|
-
# even when expiration is not enabled for the horreum_instance class.
|
367
|
-
module Base
|
368
|
-
# Base implementation of update_expiration that maintains API compatibility
|
369
|
-
# with the :expiration feature's implementation.
|
370
|
-
#
|
371
|
-
# This is a no-op implementation that gets overridden by the :expiration
|
372
|
-
# feature. It accepts an optional default_expiration parameter to maintain
|
373
|
-
# interface compatibility with the overriding implementations.
|
374
|
-
#
|
375
|
-
# @param default_expiration [Numeric, nil] Time To Live in seconds
|
376
|
-
# @return [nil] Always returns nil for the base implementation
|
377
|
-
#
|
378
|
-
# @note This is a no-op implementation. Classes that need expiration
|
379
|
-
# functionality should include the :expiration feature.
|
380
|
-
#
|
381
|
-
# @example Enable expiration feature
|
382
|
-
# class MyModel < Familia::Horreum
|
383
|
-
# feature :expiration
|
384
|
-
# default_expiration 1.hour
|
385
|
-
# end
|
386
|
-
#
|
387
|
-
def update_expiration(default_expiration: nil)
|
388
|
-
Familia.ld <<~LOG
|
389
|
-
[update_expiration] Expiration feature not enabled for #{self.class}.
|
390
|
-
Key: #{dbkey} Arg: #{default_expiration} (caller: #{caller(1..1)})
|
391
|
-
LOG
|
392
|
-
nil
|
393
|
-
end
|
394
|
-
|
395
|
-
# Base implementation of ttl that returns -1 (no expiration set)
|
396
|
-
#
|
397
|
-
# @return [Integer] Always returns -1 for the base implementation
|
398
|
-
#
|
399
|
-
def ttl
|
400
|
-
-1
|
401
|
-
end
|
402
|
-
|
403
|
-
# Base implementation of expires? that returns false
|
404
|
-
#
|
405
|
-
# @return [Boolean] Always returns false for the base implementation
|
406
|
-
#
|
407
|
-
def expires?
|
408
|
-
false
|
409
|
-
end
|
410
|
-
|
411
|
-
# Base implementation of expired? that returns false
|
412
|
-
#
|
413
|
-
# @param threshold [Numeric] Ignored in base implementation
|
414
|
-
# @return [Boolean] Always returns false for the base implementation
|
415
|
-
#
|
416
|
-
def expired?(_threshold = 0)
|
417
|
-
false
|
418
361
|
end
|
419
362
|
end
|
420
363
|
end
|
@@ -5,6 +5,9 @@ module Familia
|
|
5
5
|
# Familia::Features::ExternalIdentifier
|
6
6
|
#
|
7
7
|
module ExternalIdentifier
|
8
|
+
|
9
|
+
Familia::Base.add_feature self, :external_identifier, depends_on: [:object_identifier]
|
10
|
+
|
8
11
|
def self.included(base)
|
9
12
|
Familia.trace :LOADED, self, base, caller(1..1) if Familia.debug?
|
10
13
|
base.extend ClassMethods
|
@@ -304,7 +307,6 @@ module Familia
|
|
304
307
|
end
|
305
308
|
end
|
306
309
|
|
307
|
-
Familia::Base.add_feature self, :external_identifier, depends_on: [:object_identifier]
|
308
310
|
end
|
309
311
|
end
|
310
312
|
end
|
@@ -81,6 +81,8 @@ module Familia
|
|
81
81
|
# - Custom generators allow domain-specific security requirements
|
82
82
|
#
|
83
83
|
module ObjectIdentifier
|
84
|
+
Familia::Base.add_feature self, :object_identifier, depends_on: []
|
85
|
+
|
84
86
|
DEFAULT_GENERATOR = :uuid_v7
|
85
87
|
|
86
88
|
def self.included(base)
|
@@ -301,7 +303,6 @@ module Familia
|
|
301
303
|
Familia.trace :OBJID_INIT, dbclient, "Generator strategy: #{generator}", caller(1..1)
|
302
304
|
end
|
303
305
|
|
304
|
-
Familia::Base.add_feature self, :object_identifier, depends_on: []
|
305
306
|
end
|
306
307
|
end
|
307
308
|
end
|
@@ -246,6 +246,8 @@ module Familia
|
|
246
246
|
#
|
247
247
|
module Quantization
|
248
248
|
|
249
|
+
Familia::Base.add_feature self, :quantization
|
250
|
+
|
249
251
|
using Familia::Refinements::TimeLiterals
|
250
252
|
|
251
253
|
def self.included(base)
|
@@ -397,8 +399,6 @@ module Familia
|
|
397
399
|
end
|
398
400
|
|
399
401
|
extend ClassMethods
|
400
|
-
|
401
|
-
Familia::Base.add_feature self, :quantization
|
402
402
|
end
|
403
403
|
end
|
404
404
|
end
|
@@ -98,9 +98,13 @@ module Familia
|
|
98
98
|
# { owner: team, collection: :domains }
|
99
99
|
# ], min_permission: :read)
|
100
100
|
module Relationships
|
101
|
+
|
102
|
+
# Register the feature with Familia
|
103
|
+
Familia::Base.add_feature Relationships, :relationships
|
104
|
+
|
101
105
|
# Feature initialization
|
102
106
|
def self.included(base)
|
103
|
-
|
107
|
+
Familia.ld "[#{base}] Relationships included"
|
104
108
|
base.extend ClassMethods
|
105
109
|
base.include InstanceMethods
|
106
110
|
|
@@ -108,11 +112,8 @@ module Familia
|
|
108
112
|
base.include ScoreEncoding
|
109
113
|
base.include RedisOperations
|
110
114
|
|
111
|
-
puts '[DEBUG] Including Tracking module'
|
112
115
|
base.include Tracking
|
113
|
-
puts '[DEBUG] Extending with Tracking::ClassMethods'
|
114
116
|
base.extend Tracking::ClassMethods
|
115
|
-
puts "[DEBUG] Base now responds to tracked_in: #{base.respond_to?(:tracked_in)}"
|
116
117
|
|
117
118
|
base.include Indexing
|
118
119
|
base.extend Indexing::ClassMethods
|
@@ -466,8 +467,6 @@ module Familia
|
|
466
467
|
end
|
467
468
|
end
|
468
469
|
|
469
|
-
# Register the feature with Familia
|
470
|
-
Familia::Base.add_feature Relationships, :relationships
|
471
470
|
end
|
472
471
|
end
|
473
472
|
end
|
@@ -41,15 +41,15 @@ module Familia::Features
|
|
41
41
|
# of symbols in the order they were defined.
|
42
42
|
#
|
43
43
|
module SafeDump
|
44
|
-
|
44
|
+
|
45
|
+
Familia::Base.add_feature self, :safe_dump
|
46
|
+
|
45
47
|
using Familia::Refinements::SnakeCase
|
46
48
|
|
47
49
|
@dump_method = :to_json
|
48
50
|
@load_method = :from_json
|
49
51
|
|
50
52
|
def self.included(base)
|
51
|
-
# Call the Autoloadable module's included method for post-inclusion setup
|
52
|
-
super
|
53
53
|
|
54
54
|
Familia.trace(:LOADED, self, base, caller(1..1)) if Familia.debug?
|
55
55
|
base.extend ClassMethods
|
@@ -153,8 +153,6 @@ module Familia::Features
|
|
153
153
|
end
|
154
154
|
|
155
155
|
extend ClassMethods
|
156
|
-
|
157
|
-
Familia::Base.add_feature self, :safe_dump
|
158
156
|
end
|
159
157
|
end
|
160
158
|
# rubocop:enable ThreadSafety/ClassInstanceVariable
|
@@ -104,6 +104,9 @@ module Familia
|
|
104
104
|
# (HashiCorp Vault, AWS Secrets Manager) or languages with secure memory handling.
|
105
105
|
#
|
106
106
|
module TransientFields
|
107
|
+
|
108
|
+
Familia::Base.add_feature self, :transient_fields, depends_on: nil
|
109
|
+
|
107
110
|
def self.included(base)
|
108
111
|
Familia.trace :LOADED, self, base, caller(1..1) if Familia.debug?
|
109
112
|
base.extend ClassMethods
|
@@ -221,7 +224,6 @@ module Familia
|
|
221
224
|
end
|
222
225
|
end
|
223
226
|
|
224
|
-
Familia::Base.add_feature self, :transient_fields, depends_on: nil
|
225
227
|
end
|
226
228
|
end
|
227
229
|
end
|
data/lib/familia/features.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# lib/familia/features.rb
|
2
2
|
|
3
3
|
# Load the Autoloader first, then use it to load all other features
|
4
|
-
require_relative 'autoloader'
|
4
|
+
require_relative 'features/autoloader'
|
5
5
|
|
6
6
|
module Familia
|
7
7
|
FeatureDefinition = Data.define(:name, :depends_on)
|
@@ -25,12 +25,21 @@ module Familia
|
|
25
25
|
# feature options. When you enable a feature with options in different models,
|
26
26
|
# each model stores its own separate configuration without interference.
|
27
27
|
#
|
28
|
-
# ## Project Organization with
|
28
|
+
# ## Project Organization with Autoloader
|
29
29
|
#
|
30
|
-
# For large projects, use {Familia::Features::
|
30
|
+
# For large projects, use {Familia::Features::Autoloader} to automatically load
|
31
31
|
# project-specific features from a dedicated directory structure. This helps
|
32
32
|
# organize complex models by separating features into individual files.
|
33
33
|
#
|
34
|
+
# ### Class Reopening (Deprecated)
|
35
|
+
#
|
36
|
+
# Direct class reopening still works but generates deprecation warnings:
|
37
|
+
#
|
38
|
+
# # app/models/customer/safe_dump_extensions.rb
|
39
|
+
# class Customer
|
40
|
+
# safe_dump_fields :name, :email # Works but not recommended
|
41
|
+
# end
|
42
|
+
#
|
34
43
|
# @example Different models with different feature options
|
35
44
|
# class UserModel < Familia::Horreum
|
36
45
|
# feature :object_identifier, generator: :uuid_v4
|
@@ -57,15 +66,15 @@ module Familia
|
|
57
66
|
# # In your model file: app/models/customer.rb
|
58
67
|
# class Customer < Familia::Horreum
|
59
68
|
# module Features
|
60
|
-
# include Familia::Features::
|
69
|
+
# include Familia::Features::Autoloader
|
61
70
|
# # Automatically loads all .rb files from app/models/customer/features/
|
62
71
|
# end
|
63
72
|
# end
|
64
73
|
#
|
65
|
-
# @see Familia::Features::
|
74
|
+
# @see Familia::Features::Autoloader For automatic feature loading
|
66
75
|
#
|
67
76
|
module Features
|
68
|
-
include Familia::Autoloader
|
77
|
+
include Familia::Features::Autoloader
|
69
78
|
|
70
79
|
@features_enabled = nil
|
71
80
|
attr_reader :features_enabled
|
@@ -110,8 +119,8 @@ module Familia
|
|
110
119
|
|
111
120
|
# If there's a value provided check that it's a valid feature
|
112
121
|
feature_name = feature_name.to_sym
|
113
|
-
|
114
|
-
unless
|
122
|
+
feature_module = Familia::Base.find_feature(feature_name, self)
|
123
|
+
unless feature_module
|
115
124
|
raise Familia::Problem, "Unsupported feature: #{feature_name}"
|
116
125
|
end
|
117
126
|
|
@@ -146,11 +155,11 @@ module Familia
|
|
146
155
|
end
|
147
156
|
|
148
157
|
# Extend the Familia::Base subclass (e.g. Customer) with the feature module
|
149
|
-
include
|
158
|
+
include feature_module
|
150
159
|
|
151
160
|
# Trigger post-inclusion autoloading for features that support it
|
152
|
-
if
|
153
|
-
|
161
|
+
if feature_module.respond_to?(:post_inclusion_autoload)
|
162
|
+
feature_module.post_inclusion_autoload(self, feature_name, options)
|
154
163
|
end
|
155
164
|
|
156
165
|
# NOTE: Do we want to extend Familia::DataType here? That would make it
|
data/lib/familia/version.rb
CHANGED
data/lib/familia.rb
CHANGED
data/try/core/autoloader_try.rb
CHANGED
@@ -30,16 +30,16 @@ File.write(@excluded_file, <<~RUBY)
|
|
30
30
|
$autoloader_file_loaded = true
|
31
31
|
RUBY
|
32
32
|
|
33
|
-
## Test that Familia::Autoloader exists and is a module
|
34
|
-
Familia::Autoloader.is_a?(Module)
|
33
|
+
## Test that Familia::Features::Autoloader exists and is a module
|
34
|
+
Familia::Features::Autoloader.is_a?(Module)
|
35
35
|
#=> true
|
36
36
|
|
37
37
|
## Test that autoload_files class method exists
|
38
|
-
Familia::Autoloader.respond_to?(:autoload_files)
|
38
|
+
Familia::Features::Autoloader.respond_to?(:autoload_files)
|
39
39
|
#=> true
|
40
40
|
|
41
41
|
## Test that included class method exists
|
42
|
-
Familia::Autoloader.respond_to?(:included)
|
42
|
+
Familia::Features::Autoloader.respond_to?(:included)
|
43
43
|
#=> true
|
44
44
|
|
45
45
|
## Test autoload_files with single pattern
|
@@ -47,7 +47,7 @@ $test_feature1_loaded = false
|
|
47
47
|
$test_feature2_loaded = false
|
48
48
|
$autoloader_file_loaded = false
|
49
49
|
|
50
|
-
Familia::Autoloader.autoload_files(File.join(@features_dir, '*.rb'))
|
50
|
+
Familia::Features::Autoloader.autoload_files(File.join(@features_dir, '*.rb'))
|
51
51
|
$test_feature1_loaded && $test_feature2_loaded
|
52
52
|
#=> true
|
53
53
|
|
@@ -64,7 +64,7 @@ File.write(@exclude_file, '$exclude_me_loaded = true')
|
|
64
64
|
$include_me_loaded = false
|
65
65
|
$exclude_me_loaded = false
|
66
66
|
|
67
|
-
Familia::Autoloader.autoload_files(
|
67
|
+
Familia::Features::Autoloader.autoload_files(
|
68
68
|
File.join(@exclude_features_dir, '*.rb'),
|
69
69
|
exclude: ['autoloader.rb']
|
70
70
|
)
|
@@ -88,7 +88,7 @@ File.write(@pattern_file2, '$pattern2_loaded = true')
|
|
88
88
|
$pattern1_loaded = false
|
89
89
|
$pattern2_loaded = false
|
90
90
|
|
91
|
-
Familia::Autoloader.autoload_files([
|
91
|
+
Familia::Features::Autoloader.autoload_files([
|
92
92
|
File.join(@pattern_dir1, '*.rb'),
|
93
93
|
File.join(@pattern_dir2, '*.rb')
|
94
94
|
])
|
@@ -99,11 +99,11 @@ $pattern1_loaded && $pattern2_loaded
|
|
99
99
|
## Test that included method loads features from features directory
|
100
100
|
# Create a mock module that includes Autoloader
|
101
101
|
@mock_features_module = Module.new do
|
102
|
-
include Familia::Autoloader
|
102
|
+
include Familia::Features::Autoloader
|
103
103
|
end
|
104
104
|
|
105
105
|
# The Features module already includes Autoloader, so test indirectly
|
106
|
-
Familia::Features.ancestors.include?(Familia::Autoloader)
|
106
|
+
Familia::Features.ancestors.include?(Familia::Features::Autoloader)
|
107
107
|
#=> true
|
108
108
|
|
109
109
|
# Cleanup test files and directories
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# try/features/safe_dump/module_based_extensions_try.rb
|
2
|
+
|
3
|
+
require_relative '../../../lib/familia'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'tmpdir'
|
6
|
+
|
7
|
+
# Create test directory structure for module-based SafeDump extensions
|
8
|
+
@test_dir = Dir.mktmpdir('familia_module_extensions_test')
|
9
|
+
@model_file = File.join(@test_dir, 'test_model.rb')
|
10
|
+
@extension_file = File.join(@test_dir, 'test_model', 'safe_dump_extensions.rb')
|
11
|
+
@extension_dir = File.join(@test_dir, 'test_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 TestModel < 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 using NEW module-based pattern
|
28
|
+
File.write(@extension_file, <<~RUBY)
|
29
|
+
module TestModel::SafeDumpExtensions
|
30
|
+
def self.included(base)
|
31
|
+
# Define safe dump fields using the DSL
|
32
|
+
base.safe_dump_fields :name, :email
|
33
|
+
|
34
|
+
# Add computed field
|
35
|
+
base.safe_dump_field :display_name, ->(obj) { "\#{obj.name} <\#{obj.email}>" }
|
36
|
+
end
|
37
|
+
|
38
|
+
# Add instance method to verify module inclusion
|
39
|
+
def module_extension_loaded?
|
40
|
+
true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
RUBY
|
44
|
+
|
45
|
+
## Test module-based autoloading by loading model file
|
46
|
+
@model_instance = nil
|
47
|
+
|
48
|
+
begin
|
49
|
+
# Add test directory to load path for extension file loading
|
50
|
+
$LOAD_PATH.unshift(@test_dir)
|
51
|
+
|
52
|
+
require @model_file
|
53
|
+
@model_instance = TestModel.new(
|
54
|
+
name: 'Jane Doe',
|
55
|
+
email: 'jane@example.com',
|
56
|
+
secret: 'top secret'
|
57
|
+
)
|
58
|
+
true
|
59
|
+
rescue => e
|
60
|
+
puts "Error: #{e.message}"
|
61
|
+
false
|
62
|
+
end
|
63
|
+
#=> true
|
64
|
+
|
65
|
+
## Test that module extension method is available
|
66
|
+
@model_instance.respond_to?(:module_extension_loaded?)
|
67
|
+
#=> true
|
68
|
+
|
69
|
+
## Test module extension method works
|
70
|
+
@model_instance.module_extension_loaded?
|
71
|
+
#=> true
|
72
|
+
|
73
|
+
## Test that safe_dump fields were loaded from module extension
|
74
|
+
TestModel.safe_dump_field_names.sort
|
75
|
+
#=> [:display_name, :email, :name]
|
76
|
+
|
77
|
+
## Test that safe_dump functionality works with module-loaded fields
|
78
|
+
@dump_result = @model_instance.safe_dump
|
79
|
+
@dump_result.keys.sort
|
80
|
+
#=> [:display_name, :email, :name]
|
81
|
+
|
82
|
+
## Test basic field values
|
83
|
+
[@dump_result[:name], @dump_result[:email]]
|
84
|
+
#=> ["Jane Doe", "jane@example.com"]
|
85
|
+
|
86
|
+
## Test computed field from module
|
87
|
+
@dump_result[:display_name]
|
88
|
+
#=> "Jane Doe <jane@example.com>"
|
89
|
+
|
90
|
+
## Test that secret field is excluded
|
91
|
+
@dump_result.key?(:secret)
|
92
|
+
#=> false
|
93
|
+
|
94
|
+
## Test that the module was actually included (not just loaded)
|
95
|
+
TestModel.included_modules.any? { |mod| mod.name&.include?('SafeDumpExtensions') }
|
96
|
+
#=> true
|
97
|
+
|
98
|
+
# Cleanup test files and directories
|
99
|
+
FileUtils.rm_rf(@test_dir)
|
100
|
+
$LOAD_PATH.shift if $LOAD_PATH.first == @test_dir
|
@@ -37,10 +37,6 @@ File.write(@extension_file, <<~RUBY)
|
|
37
37
|
end
|
38
38
|
RUBY
|
39
39
|
|
40
|
-
## Test that SafeDump includes Autoloadable
|
41
|
-
Familia::Features::SafeDump.ancestors.include?(Familia::Features::Autoloadable)
|
42
|
-
#=> true
|
43
|
-
|
44
40
|
## Test that SafeDump has post_inclusion_autoload capability
|
45
41
|
Familia::Features::SafeDump.respond_to?(:post_inclusion_autoload)
|
46
42
|
#=> 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.0.0.
|
4
|
+
version: 2.0.0.pre15
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Delano Mandelbaum
|
@@ -193,7 +193,6 @@ files:
|
|
193
193
|
- examples/safe_dump.rb
|
194
194
|
- familia.gemspec
|
195
195
|
- lib/familia.rb
|
196
|
-
- lib/familia/autoloader.rb
|
197
196
|
- lib/familia/base.rb
|
198
197
|
- lib/familia/connection.rb
|
199
198
|
- lib/familia/data_type.rb
|
@@ -217,11 +216,12 @@ files:
|
|
217
216
|
- lib/familia/encryption/request_cache.rb
|
218
217
|
- lib/familia/errors.rb
|
219
218
|
- lib/familia/features.rb
|
220
|
-
- lib/familia/features/
|
219
|
+
- lib/familia/features/autoloader.rb
|
221
220
|
- lib/familia/features/encrypted_fields.rb
|
222
221
|
- lib/familia/features/encrypted_fields/concealed_string.rb
|
223
222
|
- lib/familia/features/encrypted_fields/encrypted_field_type.rb
|
224
223
|
- lib/familia/features/expiration.rb
|
224
|
+
- lib/familia/features/expiration/extensions.rb
|
225
225
|
- lib/familia/features/external_identifier.rb
|
226
226
|
- lib/familia/features/object_identifier.rb
|
227
227
|
- lib/familia/features/quantization.rb
|
@@ -336,7 +336,6 @@ files:
|
|
336
336
|
- try/encryption/providers/xchacha20_poly1305_provider_try.rb
|
337
337
|
- try/encryption/roundtrip_validation_try.rb
|
338
338
|
- try/encryption/secure_memory_handling_try.rb
|
339
|
-
- try/features/autoloadable/autoloadable_try.rb
|
340
339
|
- try/features/encrypted_fields/aad_protection_try.rb
|
341
340
|
- try/features/encrypted_fields/concealed_string_core_try.rb
|
342
341
|
- try/features/encrypted_fields/context_isolation_try.rb
|
@@ -370,6 +369,7 @@ files:
|
|
370
369
|
- try/features/relationships/relationships_performance_try.rb
|
371
370
|
- try/features/relationships/relationships_performance_working_try.rb
|
372
371
|
- try/features/relationships/relationships_try.rb
|
372
|
+
- try/features/safe_dump/module_based_extensions_try.rb
|
373
373
|
- try/features/safe_dump/safe_dump_advanced_try.rb
|
374
374
|
- try/features/safe_dump/safe_dump_autoloading_try.rb
|
375
375
|
- try/features/safe_dump/safe_dump_try.rb
|
@@ -1,113 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../refinements/snake_case'
|
4
|
-
|
5
|
-
module Familia
|
6
|
-
module Features
|
7
|
-
# Enables automatic loading of feature-specific files when a feature is included in a user class.
|
8
|
-
#
|
9
|
-
# When included in a feature module, adds ClassMethods that detect when the feature is
|
10
|
-
# included in user classes, derives the feature name, and autoloads files matching
|
11
|
-
# conventional patterns in the user class's directory structure.
|
12
|
-
module Autoloadable
|
13
|
-
using Familia::Refinements::SnakeCase
|
14
|
-
|
15
|
-
# Sets up a feature module with autoloading capabilities.
|
16
|
-
#
|
17
|
-
# Extends the feature module with ClassMethods to handle post-inclusion autoloading.
|
18
|
-
#
|
19
|
-
# @param feature_module [Module] the feature module being enhanced
|
20
|
-
def self.included(feature_module)
|
21
|
-
feature_module.extend(ClassMethods)
|
22
|
-
end
|
23
|
-
|
24
|
-
# Methods added to feature modules that include Autoloadable.
|
25
|
-
module ClassMethods
|
26
|
-
# Triggered when the feature is included in a user class.
|
27
|
-
#
|
28
|
-
# Sets up for post-inclusion autoloading. The actual autoloading
|
29
|
-
# is deferred until after feature setup completes.
|
30
|
-
#
|
31
|
-
# @param base [Class] the user class including this feature
|
32
|
-
def included(base)
|
33
|
-
# Call parent included method if it exists (defensive programming for mixed-in contexts)
|
34
|
-
super if defined?(super)
|
35
|
-
|
36
|
-
# No autoloading here - it's deferred to post_inclusion_autoload
|
37
|
-
# to ensure the feature is fully set up before extension files are loaded
|
38
|
-
end
|
39
|
-
|
40
|
-
# Called by the feature system after the feature is fully included.
|
41
|
-
#
|
42
|
-
# Uses const_source_location to determine where the base class is defined,
|
43
|
-
# then autoloads feature-specific extension files from that location.
|
44
|
-
#
|
45
|
-
# @param base [Class] the class that included this feature
|
46
|
-
# @param feature_name [Symbol] the name of the feature
|
47
|
-
# @param options [Hash] feature options (unused but kept for compatibility)
|
48
|
-
def post_inclusion_autoload(base, feature_name, options)
|
49
|
-
Familia.trace :FEATURE, nil, "[Autoloadable] post_inclusion_autoload called for #{feature_name} on #{base.name || base}", caller(1..1) if Familia.debug?
|
50
|
-
|
51
|
-
# Get the source location via Ruby's built-in introspection
|
52
|
-
source_location = nil
|
53
|
-
|
54
|
-
# Check for named classes that can be looked up via const_source_location
|
55
|
-
# Class#name always returns String or nil, so type check is redundant
|
56
|
-
if base.name && !base.name.empty?
|
57
|
-
begin
|
58
|
-
location_info = Module.const_source_location(base.name)
|
59
|
-
source_location = location_info&.first
|
60
|
-
Familia.trace :FEATURE, nil, "[Autoloadable] Source location for #{base.name}: #{source_location}", caller(1..1) if Familia.debug?
|
61
|
-
rescue NameError => e
|
62
|
-
# Handle cases where the class name is not a valid constant name
|
63
|
-
# This can happen in test environments with dynamically created classes
|
64
|
-
Familia.trace :FEATURE, nil, "[Autoloadable] Cannot resolve source location for #{base.name}: #{e.message}", caller(1..1) if Familia.debug?
|
65
|
-
end
|
66
|
-
else
|
67
|
-
Familia.trace :FEATURE, nil, "[Autoloadable] Skipping source location detection - base.name=#{base.name.inspect}", caller(1..1) if Familia.debug?
|
68
|
-
end
|
69
|
-
|
70
|
-
# Autoload feature-specific files if we have a valid source location
|
71
|
-
if source_location && !source_location.include?('-e') # Skip eval/irb contexts
|
72
|
-
Familia.trace :FEATURE, nil, "[Autoloadable] Calling autoload_feature_files with #{source_location}", caller(1..1) if Familia.debug?
|
73
|
-
autoload_feature_files(source_location, base, feature_name.to_s.snake_case)
|
74
|
-
else
|
75
|
-
Familia.trace :FEATURE, nil, "[Autoloadable] Skipping autoload - no valid source location", caller(1..1) if Familia.debug?
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
private
|
80
|
-
|
81
|
-
# Autoloads feature-specific files from conventional directory patterns.
|
82
|
-
#
|
83
|
-
# Searches for files matching patterns like:
|
84
|
-
# - model_name/feature_name_*.rb
|
85
|
-
# - model_name/features/feature_name_*.rb
|
86
|
-
# - features/feature_name_*.rb
|
87
|
-
#
|
88
|
-
# @param location_path [String] path where the user class is defined
|
89
|
-
# @param base [Class] the user class including the feature
|
90
|
-
# @param feature_name [String] snake_case name of the feature
|
91
|
-
def autoload_feature_files(location_path, base, feature_name)
|
92
|
-
base_dir = File.dirname(location_path)
|
93
|
-
|
94
|
-
# Handle anonymous classes gracefully
|
95
|
-
model_name = base.name ? base.name.snake_case : "anonymous_#{base.object_id}"
|
96
|
-
|
97
|
-
# Look for feature-specific files in conventional locations
|
98
|
-
patterns = [
|
99
|
-
File.join(base_dir, model_name, "#{feature_name}_*.rb"),
|
100
|
-
File.join(base_dir, model_name, 'features', "#{feature_name}_*.rb"),
|
101
|
-
File.join(base_dir, 'features', "#{feature_name}_*.rb"),
|
102
|
-
]
|
103
|
-
|
104
|
-
# Use Autoloader's shared method for consistent file loading
|
105
|
-
Familia::Autoloader.autoload_files(
|
106
|
-
patterns,
|
107
|
-
log_prefix: "Autoloadable(#{feature_name})"
|
108
|
-
)
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
@@ -1,61 +0,0 @@
|
|
1
|
-
# try/features/autoloadable/autoloadable_try.rb
|
2
|
-
|
3
|
-
require_relative '../../../lib/familia'
|
4
|
-
|
5
|
-
# Create test feature module that includes Autoloadable
|
6
|
-
module TestAutoloadableFeature
|
7
|
-
include Familia::Features::Autoloadable
|
8
|
-
|
9
|
-
def self.included(base)
|
10
|
-
super
|
11
|
-
base.define_method(:test_feature_method) { "feature_loaded" }
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
# Create test class to include the feature
|
16
|
-
class TestModelForAutoloadable < Familia::Horreum
|
17
|
-
field :name
|
18
|
-
end
|
19
|
-
|
20
|
-
## Test that Autoloadable can be included in feature modules
|
21
|
-
TestAutoloadableFeature.ancestors.include?(Familia::Features::Autoloadable)
|
22
|
-
#=> true
|
23
|
-
|
24
|
-
## Test that Autoloadable extends feature modules with ClassMethods
|
25
|
-
TestAutoloadableFeature.respond_to?(:post_inclusion_autoload)
|
26
|
-
#=> true
|
27
|
-
|
28
|
-
## Test that including autoloadable feature in Horreum class works
|
29
|
-
TestModelForAutoloadable.include(TestAutoloadableFeature)
|
30
|
-
TestModelForAutoloadable.ancestors.include?(TestAutoloadableFeature)
|
31
|
-
#=> true
|
32
|
-
|
33
|
-
## Test that post_inclusion_autoload can be called with test class
|
34
|
-
TestAutoloadableFeature.post_inclusion_autoload(TestModelForAutoloadable, :test_autoloadable_feature, {})
|
35
|
-
"success"
|
36
|
-
#=> "success"
|
37
|
-
|
38
|
-
## Test that feature methods are available on the model
|
39
|
-
@test_instance = TestModelForAutoloadable.new(name: 'test')
|
40
|
-
@test_instance.respond_to?(:test_feature_method)
|
41
|
-
#=> true
|
42
|
-
|
43
|
-
## Test that feature method works
|
44
|
-
@test_instance.test_feature_method
|
45
|
-
#=> "feature_loaded"
|
46
|
-
|
47
|
-
## Test that Autoloadable works with DataType classes (should not crash)
|
48
|
-
class TestDataTypeAutoloadable < Familia::DataType
|
49
|
-
include Familia::Features::Autoloadable
|
50
|
-
end
|
51
|
-
|
52
|
-
TestDataTypeAutoloadable.ancestors.include?(Familia::Features::Autoloadable)
|
53
|
-
#=> true
|
54
|
-
|
55
|
-
## Test that SafeDump includes Autoloadable (real-world usage)
|
56
|
-
Familia::Features::SafeDump.ancestors.include?(Familia::Features::Autoloadable)
|
57
|
-
#=> true
|
58
|
-
|
59
|
-
## Test that SafeDump has post_inclusion_autoload capability
|
60
|
-
Familia::Features::SafeDump.respond_to?(:post_inclusion_autoload)
|
61
|
-
#=> true
|