familia 2.0.0.pre12 → 2.0.0.pre14

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +2 -3
  3. data/CHANGELOG.rst +529 -0
  4. data/CLAUDE.md +1 -1
  5. data/Gemfile +1 -6
  6. data/Gemfile.lock +13 -7
  7. data/README.md +21 -2
  8. data/changelog.d/README.md +5 -5
  9. data/{setup.cfg → changelog.d/scriv.ini} +1 -1
  10. data/docs/guides/Feature-System-Autoloading.md +228 -0
  11. data/docs/guides/time-utilities.md +221 -0
  12. data/docs/migrating/v2.0.0-pre11.md +14 -16
  13. data/docs/migrating/v2.0.0-pre13.md +95 -0
  14. data/docs/migrating/v2.0.0-pre14.md +37 -0
  15. data/examples/autoloader/mega_customer/safe_dump_fields.rb +6 -0
  16. data/examples/autoloader/mega_customer.rb +17 -0
  17. data/examples/safe_dump.rb +1 -1
  18. data/familia.gemspec +1 -0
  19. data/lib/familia/autoloader.rb +53 -0
  20. data/lib/familia/base.rb +5 -0
  21. data/lib/familia/data_type.rb +4 -0
  22. data/lib/familia/encryption/encrypted_data.rb +4 -4
  23. data/lib/familia/encryption/manager.rb +6 -4
  24. data/lib/familia/encryption.rb +1 -1
  25. data/lib/familia/errors.rb +3 -0
  26. data/lib/familia/features/autoloadable.rb +113 -0
  27. data/lib/familia/features/encrypted_fields/concealed_string.rb +4 -2
  28. data/lib/familia/features/expiration.rb +4 -0
  29. data/lib/familia/features/external_identifier.rb +3 -3
  30. data/lib/familia/features/quantization.rb +5 -0
  31. data/lib/familia/features/safe_dump.rb +7 -0
  32. data/lib/familia/features.rb +20 -16
  33. data/lib/familia/field_type.rb +2 -0
  34. data/lib/familia/horreum/core/serialization.rb +3 -3
  35. data/lib/familia/horreum/subclass/definition.rb +3 -4
  36. data/lib/familia/horreum.rb +2 -0
  37. data/lib/familia/json_serializer.rb +70 -0
  38. data/lib/familia/logging.rb +12 -10
  39. data/lib/familia/refinements/logger_trace.rb +57 -0
  40. data/lib/familia/refinements/snake_case.rb +40 -0
  41. data/lib/familia/refinements/time_literals.rb +279 -0
  42. data/lib/familia/refinements.rb +3 -49
  43. data/lib/familia/utils.rb +2 -0
  44. data/lib/familia/validation/{test_helpers.rb → validation_helpers.rb} +2 -2
  45. data/lib/familia/validation.rb +1 -1
  46. data/lib/familia/version.rb +1 -1
  47. data/lib/familia.rb +15 -3
  48. data/try/core/autoloader_try.rb +112 -0
  49. data/try/core/extensions_try.rb +38 -21
  50. data/try/core/familia_extended_try.rb +4 -3
  51. data/try/core/time_utils_try.rb +130 -0
  52. data/try/data_types/datatype_base_try.rb +3 -2
  53. data/try/features/autoloadable/autoloadable_try.rb +61 -0
  54. data/try/features/encrypted_fields/concealed_string_core_try.rb +8 -3
  55. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +59 -17
  56. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +36 -12
  57. data/try/features/external_identifier/external_identifier_try.rb +26 -0
  58. data/try/features/feature_improvements_try.rb +2 -1
  59. data/try/features/real_feature_integration_try.rb +1 -1
  60. data/try/features/safe_dump/safe_dump_autoloading_try.rb +111 -0
  61. data/try/helpers/test_helpers.rb +24 -0
  62. data/try/integration/cross_component_try.rb +3 -1
  63. metadata +34 -6
  64. data/CHANGELOG.md +0 -247
  65. data/lib/familia/core_ext.rb +0 -135
  66. data/lib/familia/features/autoloader.rb +0 -57
@@ -17,7 +17,7 @@ module Familia
17
17
 
18
18
  # Get the severity letter from the thread local variable or use
19
19
  # the default. The thread local variable is set in the trace
20
- # method in the LoggerTraceRefinement module. The name of the
20
+ # method in the Familia::Refinements::LoggerTrace module. The name of the
21
21
  # variable `severity_letter` is arbitrary and could be anything.
