cmdx 1.1.0 → 1.1.1

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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/docs.md +9 -0
  3. data/.cursor/prompts/rspec.md +13 -12
  4. data/.cursor/prompts/yardoc.md +11 -6
  5. data/CHANGELOG.md +13 -2
  6. data/README.md +1 -0
  7. data/docs/ai_prompts.md +269 -195
  8. data/docs/basics/call.md +124 -58
  9. data/docs/basics/chain.md +190 -160
  10. data/docs/basics/context.md +242 -154
  11. data/docs/basics/setup.md +302 -32
  12. data/docs/callbacks.md +390 -94
  13. data/docs/configuration.md +181 -65
  14. data/docs/deprecation.md +245 -0
  15. data/docs/getting_started.md +161 -39
  16. data/docs/internationalization.md +590 -70
  17. data/docs/interruptions/exceptions.md +135 -118
  18. data/docs/interruptions/faults.md +150 -125
  19. data/docs/interruptions/halt.md +134 -80
  20. data/docs/logging.md +181 -118
  21. data/docs/middlewares.md +150 -377
  22. data/docs/outcomes/result.md +140 -112
  23. data/docs/outcomes/states.md +134 -99
  24. data/docs/outcomes/statuses.md +204 -146
  25. data/docs/parameters/coercions.md +232 -281
  26. data/docs/parameters/defaults.md +224 -169
  27. data/docs/parameters/definitions.md +289 -141
  28. data/docs/parameters/namespacing.md +250 -161
  29. data/docs/parameters/validations.md +260 -133
  30. data/docs/testing.md +191 -197
  31. data/docs/workflows.md +143 -98
  32. data/lib/cmdx/callback.rb +23 -19
  33. data/lib/cmdx/callback_registry.rb +1 -3
  34. data/lib/cmdx/chain_inspector.rb +23 -23
  35. data/lib/cmdx/chain_serializer.rb +38 -19
  36. data/lib/cmdx/coercion.rb +20 -12
  37. data/lib/cmdx/coercion_registry.rb +51 -32
  38. data/lib/cmdx/configuration.rb +84 -31
  39. data/lib/cmdx/context.rb +32 -21
  40. data/lib/cmdx/core_ext/hash.rb +13 -13
  41. data/lib/cmdx/core_ext/module.rb +1 -1
  42. data/lib/cmdx/core_ext/object.rb +12 -12
  43. data/lib/cmdx/correlator.rb +60 -39
  44. data/lib/cmdx/errors.rb +105 -131
  45. data/lib/cmdx/fault.rb +66 -45
  46. data/lib/cmdx/immutator.rb +20 -21
  47. data/lib/cmdx/lazy_struct.rb +78 -70
  48. data/lib/cmdx/log_formatters/json.rb +1 -1
  49. data/lib/cmdx/log_formatters/key_value.rb +1 -1
  50. data/lib/cmdx/log_formatters/line.rb +1 -1
  51. data/lib/cmdx/log_formatters/logstash.rb +1 -1
  52. data/lib/cmdx/log_formatters/pretty_json.rb +1 -1
  53. data/lib/cmdx/log_formatters/pretty_key_value.rb +1 -1
  54. data/lib/cmdx/log_formatters/pretty_line.rb +1 -1
  55. data/lib/cmdx/log_formatters/raw.rb +2 -2
  56. data/lib/cmdx/logger.rb +19 -14
  57. data/lib/cmdx/logger_ansi.rb +33 -17
  58. data/lib/cmdx/logger_serializer.rb +85 -24
  59. data/lib/cmdx/middleware.rb +39 -21
  60. data/lib/cmdx/middleware_registry.rb +4 -3
  61. data/lib/cmdx/parameter.rb +151 -89
  62. data/lib/cmdx/parameter_inspector.rb +34 -21
  63. data/lib/cmdx/parameter_registry.rb +36 -30
  64. data/lib/cmdx/parameter_serializer.rb +21 -14
  65. data/lib/cmdx/result.rb +136 -135
  66. data/lib/cmdx/result_ansi.rb +31 -17
  67. data/lib/cmdx/result_inspector.rb +32 -27
  68. data/lib/cmdx/result_logger.rb +23 -14
  69. data/lib/cmdx/result_serializer.rb +65 -27
  70. data/lib/cmdx/task.rb +234 -113
  71. data/lib/cmdx/task_deprecator.rb +22 -25
  72. data/lib/cmdx/task_processor.rb +89 -88
  73. data/lib/cmdx/task_serializer.rb +27 -14
  74. data/lib/cmdx/utils/monotonic_runtime.rb +2 -4
  75. data/lib/cmdx/validator.rb +25 -16
  76. data/lib/cmdx/validator_registry.rb +53 -31
  77. data/lib/cmdx/validators/exclusion.rb +1 -1
  78. data/lib/cmdx/validators/format.rb +2 -2
  79. data/lib/cmdx/validators/inclusion.rb +2 -2
  80. data/lib/cmdx/validators/length.rb +2 -2
  81. data/lib/cmdx/validators/numeric.rb +3 -3
  82. data/lib/cmdx/validators/presence.rb +2 -2
  83. data/lib/cmdx/version.rb +1 -1
  84. data/lib/cmdx/workflow.rb +54 -33
  85. data/lib/generators/cmdx/task_generator.rb +6 -6
  86. data/lib/generators/cmdx/workflow_generator.rb +6 -6
  87. metadata +3 -1
