familia 2.0.0.pre12 → 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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +2 -3
  3. data/CHANGELOG.rst +507 -0
  4. data/CLAUDE.md +1 -1
  5. data/Gemfile +1 -6
  6. data/Gemfile.lock +13 -7
  7. data/changelog.d/README.md +5 -5
  8. data/{setup.cfg → changelog.d/scriv.ini} +1 -1
  9. data/docs/guides/Feature-System-Autoloading.md +228 -0
  10. data/docs/guides/time-utilities.md +221 -0
  11. data/docs/migrating/v2.0.0-pre11.md +14 -16
  12. data/docs/migrating/v2.0.0-pre13.md +329 -0
  13. data/examples/autoloader/mega_customer/safe_dump_fields.rb +6 -0
  14. data/examples/autoloader/mega_customer.rb +17 -0
  15. data/familia.gemspec +1 -0
  16. data/lib/familia/autoloader.rb +53 -0
  17. data/lib/familia/base.rb +5 -0
  18. data/lib/familia/data_type.rb +4 -0
  19. data/lib/familia/encryption/encrypted_data.rb +4 -4
  20. data/lib/familia/encryption/manager.rb +6 -4
  21. data/lib/familia/encryption.rb +1 -1
  22. data/lib/familia/errors.rb +3 -0
  23. data/lib/familia/features/autoloadable.rb +113 -0
  24. data/lib/familia/features/encrypted_fields/concealed_string.rb +4 -2
  25. data/lib/familia/features/expiration.rb +4 -0
  26. data/lib/familia/features/quantization.rb +5 -0
  27. data/lib/familia/features/safe_dump.rb +7 -0
  28. data/lib/familia/features.rb +20 -16
  29. data/lib/familia/field_type.rb +2 -0
  30. data/lib/familia/horreum/core/serialization.rb +3 -3
  31. data/lib/familia/horreum/subclass/definition.rb +3 -4
  32. data/lib/familia/horreum.rb +2 -0
  33. data/lib/familia/json_serializer.rb +70 -0
  34. data/lib/familia/logging.rb +12 -10
  35. data/lib/familia/refinements/logger_trace.rb +57 -0
  36. data/lib/familia/refinements/snake_case.rb +40 -0
  37. data/lib/familia/refinements/time_utils.rb +248 -0
  38. data/lib/familia/refinements.rb +3 -49
  39. data/lib/familia/utils.rb +2 -0
  40. data/lib/familia/validation/{test_helpers.rb → validation_helpers.rb} +2 -2
  41. data/lib/familia/validation.rb +1 -1
  42. data/lib/familia/version.rb +1 -1
  43. data/lib/familia.rb +15 -3
  44. data/try/core/autoloader_try.rb +112 -0
  45. data/try/core/extensions_try.rb +38 -21
  46. data/try/core/familia_extended_try.rb +4 -3
  47. data/try/core/time_utils_try.rb +130 -0
  48. data/try/data_types/datatype_base_try.rb +3 -2
  49. data/try/features/autoloadable/autoloadable_try.rb +61 -0
  50. data/try/features/encrypted_fields/concealed_string_core_try.rb +8 -3
  51. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +59 -17
  52. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +36 -12
  53. data/try/features/feature_improvements_try.rb +2 -1
  54. data/try/features/real_feature_integration_try.rb +1 -1
  55. data/try/features/safe_dump/safe_dump_autoloading_try.rb +111 -0
  56. data/try/helpers/test_helpers.rb +24 -0
  57. data/try/integration/cross_component_try.rb +3 -1
  58. metadata +33 -6
  59. data/CHANGELOG.md +0 -247
  60. data/lib/familia/core_ext.rb +0 -135
  61. data/lib/familia/features/autoloader.rb +0 -57
@@ -1,51 +1,5 @@
1
1
  # lib/familia/refinements.rb
2
2
 