22
22
  severity_letter = Thread.current[:severity_letter] || severity_letter
23
23
 
@@ -36,7 +36,7 @@ module Familia
36
36
  # == Methods:
37
37
  # trace::
38
38
  # Logs a message at the TRACE level. This method is only available if the
39
- # LoggerTraceRefinement is used.
39
+ # Familia::Refinements::LoggerTrace is used.
40
40
  #
41
41
  # debug::
42
42
  # Logs a message at the DEBUG level. This is used for low-level system information
@@ -59,14 +59,14 @@ module Familia
59
59
  # that will presumably lead the application to abort.
60
60
  #
61
61
  # == Usage:
62
- # To use the Logging module, you need to include the LoggerTraceRefinement module
62
+ # To use the Logging module, you need to include the Familia::Refinements::LoggerTrace module
63
63
  # and use the `using` keyword to enable the refinement. This will add the TRACE
64
64
  # log level and the trace method to the Logger class.
65
65
  #
66
66
  # Example:
67
67
  # require 'logger'
68
68
  #
69
- # module LoggerTraceRefinement
69
+ # module Familia::Refinements::LoggerTrace
70
70
  # refine Logger do
71
71
  # TRACE = 0
72
72
  #
@@ -76,7 +76,7 @@ module Familia
76
76
  # end
77
77
  # end
78
78
  #
79
- # using LoggerTraceRefinement
79
+ # using Familia::Refinements::LoggerTrace
80
80
  #
81
81
  # logger = Logger.new(STDOUT)
82
82
  # logger.trace("This is a trace message")
@@ -86,13 +86,13 @@ module Familia
86
86
  # logger.error("This is an error message")
87
87
  # logger.fatal("This is a fatal message")
88
88
  #
89
- # In this example, the LoggerTraceRefinement module is defined with a refinement
89
+ # In this example, the Familia::Refinements::LoggerTrace module is defined with a refinement
90
90
  # for the Logger class. The TRACE constant and trace method are added to the Logger
91
91
  # class within the refinement. The `using` keyword is used to apply the refinement
92
92
  # in the scope where it's needed.
93
93
  #
94
94
  # == Conditions:
95
- # The trace method and TRACE log level are only available if the LoggerTraceRefinement
95
+ # The trace method and TRACE log level are only available if the Familia::Refinements::LoggerTrace
96
96
  # module is used with the `using` keyword. Without this, the Logger class will not
97
97
  # have the trace method or the TRACE log level.
98
98
  #
@@ -103,7 +103,9 @@ module Familia
103
103
  attr_reader :logger
104
104
 
105
105
  # Gives our logger the ability to use our trace method.
106
- using LoggerTraceRefinement if LoggerTraceRefinement::ENABLED
106
+ if Familia::Refinements::LoggerTrace::ENABLED
107
+ using Familia::Refinements::LoggerTrace
108
+ end
107
109
 
108
110
  def info(*msg)
109
111
  @logger.info(*msg)
@@ -140,13 +142,13 @@ module Familia
140
142
  #
141
143
  # @return [nil]
142
144
  #
143
- # @note This method only executes if LoggerTraceRefinement::ENABLED is true.
145
+ # @note This method only executes if Familia::Refinements::LoggerTrace::ENABLED is true.
144
146
  # @note The dbclient can be a Database object, Redis::Future (used in
145
147
  # pipelined and multi blocks), or nil (when the database connection isn't
146
148
  # relevant).
147
149
  #
148
150
  def trace(label, dbclient, ident, context = nil)
149
- return unless LoggerTraceRefinement::ENABLED
151
+ return unless Familia::Refinements::LoggerTrace::ENABLED
150
152
 
151
153
  # Usually dbclient is a Database object, but it could be
152
154
  # a Redis::Future which is what is used inside of pipelined