@@ -1,89 +1,110 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Thread-local correlation ID management for tracing and tracking execution context.
4
+ # Thread-safe correlation ID management for distributed tracing and request tracking.
5
5
  #
6
- # This module provides utilities for managing correlation IDs within thread-local storage,
7
- # enabling request tracing and execution context tracking across task chains and workflows.
8
- # Each thread maintains its own correlation ID that can be used to correlate related operations.
6
+ # Correlator provides functionality to generate, store, and manage correlation IDs
7
+ # across thread boundaries for request tracing, logging correlation, and distributed
8
+ # system monitoring. Correlation IDs are stored in thread-local storage to ensure
9
+ # thread safety and isolation between concurrent operations.
9
10
  module Correlator
10
11
 
11
12
  THREAD_KEY = :cmdx_correlation_id
12
13
 
13
14
  module_function
14
15
 
15
- # Generates a new unique correlation ID using SecureRandom.
16
- # Prefers UUID v7 when available, falls back to UUID v4.
16
+ # Generates a new correlation ID using the best available UUID algorithm.
17
17
  #
18
- # @return [String] a new UUID string for use as correlation ID
18
+ # Attempts to use UUID v7 (time-ordered) if available in Ruby 3.3+, otherwise
19
+ # falls back to standard UUID v4. UUID v7 provides better database indexing
20
+ # performance and natural time-based ordering for correlation tracking.
19
21
  #
20
- # @example Generate a new correlation ID
21
- # CMDx::Correlator.generate #=> "f47ac10b-58cc-4372-a567-0e02b2c3d479"
22
+ # @return [String] a new UUID correlation ID
23
+ #
24
+ # @example Generate a correlation ID
25
+ # Correlator.generate #=> "01234567-89ab-7def-0123-456789abcdef"
26
+ #
27
+ # @example Using the generated ID for logging
28
+ # correlation_id = Correlator.generate
29
+ # logger.info "Request started", correlation_id: correlation_id
22
30
  def generate
23
31
  return SecureRandom.uuid_v7 if SecureRandom.respond_to?(:uuid_v7)
24
32
 
25
33
  SecureRandom.uuid
26
34
  end
27
35
 
28
- # Retrieves the current thread's correlation ID.
36
+ # Retrieves the current correlation ID for the active thread.
29
37
  #
30
- # @return [String, nil] the current correlation ID or nil if not set
38
+ # Returns the correlation ID that has been set for the current thread's
39
+ # execution context. Returns nil if no correlation ID has been established
40
+ # for the current thread.
31
41
  #
32
- # @example Get current correlation ID
33
- # CMDx::Correlator.id #=> "f47ac10b-58cc-4372-a567-0e02b2c3d479"
42
+ # @return [String, nil] the current thread's correlation ID, or nil if not set
34
43
  #
35
- # @example When no correlation ID is set
36
- # CMDx::Correlator.id #=> nil
44
+ # @example Get current correlation ID
45
+ # Correlator.id #=> "01234567-89ab-7def-0123-456789abcdef"
37
46
  def id