3
- require 'pathname'
4
- require 'logger'
5
-
6
- # Controls whether tracing is enabled via an environment variable
7
- FAMILIA_TRACE = ENV.fetch('FAMILIA_TRACE', 'false').downcase
8
-
9
- # LoggerTraceRefinement
10
- #
11
- # This module adds a 'trace' log level to the Ruby Logger class.
12
- # It is enabled when the FAMILIA_TRACE environment variable is set to
13
- # '1', 'true', or 'yes' (case-insensitive).
14
- #
15
- # @example Enabling trace logging
16
- # # Set environment variable
17
- # ENV['FAMILIA_TRACE'] = 'true'
18
- #
19
- # # In your Ruby code
20
- # require 'logger'
21
- # using LoggerTraceRefinement
22
- #
23
- # logger = Logger.new(STDOUT)
24
- # logger.trace("This is a trace message")
25
- #
26
- module LoggerTraceRefinement
27
- unless defined?(ENABLED)
28
- # Indicates whether trace logging is enabled
29
- ENABLED = %w[1 true yes].include?(FAMILIA_TRACE).freeze
30
- # The numeric level for trace logging (same as DEBUG)
31
- TRACE = 0
32
- end
33
-
34
- refine Logger do
35
- ##
36
- # Logs a message at the TRACE level.
37
- #
38
- # @param progname [String] The program name to include in the log message
39
- # @yield A block that evaluates to the message to log
40
- # @return [true] Always returns true
41
- #
42
- # @example Logging a trace message
43
- # logger.trace("MyApp") { "Detailed trace information" }
44
- def trace(progname = nil, &block)
45
- Thread.current[:severity_letter] = 'T'
46
- add(LoggerTraceRefinement::TRACE, nil, progname, &block)
47
- ensure
48
- Thread.current[:severity_letter] = nil
49
- end
50
- end
51
- end
3
+ require_relative 'refinements/logger_trace'
4
+ require_relative 'refinements/snake_case'
5
+ require_relative 'refinements/time_utils'
data/lib/familia/utils.rb CHANGED
@@ -6,6 +6,8 @@ module Familia
6
6
  #
7
7
  module Utils
8
8
 
9
+ using Familia::Refinements::TimeUtils
10
+
9
11
  # Joins array elements with Familia delimiter
10
12
  # @param val [Array] elements to join
11
13
  # @return [String] joined string
@@ -1,4 +1,4 @@
1
- # lib/familia/validation/test_helpers.rb
1
+ # lib/familia/validation/validation_helpers.rb
2
2
 
3
3
  module Familia
4
4
  module Validation
@@ -7,7 +7,7 @@ module Familia
7
7
  # and automatic setup/cleanup for command validation tests.
8
8
  #
9
9
  # @example Basic usage in a try file
10
- # require_relative '../validation/test_helpers'
10
+ # require_relative '../validation/validation_helpers'
11
11
  # extend Familia::Validation::TestHelpers
12
12
  #
13
13
  # ## User save should execute expected Redis commands
@@ -51,7 +51,7 @@
51
51
  require_relative 'validation/command_recorder'
52
52
  require_relative 'validation/expectations'
53
53
  require_relative 'validation/validator'
54
- require_relative 'validation/test_helpers'
54
+ require_relative 'validation/validation_helpers'
55
55
 
56
56
  module Familia
57
57
  module Validation
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Familia
4
4
  # Version information for the Familia
5
- VERSION = '2.0.0.pre12'.freeze unless defined?(Familia::VERSION)
5
+ VERSION = '2.0.0.pre13'.freeze unless defined?(Familia::VERSION)
6
6
  end
data/lib/familia.rb CHANGED
@@ -1,11 +1,12 @@
1
1
  # lib/familia.rb
2
2
 
3
- require 'json'
3
+ require 'oj'
4
4
  require 'redis'
5
5
  require 'uri/valkey'
6
6
  require 'connection_pool'
