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.
- checksums.yaml +4 -4
- data/CHANGELOG.rst +29 -7
- data/Gemfile.lock +1 -1
- data/README.md +21 -2
- data/docs/guides/Feature-System-Autoloading.md +3 -33
- data/docs/guides/time-utilities.md +4 -4
- data/docs/migrating/v2.0.0-pre11.md +2 -2
- data/docs/migrating/v2.0.0-pre13.md +38 -272
- data/docs/migrating/v2.0.0-pre14.md +37 -0
- data/examples/safe_dump.rb +1 -1
- data/lib/familia/base.rb +1 -1
- data/lib/familia/data_type.rb +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 +5 -62
- data/lib/familia/features/external_identifier.rb +6 -4
- data/lib/familia/features/object_identifier.rb +2 -1
- data/lib/familia/features/quantization.rb +3 -3
- 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/field_type.rb +1 -1
- data/lib/familia/horreum.rb +1 -1
- data/lib/familia/refinements/{time_utils.rb → time_literals.rb} +35 -4
- data/lib/familia/refinements.rb +1 -1
- data/lib/familia/utils.rb +1 -1
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +0 -1
- data/try/core/autoloader_try.rb +9 -9
- data/try/core/extensions_try.rb +1 -1
- data/try/core/time_utils_try.rb +18 -18
- data/try/features/external_identifier/external_identifier_try.rb +26 -0
- data/try/features/safe_dump/module_based_extensions_try.rb +100 -0
- data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -4
- data/try/helpers/test_helpers.rb +6 -6
- metadata +6 -5
- data/lib/familia/features/autoloadable.rb +0 -113
- data/try/features/autoloadable/autoloadable_try.rb +0 -61
@@ -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,7 +151,9 @@ module Familia
|
|
149
151
|
module Expiration
|
150
152
|
@default_expiration = nil
|
151
153
|
|
152
|
-
|
154
|
+
Familia::Base.add_feature self, :expiration
|
155
|
+
|
156
|
+
using Familia::Refinements::TimeLiterals
|
153
157
|
|
154
158
|
def self.included(base)
|
155
159
|
Familia.trace :LOADED, self, base, caller(1..1) if Familia.debug?
|
@@ -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
|
@@ -99,7 +102,7 @@ module Familia
|
|
99
102
|
klass.define_method :"#{method_name}=" do |value|
|
100
103
|
# Remove old mapping if extid is changing
|
101
104
|
old_value = instance_variable_get(:"@#{field_name}")
|
102
|
-
self.class.extid_lookup.
|
105
|
+
self.class.extid_lookup.remove_field(old_value) if old_value && old_value != value
|
103
106
|
|
104
107
|
# Set the new value
|
105
108
|
instance_variable_set(:"@#{field_name}", value)
|
@@ -153,7 +156,7 @@ module Familia
|
|
153
156
|
find_by_id(primary_id)
|
154
157
|
rescue Familia::NotFound
|
155
158
|
# If the object was deleted but mapping wasn't cleaned up
|
156
|
-
extid_lookup.
|
159
|
+
extid_lookup.remove_field(extid)
|
157
160
|
nil
|
158
161
|
end
|
159
162
|
end
|
@@ -236,7 +239,7 @@ module Familia
|
|
236
239
|
def destroy!
|
237
240
|
# Clean up extid mapping when object is destroyed
|
238
241
|
current_extid = instance_variable_get(:@extid)
|
239
|
-
self.class.extid_lookup.
|
242
|
+
self.class.extid_lookup.remove_field(current_extid) if current_extid
|
240
243
|
|
241
244
|
super if defined?(super)
|
242
245
|
end
|
@@ -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,7 +246,9 @@ module Familia
|
|
246
246
|
#
|
247
247
|
module Quantization
|
248
248
|
|
249
|
-
|
249
|
+
Familia::Base.add_feature self, :quantization
|
250
|
+
|
251
|
+
using Familia::Refinements::TimeLiterals
|
250
252
|
|
251
253
|
def self.included(base)
|
252
254
|
Familia.trace :LOADED, self, base, caller(1..1) if Familia.debug?
|
@@ -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/field_type.rb
CHANGED
data/lib/familia/horreum.rb
CHANGED
@@ -1,10 +1,41 @@
|
|
1
|
-
# lib/familia/refinements/
|
1
|
+
# lib/familia/refinements/time_literals.rb
|
2
2
|
|
3
3
|
module Familia
|
4
4
|
module Refinements
|
5
5
|
|
6
|
-
# Familia::Refinements::
|
7
|
-
|
6
|
+
# Familia::Refinements::TimeLiterals
|
7
|
+
#
|
8
|
+
# This module provides a set of refinements for `Numeric` and `String` to
|
9
|
+
# enable readable and expressive time duration and timestamp manipulation.
|
10
|
+
#
|
11
|
+
# The name "TimeLiterals" reflects its core purpose: to allow us to treat
|
12
|
+
# numeric values directly as "literals" of time units (e.g., `5.minutes`,
|
13
|
+
# `1.day`). It extends this concept to include conversions between these
|
14
|
+
# literal time quantities, parsing string representations of time
|
15
|
+
# durations, and performing common timestamp-based calculations
|
16
|
+
# in an intuitive manner.
|
17
|
+
#
|
18
|
+
# @example Expressing durations
|
19
|
+
# 5.minutes.ago #=> A Time object 5 minutes in the past
|
20
|
+
# 1.day.from_now #=> A Time object 1 day in the future
|
21
|
+
# (2.5).hours #=> 9000.0 (seconds)
|
22
|
+
#
|
23
|
+
# @example Converting between units
|
24
|
+
# 3600.in_hours #=> 1.0
|
25
|
+
# 86400.in_days #=> 1.0
|
26
|
+
#
|
27
|
+
# @example Parsing string durations
|
28
|
+
# "30m".in_seconds #=> 1800.0
|
29
|
+
# "2.5h".in_seconds #=> 9000.0
|
30
|
+
#
|
31
|
+
# @example Timestamp calculations
|
32
|
+
# timestamp = 2.days.ago.to_i
|
33
|
+
# timestamp.days_old #=> ~2.0
|
34
|
+
# timestamp.older_than?(1.day) #=> true
|
35
|
+
#
|
36
|
+
# @note `to_bytes` also lives here until we find it a better home!
|
37
|
+
#
|
38
|
+
module TimeLiterals
|
8
39
|
# Time unit constants
|
9
40
|
PER_MICROSECOND = 0.000001
|
10
41
|
PER_MILLISECOND = 0.001
|
@@ -65,7 +96,7 @@ module Familia
|
|
65
96
|
alias_method :month, :months
|
66
97
|
alias_method :year, :years
|
67
98
|
|
68
|
-
#
|
99
|
+
# Shortest aliases
|
69
100
|
alias_method :ms, :milliseconds
|
70
101
|
alias_method :μs, :microseconds
|
71
102
|
|
data/lib/familia/refinements.rb
CHANGED
data/lib/familia/utils.rb
CHANGED
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
|
data/try/core/extensions_try.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require_relative '../helpers/test_helpers'
|
2
2
|
|
3
3
|
module RefinedContext
|
4
|
-
using Familia::Refinements::
|
4
|
+
using Familia::Refinements::TimeLiterals
|
5
5
|
|
6
6
|
# This helper evaluates code within the refined context using eval.
|
7
7
|
# This works because eval executes the code as if it were written
|