38
47
  Thread.current[THREAD_KEY]
39
48
  end
40
49
 
41
- # Sets the current thread's correlation ID.
50
+ # Sets the correlation ID for the current thread.
42
51
  #
43
- # @param value [String, Symbol] the correlation ID to set
52
+ # Establishes a correlation ID in thread-local storage that will be
53
+ # accessible to all operations within the current thread's execution
54
+ # context. This ID will persist until explicitly changed or cleared.
44
55
  #
45
- # @return [String, Symbol] the value that was set
56
+ # @param value [String, Symbol] the correlation ID to set for this thread
46
57
  #
47
- # @example Set correlation ID
48
- # CMDx::Correlator.id = "custom-trace-123"
49
- # CMDx::Correlator.id #=> "custom-trace-123"
58
+ # @return [String, Symbol] the assigned correlation ID value
59
+ #
60
+ # @example Set a custom correlation ID
61
+ # Correlator.id = "custom-trace-123"
62
+ #
63
+ # @example Set a generated correlation ID
64
+ # Correlator.id = Correlator.generate
50
65
  def id=(value)
51
66
  Thread.current[THREAD_KEY] = value
52
67
  end
53
68
 
54
- # Clears the current thread's correlation ID.
69
+ # Clears the correlation ID for the current thread.
55
70
  #
56
- # @return [nil] always returns nil
71
+ # Removes the correlation ID from thread-local storage, effectively
72
+ # resetting the correlation context for the current thread. Useful
73
+ # for cleanup between request processing or test scenarios.
74
+ #
75
+ # @return [nil] always returns nil after clearing
57
76
  #
58
77
  # @example Clear correlation ID
59
- # CMDx::Correlator.clear
60
- # CMDx::Correlator.id #=> nil
78
+ # Correlator.clear
79
+ # Correlator.id #=> nil
61
80
  def clear
62
81
  Thread.current[THREAD_KEY] = nil
63
82
  end
64
83
 
65
- # Temporarily uses a correlation ID for the duration of a block.
66
- # Restores the previous correlation ID after the block completes, even if an exception occurs.
84
+ # Temporarily sets a correlation ID for the duration of a block execution.
85
+ #
86
+ # Establishes a correlation ID context for the provided block, automatically
87
+ # restoring the previous correlation ID when the block completes. This ensures
88
+ # proper correlation ID isolation for nested operations or temporary contexts.
67
89
  #
68
- # @param value [String, Symbol] the correlation ID to use during block execution
90
+ # @param value [String, Symbol] the temporary correlation ID to use during block execution
69
91
  #
70
- # @return [Object] the result of the block execution
92
+ # @return [Object] the return value of the executed block
71
93
  #
72
- # @raise [TypeError] if value is not a String or Symbol
94
+ # @raise [TypeError] if the provided value is not a String or Symbol
73
95
  #
74
96
  # @example Use temporary correlation ID
75
- # CMDx::Correlator.use("temp-123") do
76
- # puts CMDx::Correlator.id # => "temp-123"
77
- # # ... perform work ...
97
+ # Correlator.use("temp-id-123") do
98
+ # logger.info "Processing with temporary ID"
99
+ # perform_operation
78
100
  # end
79
- # # Previous correlation ID is restored
80
101
  #
81
- # @example With exception handling
82
- # CMDx::Correlator.id = "original"
83
- # CMDx::Correlator.use("temp") do
84
- # raise StandardError, "oops"
102
+ # @example Nested correlation contexts
103
+ # Correlator.id = "parent-id"
104
+ # Correlator.use("child-id") do
105
+ # puts Correlator.id #=> "child-id"
85
106
  # end
86
- # # CMDx::Correlator.id is still "original"
107
+ # puts Correlator.id #=> "parent-id"
87
108
  def use(value)
88
109
  unless value.is_a?(String) || value.is_a?(Symbol)
89
110
  raise TypeError,