7
7
 
8
- require_relative 'familia/core_ext'
8
+ # OJ configuration is handled internally by Familia::JsonSerializer
9
+
9
10
  require_relative 'familia/refinements'
10
11
  require_relative 'familia/errors'
11
12
  require_relative 'familia/version'
@@ -71,6 +72,7 @@ module Familia
71
72
  require_relative 'familia/connection'
72
73
  require_relative 'familia/settings'
73
74
  require_relative 'familia/utils'
75
+ require_relative 'familia/json_serializer'
74
76
 
75
77
  extend SecureIdentifier
76
78
  extend Connection
@@ -80,8 +82,18 @@ module Familia
80
82
  end
81
83
 
82
84
  require_relative 'familia/base'
85
+ require_relative 'familia/features/autoloadable'
83
86
  require_relative 'familia/features'
84
- require_relative 'familia/features/autoloader'
85
87
  require_relative 'familia/data_type'
86
88
  require_relative 'familia/horreum'
87
89
  require_relative 'familia/encryption'
90
+
91
+ # Ensure JSON constant is available for backward compatibility with existing code
92
+ # This approach is safer than monkey-patching core classes globally
93
+ begin
94
+ require 'json'
95
+ rescue LoadError
96
+ # If json gem is not available, define a minimal JSON constant
97
+ # that delegates to Familia::JsonSerializer for compatibility
98
+ JSON = Familia::JsonSerializer
99
+ end
@@ -0,0 +1,112 @@
1
+ # try/core/autoloader_try.rb
2
+
3
+ require_relative '../../lib/familia'
4
+ require 'fileutils'
5
+ require 'tmpdir'
6
+
7
+ # Create test directory structure for Autoloader testing
8
+ @test_dir = Dir.mktmpdir('familia_autoloader_test')
9
+ @features_dir = File.join(@test_dir, 'features')
10
+ @test_file1 = File.join(@features_dir, 'test_feature1.rb')
11
+ @test_file2 = File.join(@features_dir, 'test_feature2.rb')
12
+ @excluded_file = File.join(@features_dir, 'autoloader.rb')
13
+
14
+ # Create directory structure
15
+ FileUtils.mkdir_p(@features_dir)
16
+
17
+ # Write test files
18
+ File.write(@test_file1, <<~RUBY)
19
+ # Test feature file 1
20
+ $test_feature1_loaded = true
21
+ RUBY
22
+
23
+ File.write(@test_file2, <<~RUBY)
24
+ # Test feature file 2
25
+ $test_feature2_loaded = true
26
+ RUBY
27
+
28
+ File.write(@excluded_file, <<~RUBY)
29
+ # This should be excluded
30
+ $autoloader_file_loaded = true
31
+ RUBY
32
+
33
+ ## Test that Familia::Autoloader exists and is a module
34
+ Familia::Autoloader.is_a?(Module)
35
+ #=> true
36
+
37
+ ## Test that autoload_files class method exists
38
+ Familia::Autoloader.respond_to?(:autoload_files)
39
+ #=> true
40
+
41
+ ## Test that included class method exists
42
+ Familia::Autoloader.respond_to?(:included)
43
+ #=> true
44
+
45
+ ## Test autoload_files with single pattern
46
+ $test_feature1_loaded = false
47
+ $test_feature2_loaded = false
48
+ $autoloader_file_loaded = false
49
+
50
+ Familia::Autoloader.autoload_files(File.join(@features_dir, '*.rb'))
51
+ $test_feature1_loaded && $test_feature2_loaded
52
+ #=> true
53
+
54
+ ## Test that autoload_files respects exclusions (using fresh files)
55
+ @exclude_test_dir = Dir.mktmpdir('familia_autoloader_exclude_test')
56
+ @exclude_features_dir = File.join(@exclude_test_dir, 'features')
57
+ @include_file = File.join(@exclude_features_dir, 'include_me.rb')
58
+ @exclude_file = File.join(@exclude_features_dir, 'autoloader.rb')
59
+
60
+ FileUtils.mkdir_p(@exclude_features_dir)
61
+ File.write(@include_file, '$include_me_loaded = true')
62
+ File.write(@exclude_file, '$exclude_me_loaded = true')
63
+
64
+ $include_me_loaded = false
65
+ $exclude_me_loaded = false
66
+
67
+ Familia::Autoloader.autoload_files(
68
+ File.join(@exclude_features_dir, '*.rb'),
69
+ exclude: ['autoloader.rb']
70
+ )
71
+
72
+ # Should load include file but not the excluded one
73
+ $include_me_loaded && !$exclude_me_loaded
74
+ #=> true
75
+
76
+ ## Test autoload_files with array of patterns (using fresh files)
77
+ @pattern_test_dir = Dir.mktmpdir('familia_autoloader_pattern_test')
78
+ @pattern_dir1 = File.join(@pattern_test_dir, 'dir1')
79
+ @pattern_dir2 = File.join(@pattern_test_dir, 'dir2')
80
+ @pattern_file1 = File.join(@pattern_dir1, 'file1.rb')
81
+ @pattern_file2 = File.join(@pattern_dir2, 'file2.rb')
82
+
83
+ FileUtils.mkdir_p(@pattern_dir1)
84
+ FileUtils.mkdir_p(@pattern_dir2)
85
+ File.write(@pattern_file1, '$pattern1_loaded = true')
86
+ File.write(@pattern_file2, '$pattern2_loaded = true')
87
+
88
+ $pattern1_loaded = false
89
+ $pattern2_loaded = false
90
+
91
+ Familia::Autoloader.autoload_files([
92
+ File.join(@pattern_dir1, '*.rb'),
93
+ File.join(@pattern_dir2, '*.rb')
94
+ ])
95
+
96
+ $pattern1_loaded && $pattern2_loaded
97
+ #=> true
98
+
99
+ ## Test that included method loads features from features directory
100
+ # Create a mock module that includes Autoloader
101
+ @mock_features_module = Module.new do
102
+ include Familia::Autoloader
103
+ end
104
+
105
+ # The Features module already includes Autoloader, so test indirectly
106
+ Familia::Features.ancestors.include?(Familia::Autoloader)
107
+ #=> true
108
+
109
+ # Cleanup test files and directories
110
+ FileUtils.rm_rf(@test_dir)
111
+ FileUtils.rm_rf(@exclude_test_dir)
112
+ FileUtils.rm_rf(@pattern_test_dir)
@@ -1,59 +1,76 @@
1
1
  require_relative '../helpers/test_helpers'
