familia 2.0.0.pre13 → 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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.rst +29 -7
  3. data/Gemfile.lock +1 -1
  4. data/README.md +21 -2
  5. data/docs/guides/Feature-System-Autoloading.md +3 -33
  6. data/docs/guides/time-utilities.md +4 -4
  7. data/docs/migrating/v2.0.0-pre11.md +2 -2
  8. data/docs/migrating/v2.0.0-pre13.md +38 -272
  9. data/docs/migrating/v2.0.0-pre14.md +37 -0
  10. data/examples/safe_dump.rb +1 -1
  11. data/lib/familia/base.rb +1 -1
  12. data/lib/familia/data_type.rb +1 -1
  13. data/lib/familia/{autoloader.rb → features/autoloader.rb} +33 -25
  14. data/lib/familia/features/encrypted_fields.rb +2 -2
  15. data/lib/familia/features/expiration/extensions.rb +61 -0
  16. data/lib/familia/features/expiration.rb +5 -62
  17. data/lib/familia/features/external_identifier.rb +6 -4
  18. data/lib/familia/features/object_identifier.rb +2 -1
  19. data/lib/familia/features/quantization.rb +3 -3
  20. data/lib/familia/features/relationships.rb +5 -6
  21. data/lib/familia/features/safe_dump.rb +3 -5
  22. data/lib/familia/features/transient_fields.rb +3 -1
  23. data/lib/familia/features.rb +20 -11
  24. data/lib/familia/field_type.rb +1 -1
  25. data/lib/familia/horreum.rb +1 -1
  26. data/lib/familia/refinements/{time_utils.rb → time_literals.rb} +35 -4
  27. data/lib/familia/refinements.rb +1 -1
  28. data/lib/familia/utils.rb +1 -1
  29. data/lib/familia/version.rb +1 -1
  30. data/lib/familia.rb +0 -1
  31. data/try/core/autoloader_try.rb +9 -9
  32. data/try/core/extensions_try.rb +1 -1
  33. data/try/core/time_utils_try.rb +18 -18
  34. data/try/features/external_identifier/external_identifier_try.rb +26 -0
  35. data/try/features/safe_dump/module_based_extensions_try.rb +100 -0
  36. data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -4
  37. data/try/helpers/test_helpers.rb +6 -6
  38. metadata +6 -5
  39. data/lib/familia/features/autoloadable.rb +0 -113
  40. data/try/features/autoloadable/autoloadable_try.rb +0 -61
@@ -1,7 +1,7 @@
1
1
  require_relative '../helpers/test_helpers'
2
2
 
3
3
  module RefinedContext
4
- using Familia::Refinements::TimeUtils
4
+ using Familia::Refinements::TimeLiterals
5
5
 
6
6
  def self.eval_in_refined_context(code)
7
7
  eval(code)
@@ -12,7 +12,7 @@ module RefinedContext
12
12
  end
13
13
  end
14
14
 
15
- # Test TimeUtils refinement
15
+ # Test TimeLiterals refinement
16
16
 
17
17
  ## Numeric#months - convert number to months in seconds
18
18
  result = RefinedContext.eval_in_refined_context("1.month")
@@ -34,7 +34,7 @@ RefinedContext.instance_eval_in_refined_context("2629746.in_months")
34
34
  #=> 1.0
35
35
 
36
36
  ## Numeric#in_years - convert seconds to years
37
- result = RefinedContext.eval_in_refined_context("#{Familia::Refinements::TimeUtils::PER_YEAR}.in_years")
37
+ result = RefinedContext.eval_in_refined_context("#{Familia::Refinements::TimeLiterals::PER_YEAR}.in_years")
38
38
  result.round(1)
39
39
  #=> 1.0
40
40
 
@@ -52,75 +52,75 @@ result.round(0)
52
52
  #=> 31556952.0
53
53
 
54
54
  ## Numeric#age_in - calculate age in months from timestamp (approximately 1 month ago)
55
- timestamp = Time.now.to_f - Familia::Refinements::TimeUtils::PER_MONTH
55
+ timestamp = Time.now.to_f - Familia::Refinements::TimeLiterals::PER_MONTH
56
56
  result = RefinedContext.eval_in_refined_context("#{timestamp}.age_in(:months)")