@@ -0,0 +1,57 @@
1
+ # lib/familia/refinements/logger_trace.rb
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
+ # Familia::Refinements::LoggerTrace
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 Familia::Refinements::LoggerTrace
22
+ #
23
+ # logger = Logger.new(STDOUT)
24
+ # logger.trace("This is a trace message")
25
+ #
26
+ module Familia
27
+ module Refinements
28
+
29
+ # Familia::Refinements::LoggerTrace
30
+ module LoggerTrace
31
+ unless defined?(ENABLED)
32
+ # Indicates whether trace logging is enabled
33
+ ENABLED = %w[1 true yes].include?(FAMILIA_TRACE).freeze
34
+ # The numeric level for trace logging (same as DEBUG)
35
+ TRACE = 0
36
+ end
37
+
38
+ refine Logger do
39
+ ##
40
+ # Logs a message at the TRACE level.
41
+ #
42
+ # @param progname [String] The program name to include in the log message
43
+ # @yield A block that evaluates to the message to log
44
+ # @return [true] Always returns true
45
+ #
46
+ # @example Logging a trace message
47
+ # logger.trace("MyApp") { "Detailed trace information" }
48
+ def trace(progname = nil, &block)
49
+ Thread.current[:severity_letter] = 'T'
50
+ add(Familia::Refinements::LoggerTrace::TRACE, nil, progname, &block)
51
+ ensure
52
+ Thread.current[:severity_letter] = nil
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,40 @@
1
+ # lib/familia/refinements/snake_case.rb
2
+
3
+ module Familia
4
+ module Refinements
5
+ module SnakeCase
6
+ # We refine String rather than Class or Module because this method operates on
7
+ # string representations of class names (like those from `Class#name`) rather
8
+ # than the class objects themselves. Refining String is safer because it
9
+ # limits its scope to only the subset string manipulation contexts where it is
10
+ # used.
11
+ #
12
+ # Appropriate for converting Ruby class names to database table names, config
13
+ # keys, part of a path or any other snake_case identifiers. The only situation
14
+ # it is not appropriate for is investigating actual snakes.
15
+ refine String do
16
+ # Converts a string from PascalCase/camelCase to snake_case format.
17
+ #
18
+ # @return [String] the snake_case version of the string
19
+ #
20
+ # @example Converting simple CamelCase
21
+ # "FirstName".snake_case #=> "first_name"
22
+ #
23
+ # @example Converting PascalCase with acronyms
24
+ # XMLHttpRequest.name.snake_case #=> "xml_http_request"
25
+ #
26
+ # @example Converting namespaced class names
27
+ # "MyApp::UserAccount".snake_case #=> "user_account"
28
+ #
29
+ # @example Handling mixed case with numbers
30
+ # "parseHTML5Document".snake_case #=> "parse_html5_document"
31
+ def snake_case
32
+ split('::').last
33
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
34
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
35
+ .downcase
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,279 @@
1
+ # lib/familia/refinements/time_literals.rb
2
+
3
+ module Familia
4
+ module Refinements
5
+
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
39
+ # Time unit constants
40
+ PER_MICROSECOND = 0.000001
41
+ PER_MILLISECOND = 0.001
42
+ PER_MINUTE = 60.0
43
+ PER_HOUR = 3600.0
44
+ PER_DAY = 86_400.0
45
+ PER_WEEK = 604_800.0
46
+ PER_YEAR = 31_556_952.0 # 365.2425 days (Gregorian year)
47
+ PER_MONTH = PER_YEAR / 12.0 # 30.437 days (consistent with Gregorian year)
48
+
49
+ UNIT_METHODS = {
50
+ 'y' => :years,
51
+ 'year' => :years,
52
+ 'years' => :years,
53
+ 'mo' => :months,
54
+ 'month' => :months,
55
+ 'months' => :months,
56
+ 'w' => :weeks,
57
+ 'week' => :weeks,
58
+ 'weeks' => :weeks,
59
+ 'd' => :days,
60
+ 'day' => :days,
61
+ 'days' => :days,
62
+ 'h' => :hours,
63
+ 'hour' => :hours,
64
+ 'hours' => :hours,
65
+ 'm' => :minutes,
66
+ 'minute' => :minutes,
67
+ 'minutes' => :minutes,
68
+ 'ms' => :milliseconds,
69
+ 'millisecond' => :milliseconds,
70
+ 'milliseconds' => :milliseconds,
71
+ 'us' => :microseconds,
72
+ 'microsecond' => :microseconds,
73
+ 'microseconds' => :microseconds,
74
+ 'μs' => :microseconds,
75
+ }.freeze
76
+
77
+ refine Numeric do
78
+ def microseconds = seconds * PER_MICROSECOND
79
+ def milliseconds = seconds * PER_MILLISECOND
80
+ def seconds = self
81
+ def minutes = seconds * PER_MINUTE
82
+ def hours = seconds * PER_HOUR
83
+ def days = seconds * PER_DAY
84
+ def weeks = seconds * PER_WEEK
85
+ def months = seconds * PER_MONTH
86
+ def years = seconds * PER_YEAR
87
+
88
+ # Aliases with singular forms
89
+ alias_method :microsecond, :microseconds
90
+ alias_method :millisecond, :milliseconds
91
+ alias_method :second, :seconds
92
+ alias_method :minute, :minutes
93
+ alias_method :hour, :hours
94
+ alias_method :day, :days
95
+ alias_method :week, :weeks
96
+ alias_method :month, :months
97
+ alias_method :year, :years
98
+
99
+ # Shortest aliases
100
+ alias_method :ms, :milliseconds
101
+ alias_method :μs, :microseconds
102
+
103
+ # Seconds -> other time units
104
+ def in_years = seconds / PER_YEAR
105
+ def in_months = seconds / PER_MONTH
106
+ def in_weeks = seconds / PER_WEEK
107
+ def in_days = seconds / PER_DAY
108
+ def in_hours = seconds / PER_HOUR
109
+ def in_minutes = seconds / PER_MINUTE
110
+ def in_milliseconds = seconds / PER_MILLISECOND
111
+ def in_microseconds = seconds / PER_MICROSECOND
112
+ # For semantic purposes
113
+ def in_seconds = seconds
114
+
115
+ # Time manipulation
116
+ def ago = Time.now.utc - seconds
117
+ def from_now = Time.now.utc + seconds
118
+ def before(time) = time - seconds
119
+ def after(time) = time + seconds
120
+ def in_time = Time.at(seconds).utc
121
+
122
+ # Milliseconds conversion
123
+ def to_ms = seconds * 1000.0
124
+
125
+ # Converts seconds to specified time unit
126
+ #
127
+ # @param u [String, Symbol] Unit to convert to
128
+ # @return [Float] Converted time value
129
+ def in_seconds(u = nil)
130
+ return self unless u
131
+
132
+ case UNIT_METHODS.fetch(u.to_s.downcase, nil)
133
+ when :milliseconds then self * PER_MILLISECOND
134
+ when :microseconds then self * PER_MICROSECOND
135
+ when :minutes then self * PER_MINUTE
136
+ when :hours then self * PER_HOUR
137
+ when :days then self * PER_DAY
138
+ when :weeks then self * PER_WEEK
139
+ when :months then self * PER_MONTH
140
+ when :years then self * PER_YEAR
141
+ else self
142
+ end
143
+ end
144
+
145
+ # Converts the number to a human-readable string representation
146
+ #
147
+ # @return [String] A formatted string e.g. "1 day" or "10 seconds"
148
+ #
149
+ # @example
150
+ # 10.to_humanize #=> "10 seconds"
151
+ # 60.to_humanize #=> "1 minute"
152
+ # 3600.to_humanize #=> "1 hour"
153
+ # 86400.to_humanize #=> "1 day"
154
+ def humanize
155
+ gte_zero = positive? || zero?
156
+ duration = (gte_zero ? self : abs) # let's keep it positive up in here
157
+ text = case (s = duration.to_i)
158
+ in 0..59 then "#{s} second#{'s' if s != 1}"
159
+ in 60..3599 then "#{s /= 60} minute#{'s' if s != 1}"
160
+ in 3600..86_399 then "#{s /= 3600} hour#{'s' if s != 1}"
161
+ else "#{s /= 86_400} day#{'s' if s != 1}"
162
+ end
163
+ gte_zero ? text : "#{text} ago"
164
+ end
165
+
166
+ # Converts the number to a human-readable byte representation using binary units
167
+ #
168
+ # @return [String] A formatted string of bytes, KiB, MiB, GiB, or TiB
169
+ #
170
+ # @example
171
+ # 1024.to_bytes #=> "1.00 KiB"
172
+ # 2_097_152.to_bytes #=> "2.00 MiB"
173
+ # 3_221_225_472.to_bytes #=> "3.00 GiB"
174
+ #
175
+ def to_bytes
176
+ units = %w[B KiB MiB GiB TiB]
177
+ size = abs.to_f
178
+ unit = 0
179
+
180
+ while size >= 1024 && unit < units.length - 1
181
+ size /= 1024
182
+ unit += 1
183
+ end
184
+
185
+ format('%3.2f %s', size, units[unit])
186
+ end
187
+
188
+ # Calculates age of timestamp in specified unit from reference time
189
+ #
190
+ # @param unit [String, Symbol] Time unit ('days', 'hours', 'minutes', 'weeks')
191
+ # @param from_time [Time, nil] Reference time (defaults to Time.now.utc)
192
+ # @return [Float] Age in specified unit
193
+ # @example
194
+ # timestamp = 2.days.ago.to_i
195
+ # timestamp.age_in(:days) #=> ~2.0
196
+ # timestamp.age_in('hours') #=> ~48.0
197
+ # timestamp.age_in(:days, 1.day.ago) #=> ~1.0
198
+ def age_in(unit, from_time = nil)
199
+ from_time ||= Time.now.utc
200
+ age_seconds = from_time.to_f - to_f
201
+ case UNIT_METHODS.fetch(unit.to_s.downcase, nil)
202
+ when :days then age_seconds / PER_DAY
203
+ when :hours then age_seconds / PER_HOUR
204
+ when :minutes then age_seconds / PER_MINUTE
205
+ when :weeks then age_seconds / PER_WEEK
206
+ when :months then age_seconds / PER_MONTH
207
+ when :years then age_seconds / PER_YEAR
208
+ else age_seconds
209
+ end
210
+ end
211
+
212
+ # Convenience methods for `age_in(unit)` calls.
213
+ #
214
+ # @param from_time [Time, nil] Reference time (defaults to Time.now.utc)
215
+ # @return [Float] Age in days
216
+ # @example
217
+ # timestamp.days_old #=> 2.5
218
+ def days_old(*) = age_in(:days, *)
219
+ def hours_old(*) = age_in(:hours, *)
220
+ def minutes_old(*) = age_in(:minutes, *)
221
+ def weeks_old(*) = age_in(:weeks, *)
222
+ def months_old(*) = age_in(:months, *)
223
+ def years_old(*) = age_in(:years, *)
224
+
225
+ # Checks if timestamp is older than specified duration in seconds
226
+ #
227
+ # @param duration [Numeric] Duration in seconds to compare against
228
+ # @return [Boolean] true if timestamp is older than duration
229
+ # @note Both older_than? and newer_than? can return false when timestamp
230
+ # is within the same second. Use within? to check this case.
231
+ #
232
+ # @example
233
+ # Time.now.older_than?(1.second) #=> false
234
+ def older_than?(duration)
235
+ self < (Time.now.utc.to_f - duration)
236
+ end
237
+
238
+ # Checks if timestamp is newer than specified duration in the future
239
+ #
240
+ # @example
241
+ # Time.now.newer_than?(1.second) #=> false
242
+ def newer_than?(duration)
243
+ self > (Time.now.utc.to_f + duration)
244
+ end
245
+
246
+ # Checks if timestamp is within specified duration of now (past or future)
247
+ #
248
+ # @param duration [Numeric] Duration in seconds to compare against
249
+ # @return [Boolean] true if timestamp is within duration of now
250
+ # @example
251
+ # 30.minutes.ago.to_i.within?(1.hour) #=> true
252
+ # 30.minutes.from_now.to_i.within?(1.hour) #=> true
253
+ # 2.hours.ago.to_i.within?(1.hour) #=> false
254
+ def within?(duration)
255
+ (self - Time.now.utc.to_f).abs <= duration
256
+ end
257
+ end
258
+
259
+ refine ::String do
260
+ # Converts string time representation to seconds
261
+ #
262
+ # @example
263
+ # "60m".in_seconds #=> 3600.0
264
+ # "2.5h".in_seconds #=> 9000.0
265
+ # "1y".in_seconds #=> 31536000.0
266
+ #
267
+ # @return [Float, nil] Time in seconds or nil if invalid
268
+ def in_seconds
269
+ q, u = scan(/([\d.]+)([a-zA-Zμs]+)?/).flatten
270
+ return nil unless q
271
+
272
+ q = q.to_f
273
+ u ||= 's'
274
+ q.in_seconds(u)
275
+ end
276
+ end
277
+ end
278
+ end
279
+ end
@@ -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_literals'
data/lib/familia/utils.rb CHANGED
@@ -6,6 +6,8 @@ module Familia
6
6
  #
7
7
  module Utils
8
8
 
9
+ using Familia::Refinements::TimeLiterals
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.pre14'.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