2
2
 
3
+ module RefinedContext
4
+ using Familia::Refinements::TimeUtils
5
+
6
+ # This helper evaluates code within the refined context using eval.
7
+ # This works because eval executes the code as if it were written
8
+ # at this location, making the refinements available.
9
+ def self.eval_in_refined_context(code)
10
+ eval(code)
11
+ end
12
+
13
+ # This helper also evaluates code in the refined context using instance_eval.
14
+ # This provides an alternative approach for testing refinements.
15
+ def self.instance_eval_in_refined_context(code)
16
+ instance_eval(code)
17
+ end
18
+ end
19
+
3
20
  # Test core extensions
4
21
 
5
22
  ## String time parsing - seconds
6
- '60s'.in_seconds
7
- #=> 60
23
+ RefinedContext.eval_in_refined_context("'60s'.in_seconds")
24
+ #=> 60.0
8
25
 
9
26
  ## String time parsing - minutes
10
- '5m'.in_seconds
11
- #=> 300
27
+ RefinedContext.instance_eval_in_refined_context("'5m'.in_seconds")
28
+ #=> 300.0
12
29
 
13
30
  ## String time parsing - hours
14
- '2h'.in_seconds
15
- #=> 7200
31
+ RefinedContext.eval_in_refined_context("'2h'.in_seconds")
32
+ #=> 7200.0
16
33
 