57
57
  (result - 1.0).abs < 0.01
58
58
  #=> true
59
59
 
60
60
  ## Numeric#age_in - calculate age in years from timestamp (approximately 1 year ago)
61
- timestamp = Time.now.to_f - Familia::Refinements::TimeUtils::PER_YEAR
61
+ timestamp = Time.now.to_f - Familia::Refinements::TimeLiterals::PER_YEAR
62
62
  result = RefinedContext.instance_eval_in_refined_context("#{timestamp}.age_in(:years)")
63
63
  (result - 1.0).abs < 0.01
64
64
  #=> true
65
65
 
66
66
  ## Numeric#months_old - convenience method for age_in(:months)
67
- timestamp = Time.now.to_f - Familia::Refinements::TimeUtils::PER_MONTH
67
+ timestamp = Time.now.to_f - Familia::Refinements::TimeLiterals::PER_MONTH
68
68
  result = RefinedContext.eval_in_refined_context("#{timestamp}.months_old")
69
69
  (result - 1.0).abs < 0.01
70
70
  #=> true
71
71
 
72
72
  ## Numeric#years_old - convenience method for age_in(:years)
73
- timestamp = Time.now.to_f - Familia::Refinements::TimeUtils::PER_YEAR
73
+ timestamp = Time.now.to_f - Familia::Refinements::TimeLiterals::PER_YEAR
74
74
  result = RefinedContext.instance_eval_in_refined_context("#{timestamp}.years_old")
75
75
  (result - 1.0).abs < 0.01
76
76
  #=> true
77
77
 
78
78
  ## Numeric#months_old - should NOT return seconds (the original bug)
79
- timestamp = Time.now.to_f - Familia::Refinements::TimeUtils::PER_MONTH
79
+ timestamp = Time.now.to_f - Familia::Refinements::TimeLiterals::PER_MONTH
80
80
  result = RefinedContext.eval_in_refined_context("#{timestamp}.months_old")
81
81
  result.between?(0.9, 1.1) # Should be ~1 month, not millions of seconds
82
82
  #=> true
83
83
 
84
84
  ## Numeric#years_old - should NOT return seconds (the original bug)
85
- timestamp = Time.now.to_f - Familia::Refinements::TimeUtils::PER_YEAR
85
+ timestamp = Time.now.to_f - Familia::Refinements::TimeLiterals::PER_YEAR
86
86
  result = RefinedContext.instance_eval_in_refined_context("#{timestamp}.years_old")
87
87
  result.between?(0.9, 1.1) # Should be ~1 year, not millions of seconds
88
88
  #=> true
89
89
 
90
90
  ## age_in with from_time parameter - months
91
- past_time = Time.now - (2 * Familia::Refinements::TimeUtils::PER_MONTH) # 2 months ago
92
- from_time = Time.now - Familia::Refinements::TimeUtils::PER_MONTH # 1 month ago
91
+ past_time = Time.now - (2 * Familia::Refinements::TimeLiterals::PER_MONTH) # 2 months ago
92
+ from_time = Time.now - Familia::Refinements::TimeLiterals::PER_MONTH # 1 month ago
93
93
  result = RefinedContext.eval_in_refined_context("#{past_time.to_f}.age_in(:months, #{from_time.to_f})")
94
94
  (result - 1.0).abs < 0.01
95
95
  #=> true
96
96
 
97
97
  ## age_in with from_time parameter - years
98
- past_time = Time.now - (2 * Familia::Refinements::TimeUtils::PER_YEAR) # 2 years ago
99
- from_time = Time.now - Familia::Refinements::TimeUtils::PER_YEAR # 1 year ago
98
+ past_time = Time.now - (2 * Familia::Refinements::TimeLiterals::PER_YEAR) # 2 years ago
99
+ from_time = Time.now - Familia::Refinements::TimeLiterals::PER_YEAR # 1 year ago
100
100
  result = RefinedContext.instance_eval_in_refined_context("#{past_time.to_f}.age_in(:years, #{from_time.to_f})")
101
101
  (result - 1.0).abs < 0.01
102
102
  #=> true
103
103
 
104
104
  ## Verify month constant is approximately correct (30.437 days)
105
105
  expected_seconds_per_month = 30.437 * 24 * 60 * 60