data/lib/cmdx/errors.rb CHANGED
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Error collection and validation system for CMDx tasks.
5
- #
6
- # This class manages error messages associated with specific attributes,
7
- # providing a flexible API for adding, querying, and formatting validation
8
- # errors. It supports both individual error messages and collections of
9
- # errors per attribute, with various convenience methods for error handling
10
- # and display.
4
+ # Container for collecting and managing validation and execution errors by attribute.
5
+ # Provides a comprehensive API for adding, querying, and formatting error messages
6
+ # with support for multiple errors per attribute and various output formats.
11
7
  class Errors
12
8
 
13
9
  cmdx_attr_delegator :clear, :delete, :empty?, :key?, :keys, :size, :values,
@@ -31,42 +27,34 @@ module CMDx
31
27
  # Alias for {#key?}. Checks if an attribute has error messages.
32
28
  alias include? key?
33
29
 
34
- # Creates a new error collection with an empty internal hash.
30
+ # Creates a new empty errors collection.
35
31
  #
36
- # @return [Errors] the newly created error collection
32
+ # @return [Errors] a new errors instance with empty internal hash
37
33
  #
38
- # @example Create a new error collection
34
+ # @example Create new errors collection
39
35
  # errors = CMDx::Errors.new
40
36
  # errors.empty? # => true
41
37
  def initialize
42
38
  @errors = {}
43
39
  end
44
40
 
45
- # Adds an error message to the specified attribute.
46
- #
47
- # If the attribute already has errors, the new message is appended to the
48
- # existing array. Duplicate messages are automatically removed to ensure
49
- # each error message appears only once per attribute.
41
+ # Adds an error message to the specified attribute. Automatically handles
42
+ # array initialization and prevents duplicate messages for the same attribute.
50
43
  #
51
44
  # @param key [Symbol, String] the attribute name to associate the error with
52
- # @param value [String] the error message to add
45
+ # @param value [String, Object] the error message or error object to add
53
46
  #
54
- # @return [Array<String>] the array of error messages for the attribute
47
+ # @return [Array] the updated array of error messages for the attribute
55
48
  #
56
- # @example Add an error to an attribute
57
- # errors = CMDx::Errors.new
58
- # errors.add(:name, "is required")
59
- # errors[:name] # => ["is required"]
60
- #
61
- # @example Add multiple errors to the same attribute
62
- # errors.add(:email, "is required")
63
- # errors.add(:email, "must be valid")
64
- # errors[:email] # => ["is required", "must be valid"]
65
- #
66
- # @example Duplicate errors are automatically removed
67
- # errors.add(:age, "must be positive")
68
- # errors.add(:age, "must be positive")
69
- # errors[:age] # => ["must be positive"]
49
+ # @example Add error to attribute
50
+ # errors.add(:name, "can't be blank")
51
+ # errors.add(:name, "is too short")
52
+ # errors.messages_for(:name) # => ["can't be blank", "is too short"]
53
+ #
54
+ # @example Prevent duplicate errors
55
+ # errors.add(:email, "is invalid")
56
+ # errors.add(:email, "is invalid")
57
+ # errors.messages_for(:email) # => ["is invalid"]
70
58
  def add(key, value)
71
59
  errors[key] ||= []
72
60
  errors[key] << value
@@ -77,18 +65,17 @@ module CMDx
77
65
  # Checks if a specific error message has been added to an attribute.
78
66
  #
79
67
  # @param key [Symbol, String] the attribute name to check
80
- # @param val [String] the error message to look for
68
+ # @param val [String, Object] the error message to look for
81
69
  #
82
- # @return [Boolean] true if the specific error message exists for the attribute
70
+ # @return [Boolean] true if the error exists for the attribute, false otherwise
83
71
  #
84
- # @example Check if a specific error exists
85
- # errors = CMDx::Errors.new
86
- # errors.add(:name, "is required")
87
- # errors.added?(:name, "is required") # => true
72
+ # @example Check for specific error
73
+ # errors.add(:name, "can't be blank")
74
+ # errors.added?(:name, "can't be blank") # => true
88
75
  # errors.added?(:name, "is invalid") # => false
89
76
  #
90
- # @example Check error on attribute without errors
91
- # errors.added?(:missing, "any error") # => false
77
+ # @example Check non-existent attribute
78
+ # errors.added?(:nonexistent, "error") # => false
92
79
  def added?(key, val)
93
80
  return false unless key?(key)
94
81
 