17
34
  ## String time parsing - days
18
- '1d'.in_seconds
19
- #=> 86_400
35
+ RefinedContext.instance_eval_in_refined_context("'1d'.in_seconds")
36
+ #=> 86_400.0
20
37
 
21
- ## String time parsing - days
22
- '1y'.in_seconds
23
- #=> 31536000
38
+ ## String time parsing - years
39
+ RefinedContext.eval_in_refined_context("'1y'.in_seconds")
40
+ #=> 31556952.0
24
41
 
25
42
  ## Time::Units - second
26
- 1.second
43
+ RefinedContext.instance_eval_in_refined_context("1.second")
27
44
  #=> 1
28
45
 
29
46
  ## Time::Units - minute
30
- 1.minute
47
+ RefinedContext.eval_in_refined_context("1.minute")
31
48
  #=> 60
32
49
 
33
50
  ## Time::Units - hour
34
- 1.hour
51
+ RefinedContext.instance_eval_in_refined_context("1.hour")
35
52
  #=> 3600
36
53
 
37
54
  ## Time::Units - day
38
- 1.day
55
+ RefinedContext.eval_in_refined_context("1.day")
39
56
  #=> 86_400
40
57
 
41
58
  ## Time::Units - week
42
- 1.week
59
+ RefinedContext.instance_eval_in_refined_context("1.week")
43
60
  #=> 604_800
44
61
 
45
62
  ## Numeric extension to_ms
46
- 1000.to_ms
47
- #=> 1000 * 1000
63
+ RefinedContext.eval_in_refined_context("1000.to_ms")
64
+ #=> 1000000.0
48
65
 
49
66
  ## Numeric extension to_bytes - single byte
50
- 1.to_bytes
67
+ RefinedContext.instance_eval_in_refined_context("1.to_bytes")
51
68
  #=> '1.00 B'
52
69
 
53
70
  ## Numeric extension to_bytes - kilobytes
54
- 1024.to_bytes
71
+ RefinedContext.eval_in_refined_context("1024.to_bytes")
55
72
  #=> '1.00 KiB'
56
73
 
57
74
  ## Numeric extension to_bytes - megabytes
58
- (1024 * 1024).to_bytes
75
+ RefinedContext.instance_eval_in_refined_context("(1024 * 1024).to_bytes")
59
76
  #=> '1.00 MiB'
@@ -44,14 +44,15 @@ parsed_time = Familia.now(Time.parse('2011-04-10 20:56:20 UTC').utc)
44
44
  #=> [1302468980.0, true, true]
45
45
 
46
46
  ## Familia.qnow
47
- Familia.qstamp 10.minutes, time: 1_302_468_980
47
+ RefinedContext.eval_in_refined_context("Familia.qstamp 10.minutes, time: 1_302_468_980")
48
48
  #=> 1302468600
49
49
 
50
50
  ## Familia::Object.qstamp
51
- Limiter.qstamp(10.minutes, pattern: '%H:%M', time: 1_302_468_980)
51
+ RefinedContext.eval_in_refined_context("Limiter.qstamp(10.minutes, pattern: '%H:%M', time: 1_302_468_980)")
52
52
  #=> '20:50'
53
53
 
54
54
  ## Familia::Object#qstamp
55
55
  limiter = Limiter.new :request
56
- limiter.qstamp(10.minutes, pattern: '%H:%M', time: 1_302_468_980)
56
+ RefinedContext.instance_variable_set(:@limiter, limiter)
57
+ RefinedContext.eval_in_refined_context("@limiter.qstamp(10.minutes, pattern: '%H:%M', time: 1_302_468_980)")
57
58
  #=> '20:50'