106
- Familia::Refinements::TimeUtils::PER_MONTH.round(0)
106
+ Familia::Refinements::TimeLiterals::PER_MONTH.round(0)
107
107
  #=> 2629746.0
108
108
 
109
109
  ## Verify year constant (365.2425 days - Gregorian year)
110
110
  expected_seconds_per_year = 365.2425 * 24 * 60 * 60
111
- Familia::Refinements::TimeUtils::PER_YEAR.round(0)
111
+ Familia::Refinements::TimeLiterals::PER_YEAR.round(0)
112
112
  #=> 31556952.0
113
113
 
114
114
  ## UNIT_METHODS contains months mapping
115
- Familia::Refinements::TimeUtils::UNIT_METHODS['months']
115
+ Familia::Refinements::TimeLiterals::UNIT_METHODS['months']
116
116
  #=> :months
117
117
 
118
118
  ## UNIT_METHODS contains mo mapping
119
- Familia::Refinements::TimeUtils::UNIT_METHODS['mo']
119
+ Familia::Refinements::TimeLiterals::UNIT_METHODS['mo']
120
120
  #=> :months
121
121
 
122
122
  ## UNIT_METHODS contains month mapping
123
- Familia::Refinements::TimeUtils::UNIT_METHODS['month']
123
+ Familia::Refinements::TimeLiterals::UNIT_METHODS['month']
124
124
  #=> :months
125
125
 
126
126
  ## Calendar consistency - 12 months equals 1 year (fix for inconsistency issue)
@@ -198,3 +198,29 @@ ExternalIdTest.extid_lookup[@test_obj.extid]
198
198
 
199
199
  # Cleanup test objects
200
200
  @test_obj.destroy! rescue nil
201
+
202
+ ## Test 1: Changing extid value (should work after bug fix)
203
+ bug_test_obj = ExternalIdTest.new(id: 'bug_test', name: 'Bug Test Object')
204
+ bug_test_obj.save
205
+ bug_test_obj.extid = 'new_extid_value'
206
+ bug_test_obj.extid
207
+ #=> "new_extid_value"
208
+
209
+ ## Test 2: find_by_extid with deleted object (should work after bug fix)
210
+ delete_test_obj = ExternalIdTest.new(id: 'delete_test', name: 'Delete Test')
211
+ delete_test_obj.save
212
+ test_extid = delete_test_obj.extid
213
+ # Delete the object directly from Redis to simulate cleanup scenario
214
+ ExternalIdTest.dbclient.del(delete_test_obj.dbkey)
215
+ # Now try to find by extid - this should clean up mapping and return nil
216
+ ExternalIdTest.find_by_extid(test_extid)
217
+ #=> nil
218
+
219
+ ## Test 3: destroy! method (should work after bug fix)
220
+ destroy_test_obj = ExternalIdTest.new(id: 'destroy_test', name: 'Destroy Test')
221
+ destroy_test_obj.save
222
+ destroy_extid = destroy_test_obj.extid
223
+ destroy_test_obj.destroy!
224
+ # Verify mapping was cleaned up
225
+ ExternalIdTest.extid_lookup.key?(destroy_extid)
226
+ #=> false
@@ -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
@@ -12,7 +12,7 @@ Familia.enable_database_logging = true
12
12
  Familia.enable_database_counter = true
13
13
 
14
14
  class Bone < Familia::Horreum
15
- using Familia::Refinements::TimeUtils
15
+ using Familia::Refinements::TimeLiterals
16
16
 
17
17
  identifier_field :token
18
18
  field :token
@@ -42,7 +42,7 @@ end
42
42
 
43
43
  class Customer < Familia::Horreum
44
44
 
45
- using Familia::Refinements::TimeUtils
45
+ using Familia::Refinements::TimeLiterals
46
46
 
47
47
  logical_database 15 # Use something other than the default DB
48
48
  default_expiration 5.years
@@ -106,7 +106,7 @@ end
106
106
  @c.custid = 'd@example.com'
107
107
 
108
108
  class Session < Familia::Horreum
109
- using Familia::Refinements::TimeUtils
109
+ using Familia::Refinements::TimeLiterals
110
110
 
111
111
  logical_database 14 # don't use Onetime's default DB
112
112
  default_expiration 180.minutes