@@ -96,19 +83,20 @@ module CMDx
96
83
  end
97
84
  alias of_kind? added?
98
85
 
99
- # Iterates over all error messages, yielding the attribute and message.
86
+ # Iterates over each error, yielding the attribute name and error message.
100
87
  #
101
- # @yield [Symbol, String] the attribute name and error message
88
+ # @yield [key, value] gives the attribute name and error message for each error
89
+ # @yieldparam key [Symbol, String] the attribute name
90
+ # @yieldparam value [String, Object] the error message
102
91
  #
103
- # @return [void]
92
+ # @return [Hash] the errors hash when no block given
104
93
  #
105
94
  # @example Iterate over all errors
106
- # errors = CMDx::Errors.new
107
- # errors.add(:name, "is required")
95
+ # errors.add(:name, "can't be blank")
108
96
  # errors.add(:email, "is invalid")
109
97
  # errors.each { |attr, msg| puts "#{attr}: #{msg}" }
110
98
  # # Output:
111
- # # name: is required
99
+ # # name: can't be blank
112
100
  # # email: is invalid
113
101
  def each
114
102
  errors.each_key do |key|
@@ -116,35 +104,30 @@ module CMDx
116
104
  end
117
105
  end
118
106
 
119
- # Formats an attribute and error message into a full error message.
107
+ # Formats an error message by combining the attribute name and error value.
120
108
  #
121
109
  # @param key [Symbol, String] the attribute name
122
- # @param value [String] the error message
110
+ # @param value [String, Object] the error message
123
111
  #
124
112
  # @return [String] the formatted full error message
125
113
  #
126
- # @example Format a full error message
127
- # errors = CMDx::Errors.new
128
- # errors.full_message(:name, "is required") # => "name is required"
129
- #
130
- # @example Format with different attribute types
131
- # errors.full_message("email", "must be valid") # => "email must be valid"
114
+ # @example Format error message
115
+ # errors.full_message(:name, "can't be blank") # => "name can't be blank"
116
+ # errors.full_message(:email, "is invalid") # => "email is invalid"
132
117
  def full_message(key, value)
133
118
  "#{key} #{value}"
134
119
  end
135
120
 
136
- # Returns an array of all full error messages across all attributes.
121
+ # Returns all error messages formatted with their attribute names.
137
122
  #
138
123
  # @return [Array<String>] array of formatted error messages
139
124
  #
140
- # @example Get all full error messages
141
- # errors = CMDx::Errors.new
142
- # errors.add(:name, "is required")
125
+ # @example Get all formatted messages
126
+ # errors.add(:name, "can't be blank")
143
127
  # errors.add(:email, "is invalid")
144
- # errors.full_messages # => ["name is required", "email is invalid"]
128
+ # errors.full_messages # => ["name can't be blank", "email is invalid"]
145
129
  #
146
- # @example Empty errors return empty array
147
- # errors = CMDx::Errors.new
130
+ # @example Empty errors collection
148
131
  # errors.full_messages # => []
149
132
  def full_messages
150
133
  errors.each_with_object([]) do |(key, arr), memo|
@@ -153,89 +136,80 @@ module CMDx
153
136
  end
154
137
  alias to_a full_messages
155
138
 
156
- # Returns full error messages for a specific attribute.
139
+ # Returns formatted error messages for a specific attribute.
157
140
  #
158
141
  # @param key [Symbol, String] the attribute name to get messages for
159
142
  #
160
143
  # @return [Array<String>] array of formatted error messages for the attribute
161
144
  #
162
- # @example Get full messages for a specific attribute
163
- # errors = CMDx::Errors.new
164
- # errors.add(:name, "is required")
145
+ # @example Get messages for existing attribute
146
+ # errors.add(:name, "can't be blank")
165
147
  # errors.add(:name, "is too short")
166
- # errors.full_messages_for(:name) # => ["name is required", "name is too short"]
148
+ # errors.full_messages_for(:name) # => ["name can't be blank", "name is too short"]
167
149
  #
168
- # @example Get messages for attribute without errors
169
- # errors.full_messages_for(:missing) # => []
150
+ # @example Get messages for non-existent attribute
151
+ # errors.full_messages_for(:nonexistent) # => []
170
152
  def full_messages_for(key)