@@ -0,0 +1,130 @@
1
+ require_relative '../helpers/test_helpers'
2
+
3
+ module RefinedContext
4
+ using Familia::Refinements::TimeUtils
5
+
6
+ def self.eval_in_refined_context(code)
7
+ eval(code)
8
+ end
9
+
10
+ def self.instance_eval_in_refined_context(code)
11
+ instance_eval(code)
12
+ end
13
+ end
14
+
15
+ # Test TimeUtils refinement
16
+
17
+ ## Numeric#months - convert number to months in seconds
18
+ result = RefinedContext.eval_in_refined_context("1.month")
19
+ result.round(0)
20
+ #=> 2629746.0
21
+
22
+ ## Numeric#months - plural form
23
+ result = RefinedContext.instance_eval_in_refined_context("2.months")
24
+ result.round(0)
25
+ #=> 5259492.0
26
+
27
+ ## Numeric#years - convert number to years in seconds
28
+ result = RefinedContext.eval_in_refined_context("1.year")
29
+ result.round(0)
30
+ #=> 31556952.0
31
+
32
+ ## Numeric#in_months - convert seconds to months
33
+ RefinedContext.instance_eval_in_refined_context("2629746.in_months")
34
+ #=> 1.0
35
+
36
+ ## Numeric#in_years - convert seconds to years
37
+ result = RefinedContext.eval_in_refined_context("#{Familia::Refinements::TimeUtils::PER_YEAR}.in_years")
38
+ result.round(1)
39
+ #=> 1.0
40
+
41
+ ## String#in_seconds - parse month string
42
+ RefinedContext.instance_eval_in_refined_context("'1mo'.in_seconds")
43
+ #=> 2629746.0
44
+
45
+ ## String#in_seconds - parse month string (long form)
46
+ RefinedContext.eval_in_refined_context("'2months'.in_seconds")
47
+ #=> 5259492.0
48
+
49
+ ## String#in_seconds - parse year string
50
+ result = RefinedContext.instance_eval_in_refined_context("'1y'.in_seconds")
51
+ result.round(0)
52
+ #=> 31556952.0
53
+
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
56
+ result = RefinedContext.eval_in_refined_context("#{timestamp}.age_in(:months)")
57
+ (result - 1.0).abs < 0.01
58
+ #=> true
59
+
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
62
+ result = RefinedContext.instance_eval_in_refined_context("#{timestamp}.age_in(:years)")
63
+ (result - 1.0).abs < 0.01
64
+ #=> true
65
+
66
+ ## Numeric#months_old - convenience method for age_in(:months)
67
+ timestamp = Time.now.to_f - Familia::Refinements::TimeUtils::PER_MONTH
68
+ result = RefinedContext.eval_in_refined_context("#{timestamp}.months_old")
69
+ (result - 1.0).abs < 0.01
70
+ #=> true
71
+
72
+ ## Numeric#years_old - convenience method for age_in(:years)
73
+ timestamp = Time.now.to_f - Familia::Refinements::TimeUtils::PER_YEAR
74
+ result = RefinedContext.instance_eval_in_refined_context("#{timestamp}.years_old")
75
+ (result - 1.0).abs < 0.01
76
+ #=> true
77
+
78
+ ## Numeric#months_old - should NOT return seconds (the original bug)
79
+ timestamp = Time.now.to_f - Familia::Refinements::TimeUtils::PER_MONTH
80
+ result = RefinedContext.eval_in_refined_context("#{timestamp}.months_old")
81
+ result.between?(0.9, 1.1) # Should be ~1 month, not millions of seconds
82
+ #=> true
83
+
84
+ ## Numeric#years_old - should NOT return seconds (the original bug)
85
+ timestamp = Time.now.to_f - Familia::Refinements::TimeUtils::PER_YEAR
86
+ result = RefinedContext.instance_eval_in_refined_context("#{timestamp}.years_old")
87
+ result.between?(0.9, 1.1) # Should be ~1 year, not millions of seconds
88
+ #=> true
89
+
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
93
+ result = RefinedContext.eval_in_refined_context("#{past_time.to_f}.age_in(:months, #{from_time.to_f})")
94
+ (result - 1.0).abs < 0.01
95
+ #=> true
96
+
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
100
+ result = RefinedContext.instance_eval_in_refined_context("#{past_time.to_f}.age_in(:years, #{from_time.to_f})")
101
+ (result - 1.0).abs < 0.01
102
+ #=> true
103
+
104
+ ## Verify month constant is approximately correct (30.437 days)
105
+ expected_seconds_per_month = 30.437 * 24 * 60 * 60
106
+ Familia::Refinements::TimeUtils::PER_MONTH.round(0)
107
+ #=> 2629746.0
108
+
109
+ ## Verify year constant (365.2425 days - Gregorian year)
110
+ expected_seconds_per_year = 365.2425 * 24 * 60 * 60
111
+ Familia::Refinements::TimeUtils::PER_YEAR.round(0)
112
+ #=> 31556952.0
113
+
114
+ ## UNIT_METHODS contains months mapping
115
+ Familia::Refinements::TimeUtils::UNIT_METHODS['months']
116
+ #=> :months
117
+
118
+ ## UNIT_METHODS contains mo mapping
119
+ Familia::Refinements::TimeUtils::UNIT_METHODS['mo']
120
+ #=> :months
121
+
122
+ ## UNIT_METHODS contains month mapping
123
+ Familia::Refinements::TimeUtils::UNIT_METHODS['month']
124
+ #=> :months
125
+
126
+ ## Calendar consistency - 12 months equals 1 year (fix for inconsistency issue)
127
+ result1 = RefinedContext.eval_in_refined_context("12.months")
128
+ result2 = RefinedContext.instance_eval_in_refined_context("1.year")
129
+ result1 == result2
130
+ #=> true
@@ -21,7 +21,7 @@ p [@a.name, @b.name]
21
21
  #=> true