@@ -130,7 +130,7 @@ end
130
130
  @s = Session.new
131
131
 
132
132
  class CustomDomain < Familia::Horreum
133
- using Familia::Refinements::TimeUtils
133
+ using Familia::Refinements::TimeLiterals
134
134
 
135
135
  feature :expiration
136
136
 
@@ -161,7 +161,7 @@ end
161
161
  @d.custid = @c.custid
162
162
 
163
163
  class Limiter < Familia::Horreum
164
- using Familia::Refinements::TimeUtils
164
+ using Familia::Refinements::TimeLiterals
165
165
 
166
166
  feature :expiration
167
167
  feature :quantization
@@ -243,7 +243,7 @@ end
243
243
 
244
244
  # Helper module for testing refinements in tryouts
245
245
  module RefinedContext
246
- using Familia::Refinements::TimeUtils
246
+ using Familia::Refinements::TimeLiterals
247
247
 
248
248
  def self.eval_in_refined_context(code)
249
249
  eval(code)
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.pre13
4
+ version: 2.0.0.pre15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delano Mandelbaum
@@ -180,6 +180,7 @@ files:
180
180
  - docs/migrating/v2.0.0-pre11.md
181
181
  - docs/migrating/v2.0.0-pre12.md
182
182
  - docs/migrating/v2.0.0-pre13.md
183
+ - docs/migrating/v2.0.0-pre14.md
183
184
  - docs/migrating/v2.0.0-pre5.md
184
185
  - docs/migrating/v2.0.0-pre6.md
185
186
  - docs/migrating/v2.0.0-pre7.md
@@ -192,7 +193,6 @@ files:
192
193
  - examples/safe_dump.rb
193
194
  - familia.gemspec
194
195
  - lib/familia.rb
195
- - lib/familia/autoloader.rb
196
196
  - lib/familia/base.rb
197
197
  - lib/familia/connection.rb
198
198
  - lib/familia/data_type.rb
@@ -216,11 +216,12 @@ files:
216
216
  - lib/familia/encryption/request_cache.rb
217
217
  - lib/familia/errors.rb
218
218
  - lib/familia/features.rb
219
- - lib/familia/features/autoloadable.rb
219
+ - lib/familia/features/autoloader.rb
220
220
  - lib/familia/features/encrypted_fields.rb
221
221
  - lib/familia/features/encrypted_fields/concealed_string.rb
222
222
  - lib/familia/features/encrypted_fields/encrypted_field_type.rb
223
223
  - lib/familia/features/expiration.rb
224
+ - lib/familia/features/expiration/extensions.rb
224
225
  - lib/familia/features/external_identifier.rb
225
226
  - lib/familia/features/object_identifier.rb
226
227
  - lib/familia/features/quantization.rb
@@ -255,7 +256,7 @@ files:
255
256
  - lib/familia/refinements.rb
256
257
  - lib/familia/refinements/logger_trace.rb
257
258
  - lib/familia/refinements/snake_case.rb
258
- - lib/familia/refinements/time_utils.rb
259
+ - lib/familia/refinements/time_literals.rb
259
260
  - lib/familia/secure_identifier.rb
260
261
  - lib/familia/settings.rb
261
262
  - lib/familia/utils.rb
@@ -335,7 +336,6 @@ files:
335
336
  - try/encryption/providers/xchacha20_poly1305_provider_try.rb
336
337
  - try/encryption/roundtrip_validation_try.rb
337
338
  - try/encryption/secure_memory_handling_try.rb
338
- - try/features/autoloadable/autoloadable_try.rb
339
339
  - try/features/encrypted_fields/aad_protection_try.rb
340
340
  - try/features/encrypted_fields/concealed_string_core_try.rb
341
341
  - try/features/encrypted_fields/context_isolation_try.rb
@@ -369,6 +369,7 @@ files:
369
369
  - try/features/relationships/relationships_performance_try.rb
370
370
  - try/features/relationships/relationships_performance_working_try.rb
371
371
  - try/features/relationships/relationships_try.rb
372
+ - try/features/safe_dump/module_based_extensions_try.rb
372
373
  - try/features/safe_dump/safe_dump_advanced_try.rb
373
374
  - try/features/safe_dump/safe_dump_autoloading_try.rb
374
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