171
153
  return [] unless key?(key)
172
154
 
173
155
  errors[key].map { |val| full_message(key, val) }
174
156
  end
175
157
 
176
- # Checks if the error collection contains any errors.
158
+ # Checks if the errors collection contains any validation errors.
177
159
  #
178
- # @return [Boolean] true if there are any errors present
160
+ # @return [Boolean] true if there are any errors present, false otherwise
179
161
  #
180
- # @example Check if errors are present
181
- # errors = CMDx::Errors.new
182
- # errors.invalid? # => false
183
- # errors.add(:name, "is required")
162
+ # @example Check invalid state
163
+ # errors.add(:name, "can't be blank")
184
164
  # errors.invalid? # => true
165
+ #
166
+ # @example Check valid state
167
+ # errors.invalid? # => false
185
168
  def invalid?
186
169
  !valid?
187
170
  end
188
171
 
189
- # Maps over all error messages, yielding the attribute and message to a block.
190
- #
191
- # Similar to {#each}, but returns an array of the block's return values
192
- # instead of iterating without collecting results.
172
+ # Transforms each error using the provided block and returns results as an array.
193
173
  #
194
- # @yield [Symbol, String] the attribute name and error message
195
- # @yieldreturn [Object] the transformed value for each error message
174
+ # @yield [key, value] gives the attribute name and error message for transformation
175
+ # @yieldparam key [Symbol, String] the attribute name
176
+ # @yieldparam value [String, Object] the error message
177
+ # @yieldreturn [Object] the transformed value to include in result array
196
178
  #
197
- # @return [Array<Object>] array of transformed values from the block
179
+ # @return [Array] array of transformed error values
198
180
  #
199
- # @example Transform error messages to a custom format
200
- # errors = CMDx::Errors.new
201
- # errors.add(:name, "is required")
181
+ # @example Transform errors to uppercase messages
182
+ # errors.add(:name, "can't be blank")
202
183
  # errors.add(:email, "is invalid")
203
- # result = errors.map { |attr, msg| "#{attr.upcase}: #{msg}" }
204
- # result # => ["NAME: is required", "EMAIL: is invalid"]
205
- #
206
- # @example Extract only attribute names with errors
207
- # errors.map { |attr, _msg| attr } # => [:name, :email]
184
+ # errors.map { |attr, msg| msg.upcase } # => ["CAN'T BE BLANK", "IS INVALID"]
208
185
  #
209
- # @example Return empty array for no errors
210
- # empty_errors = CMDx::Errors.new
211
- # empty_errors.map { |attr, msg| [attr, msg] } # => []
186
+ # @example Create custom error objects
187
+ # errors.map { |attr, msg| { attribute: attr, message: msg } }
188
+ # # => [{ attribute: :name, message: "can't be blank" }]
212
189
  def map
213
190
  errors.each_with_object([]) do |(key, _arr), memo|
214
191
  memo.concat(errors[key].map { |val| yield(key, val) })
215
192
  end
216
193
  end
217
194
 
218
- # Merges another hash of errors into this collection.
195
+ # Merges another errors hash into this collection, combining arrays for duplicate keys.
219
196
  #
220
- # When the same attribute exists in both collections, the error arrays
221
- # are combined and duplicates are removed.
197
+ # @param hash [Hash] hash of errors to merge, with attribute keys and message arrays as values
222
198
  #
223
- # @param hash [Hash] hash of errors to merge, with attribute names as keys
199
+ # @return [Hash] the updated internal errors hash
224
200
  #
225
- # @return [Hash] the merged errors hash
226
- #
227
- # @example Merge errors from another hash
228
- # errors = CMDx::Errors.new
229
- # errors.add(:name, "is required")
201
+ # @example Merge additional errors
202
+ # errors.add(:name, "can't be blank")
230
203
  # other_errors = { email: ["is invalid"], name: ["is too short"] }
231
204
  # errors.merge!(other_errors)