22
22
 
23
23
  ## Limiter#qstamp
24
- @limiter1.counter.qstamp(10.minutes, '%H:%M', 1_302_468_980)
24
+ RefinedContext.eval_in_refined_context("@limiter1.counter.qstamp(10.minutes, '%H:%M', 1_302_468_980)")
25
25
  ##=> '20:50'
26
26
 
27
27
  ## Database Types can be stored to quantized stamp suffix
@@ -32,7 +32,8 @@ p [@a.name, @b.name]
32
32
  @limiter2 = Limiter.new :requests
33
33
  p [@limiter1.default_expiration, @limiter2.default_expiration]
34
34
  p [@limiter1.counter.parent.default_expiration, @limiter2.counter.parent.default_expiration]
35
- @limiter2.counter.qstamp(10.minutes, pattern: nil, time: 1_302_468_980)
35
+ RefinedContext.instance_variable_set(:@limiter2, @limiter2)
36
+ RefinedContext.eval_in_refined_context("@limiter2.counter.qstamp(10.minutes, pattern: nil, time: 1_302_468_980)")
36
37
  #=> 1302468600
37
38
 
38
39
  ## Database Types can be stored to quantized numeric suffix. This
@@ -0,0 +1,61 @@
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
@@ -77,9 +77,14 @@ end
77
77
  @doc.content.to_str
78
78
  #=!> NoMethodError
79
79
 
80
- ## JSON serialization - to_json
81
- @doc.content.to_json
82
- #=> "\"[CONCEALED]\""
80
+ ## JSON serialization - to_json (fails for security)
81
+ begin
82
+ @doc.content.to_json
83
+ raise "Should have raised SerializerError"
84
+ rescue Familia::SerializerError => e
85
+ e.class
86
+ end
87
+ #=> Familia::SerializerError
83
88
 
84
89
  ## JSON serialization - as_json
85
90
  @doc.content.as_json