232
- # errors[:name] # => ["is required", "is too short"]
233
- # errors[:email] # => ["is invalid"]
234
- #
235
- # @example Merge with duplicate errors
236
- # errors.add(:age, "must be positive")
237
- # errors.merge!(age: ["must be positive", "must be an integer"])
238
- # errors[:age] # => ["must be positive", "must be an integer"]
205
+ # errors.messages_for(:name) # => ["can't be blank", "is too short"]
206
+ # errors.messages_for(:email) # => ["is invalid"]
207
+ #
208
+ # @example Merge with duplicate prevention
209
+ # errors.add(:name, "can't be blank")
210
+ # duplicate_errors = { name: ["can't be blank", "is required"] }
211
+ # errors.merge!(duplicate_errors)
212
+ # errors.messages_for(:name) # => ["can't be blank", "is required"]
239
213
  def merge!(hash)
240
214
  errors.merge!(hash) do |_, arr1, arr2|
241
215
  arr3 = arr1 + arr2
@@ -244,20 +218,19 @@ module CMDx
244
218
  end
245
219
  end
246
220
 
247
- # Returns the raw error messages for a specific attribute.
221
+ # Returns the raw error messages for a specific attribute without formatting.
248
222
  #
249
223
  # @param key [Symbol, String] the attribute name to get messages for
250
224
  #
251
- # @return [Array<String>] array of raw error messages for the attribute
225
+ # @return [Array] array of raw error messages for the attribute
252
226
  #
253
- # @example Get raw messages for an attribute
254
- # errors = CMDx::Errors.new
255
- # errors.add(:name, "is required")
227
+ # @example Get raw messages for existing attribute
228
+ # errors.add(:name, "can't be blank")
256
229
  # errors.add(:name, "is too short")
257
- # errors.messages_for(:name) # => ["is required", "is too short"]
230
+ # errors.messages_for(:name) # => ["can't be blank", "is too short"]
258
231
  #
259
- # @example Get messages for attribute without errors
260
- # errors.messages_for(:missing) # => []
232
+ # @example Get messages for non-existent attribute
233
+ # errors.messages_for(:nonexistent) # => []
261
234
  def messages_for(key)
262
235
  return [] unless key?(key)
263
236
 
@@ -265,35 +238,36 @@ module CMDx
265
238
  end
266
239
  alias [] messages_for
267
240
 
268
- # Checks if the error collection contains any errors.
241
+ # Checks if the errors collection contains any validation errors.
269
242
  #
270
- # @return [Boolean] true if there are any errors present
243
+ # @return [Boolean] true if there are any errors present, false otherwise
271
244
  #
272
- # @example Check if errors are present
273
- # errors = CMDx::Errors.new
274
- # errors.present? # => false
275
- # errors.add(:name, "is required")
245
+ # @example Check for errors presence
246
+ # errors.add(:name, "can't be blank")
276
247
  # errors.present? # => true
248
+ #
249
+ # @example Check empty collection
250
+ # errors.present? # => false
277
251
  def present?
278
252
  !blank?
279
253
  end
280
254
 
281
- # Converts the error collection to a hash representation.
255
+ # Converts the errors collection to a hash format, optionally with full formatted messages.
282
256
  #
283
- # @param full_messages [Boolean] whether to include full formatted messages
257
+ # @param full_messages [Boolean] whether to format messages with attribute names
284
258
  #
285
259
  # @return [Hash] hash representation of errors
260
+ # @option return [Array<String>] attribute_name array of error messages (raw or formatted)
286
261
  #
287
- # @example Get raw error messages hash
288
- # errors = CMDx::Errors.new
289
- # errors.add(:name, "is required")
290
- # errors.to_hash # => { name: ["is required"] }
262
+ # @example Get raw errors hash
263
+ # errors.add(:name, "can't be blank")
264
+ # errors.add(:email, "is invalid")
265
+ # errors.to_hash # => { :name => ["can't be blank"], :email => ["is invalid"] }
291
266
  #
292
- # @example Get full formatted messages hash
293
- # errors.to_hash(true) # => { name: ["name is required"] }
267
+ # @example Get formatted errors hash
268
+ # errors.to_hash(true) # => { :name => ["name can't be blank"], :email => ["email is invalid"] }
294
269
  #
295
- # @example Empty errors return empty hash
296
- # errors = CMDx::Errors.new
270
+ # @example Empty errors collection
297
271
  # errors.to_hash # => {}
298
272
  def to_hash(full_messages = false)
299
273
  return errors unless full_messages