cmdx 1.0.1 → 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 (170) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/docs.md +9 -0
  3. data/.cursor/prompts/rspec.md +21 -0
  4. data/.cursor/prompts/yardoc.md +13 -0
  5. data/.rubocop.yml +2 -0
  6. data/CHANGELOG.md +29 -3
  7. data/README.md +2 -1
  8. data/docs/ai_prompts.md +269 -195
  9. data/docs/basics/call.md +126 -60
  10. data/docs/basics/chain.md +190 -160
  11. data/docs/basics/context.md +242 -154
  12. data/docs/basics/setup.md +302 -32
  13. data/docs/callbacks.md +382 -119
  14. data/docs/configuration.md +211 -49
  15. data/docs/deprecation.md +245 -0
  16. data/docs/getting_started.md +161 -39
  17. data/docs/internationalization.md +590 -70
  18. data/docs/interruptions/exceptions.md +135 -118
  19. data/docs/interruptions/faults.md +152 -127
  20. data/docs/interruptions/halt.md +134 -80
  21. data/docs/logging.md +183 -120
  22. data/docs/middlewares.md +165 -392
  23. data/docs/outcomes/result.md +140 -112
  24. data/docs/outcomes/states.md +134 -99
  25. data/docs/outcomes/statuses.md +204 -146
  26. data/docs/parameters/coercions.md +251 -289
  27. data/docs/parameters/defaults.md +224 -169
  28. data/docs/parameters/definitions.md +289 -141
  29. data/docs/parameters/namespacing.md +250 -161
  30. data/docs/parameters/validations.md +247 -159
  31. data/docs/testing.md +196 -203
  32. data/docs/workflows.md +146 -101
  33. data/lib/cmdx/.DS_Store +0 -0
  34. data/lib/cmdx/callback.rb +39 -55
  35. data/lib/cmdx/callback_registry.rb +80 -73
  36. data/lib/cmdx/chain.rb +65 -122
  37. data/lib/cmdx/chain_inspector.rb +23 -116
  38. data/lib/cmdx/chain_serializer.rb +34 -146
  39. data/lib/cmdx/coercion.rb +57 -0
  40. data/lib/cmdx/coercion_registry.rb +113 -0
  41. data/lib/cmdx/coercions/array.rb +18 -36
  42. data/lib/cmdx/coercions/big_decimal.rb +21 -33
  43. data/lib/cmdx/coercions/boolean.rb +21 -40
  44. data/lib/cmdx/coercions/complex.rb +18 -31
  45. data/lib/cmdx/coercions/date.rb +20 -39
  46. data/lib/cmdx/coercions/date_time.rb +22 -39
  47. data/lib/cmdx/coercions/float.rb +19 -32
  48. data/lib/cmdx/coercions/hash.rb +22 -41
  49. data/lib/cmdx/coercions/integer.rb +20 -33
  50. data/lib/cmdx/coercions/rational.rb +20 -32
  51. data/lib/cmdx/coercions/string.rb +23 -31
  52. data/lib/cmdx/coercions/time.rb +24 -40
  53. data/lib/cmdx/coercions/virtual.rb +14 -31
  54. data/lib/cmdx/configuration.rb +101 -162
  55. data/lib/cmdx/context.rb +34 -166
  56. data/lib/cmdx/core_ext/hash.rb +42 -67
  57. data/lib/cmdx/core_ext/module.rb +35 -79
  58. data/lib/cmdx/core_ext/object.rb +63 -98
  59. data/lib/cmdx/correlator.rb +59 -154
  60. data/lib/cmdx/error.rb +37 -202
  61. data/lib/cmdx/errors.rb +153 -216
  62. data/lib/cmdx/fault.rb +68 -150
  63. data/lib/cmdx/faults.rb +26 -137
  64. data/lib/cmdx/immutator.rb +22 -110
  65. data/lib/cmdx/lazy_struct.rb +110 -186
  66. data/lib/cmdx/log_formatters/json.rb +14 -40
  67. data/lib/cmdx/log_formatters/key_value.rb +14 -40
  68. data/lib/cmdx/log_formatters/line.rb +14 -48
  69. data/lib/cmdx/log_formatters/logstash.rb +14 -57
  70. data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
  71. data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
  72. data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
  73. data/lib/cmdx/log_formatters/raw.rb +19 -49
  74. data/lib/cmdx/logger.rb +22 -79
  75. data/lib/cmdx/logger_ansi.rb +31 -72
  76. data/lib/cmdx/logger_serializer.rb +74 -103
  77. data/lib/cmdx/middleware.rb +56 -60
  78. data/lib/cmdx/middleware_registry.rb +82 -77
  79. data/lib/cmdx/middlewares/correlate.rb +41 -226
  80. data/lib/cmdx/middlewares/timeout.rb +46 -185
  81. data/lib/cmdx/parameter.rb +167 -183
  82. data/lib/cmdx/parameter_evaluator.rb +231 -0
  83. data/lib/cmdx/parameter_inspector.rb +37 -55
  84. data/lib/cmdx/parameter_registry.rb +65 -84
  85. data/lib/cmdx/parameter_serializer.rb +32 -76
  86. data/lib/cmdx/railtie.rb +24 -107
  87. data/lib/cmdx/result.rb +254 -259
  88. data/lib/cmdx/result_ansi.rb +28 -80
  89. data/lib/cmdx/result_inspector.rb +34 -70
  90. data/lib/cmdx/result_logger.rb +23 -77
  91. data/lib/cmdx/result_serializer.rb +59 -125
  92. data/lib/cmdx/rspec/matchers.rb +28 -0
  93. data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
  94. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
  95. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
  96. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
  97. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
  98. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
  99. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
  100. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
  101. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
  102. data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
  103. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
  104. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
  105. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
  106. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
  107. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
  108. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
  109. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
  110. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
  111. data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
  112. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
  113. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
  114. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
  115. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
  116. data/lib/cmdx/task.rb +336 -427
  117. data/lib/cmdx/task_deprecator.rb +52 -0
  118. data/lib/cmdx/task_processor.rb +246 -0
  119. data/lib/cmdx/task_serializer.rb +34 -69
  120. data/lib/cmdx/utils/ansi_color.rb +13 -89
  121. data/lib/cmdx/utils/log_timestamp.rb +13 -42
  122. data/lib/cmdx/utils/monotonic_runtime.rb +11 -63
  123. data/lib/cmdx/utils/name_affix.rb +21 -71
  124. data/lib/cmdx/validator.rb +57 -0
  125. data/lib/cmdx/validator_registry.rb +108 -0
  126. data/lib/cmdx/validators/exclusion.rb +55 -94
  127. data/lib/cmdx/validators/format.rb +31 -85
  128. data/lib/cmdx/validators/inclusion.rb +65 -110
  129. data/lib/cmdx/validators/length.rb +117 -133
  130. data/lib/cmdx/validators/numeric.rb +123 -130
  131. data/lib/cmdx/validators/presence.rb +38 -79
  132. data/lib/cmdx/version.rb +1 -7
  133. data/lib/cmdx/workflow.rb +58 -330
  134. data/lib/cmdx.rb +1 -1
  135. data/lib/generators/cmdx/install_generator.rb +14 -31
  136. data/lib/generators/cmdx/task_generator.rb +39 -55
  137. data/lib/generators/cmdx/templates/install.rb +24 -6
  138. data/lib/generators/cmdx/workflow_generator.rb +41 -66
  139. data/lib/locales/ar.yml +0 -1
  140. data/lib/locales/cs.yml +0 -1
  141. data/lib/locales/da.yml +0 -1
  142. data/lib/locales/de.yml +0 -1
  143. data/lib/locales/el.yml +0 -1
  144. data/lib/locales/en.yml +0 -1
  145. data/lib/locales/es.yml +0 -1
  146. data/lib/locales/fi.yml +0 -1
  147. data/lib/locales/fr.yml +0 -1
  148. data/lib/locales/he.yml +0 -1
  149. data/lib/locales/hi.yml +0 -1
  150. data/lib/locales/it.yml +0 -1
  151. data/lib/locales/ja.yml +0 -1
  152. data/lib/locales/ko.yml +0 -1
  153. data/lib/locales/nl.yml +0 -1
  154. data/lib/locales/no.yml +0 -1
  155. data/lib/locales/pl.yml +0 -1
  156. data/lib/locales/pt.yml +0 -1
  157. data/lib/locales/ru.yml +0 -1
  158. data/lib/locales/sv.yml +0 -1
  159. data/lib/locales/th.yml +0 -1
  160. data/lib/locales/tr.yml +0 -1
  161. data/lib/locales/vi.yml +0 -1
  162. data/lib/locales/zh.yml +0 -1
  163. metadata +36 -8
  164. data/lib/cmdx/parameter_validator.rb +0 -81
  165. data/lib/cmdx/parameter_value.rb +0 -244
  166. data/lib/cmdx/parameters_inspector.rb +0 -72
  167. data/lib/cmdx/parameters_serializer.rb +0 -115
  168. data/lib/cmdx/rspec/result_matchers.rb +0 -917
  169. data/lib/cmdx/rspec/task_matchers.rb +0 -570
  170. data/lib/cmdx/validators/custom.rb +0 -102
data/lib/cmdx/errors.rb CHANGED
@@ -1,151 +1,60 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- ##
5
- # Errors provides a collection-like interface for managing validation and execution errors
6
- # within CMDx tasks. It offers Rails-inspired methods for adding, querying, and formatting
7
- # error messages, making it easy to accumulate and present multiple error conditions.
8
- #
9
- # The Errors class is designed to work seamlessly with CMDx's parameter validation system,
10
- # automatically collecting validation failures and providing convenient methods for
11
- # error reporting and user feedback.
12
- #
13
- #
14
- # @example Basic error management
15
- # errors = Errors.new
16
- # errors.add(:email, "is required")
17
- # errors.add(:email, "is invalid format")
18
- # errors.add(:password, "is too short")
19
- #
20
- # errors.empty? #=> false
21
- # errors.size #=> 2 (attributes with errors)
22
- # errors[:email] #=> ["is required", "is invalid format"]
23
- # errors.full_messages #=> ["email is required", "email is invalid format", "password is too short"]
24
- #
25
- # @example Task integration
26
- # class CreateUserTask < CMDx::Task
27
- # required :email, type: :string
28
- # required :password, type: :string
29
- #
30
- # def call
31
- # validate_email
32
- # validate_password
33
- #
34
- # if errors.present?
35
- # fail!(reason: "Validation failed", validation_errors: errors.full_messages)
36
- # end
37
- #
38
- # create_user
39
- # end
40
- #
41
- # private
42
- #
43
- # def validate_email
44
- # errors.add(:email, "is required") if email.blank?
45
- # errors.add(:email, "is invalid") unless email.include?("@")
46
- # end
47
- #
48
- # def validate_password
49
- # errors.add(:password, "is too short") if password.length < 8
50
- # end
51
- # end
52
- #
53
- # @example Error querying and formatting
54
- # errors = Errors.new
55
- # errors.add(:name, "cannot be blank")
56
- # errors.add(:age, "must be a number")
57
- #
58
- # # Checking for specific errors
59
- # errors.key?(:name) #=> true
60
- # errors.added?(:name, "cannot be blank") #=> true
61
- # errors.of_kind?(:age, "must be positive") #=> false
62
- #
63
- # # Getting messages
64
- # errors.messages_for(:name) #=> ["cannot be blank"]
65
- # errors.full_messages_for(:name) #=> ["name cannot be blank"]
66
- #
67
- # # Converting to hash
68
- # errors.to_hash #=> {:name => ["cannot be blank"], :age => ["must be a number"]}
69
- # errors.to_hash(true) #=> {:name => ["name cannot be blank"], :age => ["age must be a number"]}
70
- #
71
- # @example Rails-style validation
72
- # class ValidateUserTask < CMDx::Task
73
- # required :user_data, type: :hash
74
- #
75
- # def call
76
- # user = user_data
77
- #
78
- # errors.add(:email, "can't be blank") if user[:email].blank?
79
- # errors.add(:email, "is invalid") unless valid_email?(user[:email])
80
- # errors.add(:age, "must be at least 18") if user[:age] && user[:age] < 18
81
- #
82
- # return if errors.invalid?
83
- #
84
- # context.validated_user = user
85
- # end
86
- # end
87
- #
88
- # @see Task Task base class with errors attribute
89
- # @see Parameter Parameter validation integration
90
- # @see ValidationError Individual validation errors
91
- # @since 1.0.0
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.
92
7
  class Errors
93
8
 
94
- __cmdx_attr_delegator :clear, :delete, :each, :empty?, :key?, :keys, :size, :values,
95
- to: :errors
9
+ cmdx_attr_delegator :clear, :delete, :empty?, :key?, :keys, :size, :values,
10
+ to: :errors
96
11
 
97
- ##
98
- # @!attribute [r] errors
99
- # @return [Hash] internal hash storing error messages by attribute
12
+ # @return [Hash] internal hash storing error messages by attribute
100
13
  attr_reader :errors
101
14
 
102
- ##
103
- # @!method attribute_names
104
- # @return [Array<Symbol>] list of attributes that have errors
15
+ # @return [Array<Symbol>] list of attributes that have errors
105
16
  alias attribute_names keys
106
17
 
107
- ##
108
- # @!method blank?
109
- # @return [Boolean] true if no errors are present
18
+ # @return [Boolean] true if no errors are present
110
19
  alias blank? empty?
111
20
 
112
- ##
113
- # @!method valid?
114
- # @return [Boolean] true if no errors are present
21
+ # @return [Boolean] true if no errors are present
115
22
  alias valid? empty?
116
23
 
117
- ##
118
24
  # Alias for {#key?}. Checks if an attribute has error messages.
119
25
  alias has_key? key?
120
26
 
121
- ##
122
27
  # Alias for {#key?}. Checks if an attribute has error messages.
123
28
  alias include? key?
124
29
 
125
- ##
126
- # Initializes a new Errors collection.
127
- # Creates an empty hash to store error messages by attribute.
30
+ # Creates a new empty errors collection.
128
31
  #
129
- # @example
130
- # errors = Errors.new
131
- # errors.empty? #=> true
32
+ # @return [Errors] a new errors instance with empty internal hash
33
+ #
34
+ # @example Create new errors collection
35
+ # errors = CMDx::Errors.new
36
+ # errors.empty? # => true
132
37
  def initialize
133
38
  @errors = {}
134
39
  end
135
40
 
136
- ##
137
- # Adds an error message to the specified attribute.
138
- # Messages are stored in arrays and automatically deduplicated.
41
+ # Adds an error message to the specified attribute. Automatically handles
42
+ # array initialization and prevents duplicate messages for the same attribute.
139
43
  #
140
- # @param key [Symbol, String] the attribute name
141
- # @param value [String] the error message
142
- # @return [Array<String>] the updated array of messages for the attribute
143
- #
144
- # @example Adding multiple errors
145
- # errors.add(:email, "is required")
146
- # errors.add(:email, "is invalid format")
147
- # errors.add(:email, "is required") # Duplicate - ignored
148
- # errors[:email] #=> ["is required", "is invalid format"]
44
+ # @param key [Symbol, String] the attribute name to associate the error with
45
+ # @param value [String, Object] the error message or error object to add
46
+ #
47
+ # @return [Array] the updated array of error messages for the attribute
48
+ #
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"]
149
58
  def add(key, value)
150
59
  errors[key] ||= []
151
60
  errors[key] << value
@@ -153,18 +62,20 @@ module CMDx
153
62
  end
154
63
  alias []= add
155
64
 
156
- ##
157
65
  # Checks if a specific error message has been added to an attribute.
158
66
  #
159
- # @param key [Symbol, String] the attribute name
160
- # @param val [String] the error message to check for
161
- # @return [Boolean] true if the specific error exists
162
- #
163
- # @example
164
- # errors.add(:name, "is required")
165
- # errors.added?(:name, "is required") #=> true
166
- # errors.added?(:name, "is too long") #=> false
167
- # errors.of_kind?(:name, "is required") #=> true (alias)
67
+ # @param key [Symbol, String] the attribute name to check
68
+ # @param val [String, Object] the error message to look for
69
+ #
70
+ # @return [Boolean] true if the error exists for the attribute, false otherwise
71
+ #
72
+ # @example Check for specific error
73
+ # errors.add(:name, "can't be blank")
74
+ # errors.added?(:name, "can't be blank") # => true
75
+ # errors.added?(:name, "is invalid") # => false
76
+ #
77
+ # @example Check non-existent attribute
78
+ # errors.added?(:nonexistent, "error") # => false
168
79
  def added?(key, val)
169
80
  return false unless key?(key)
170
81
 
@@ -172,20 +83,20 @@ module CMDx
172
83
  end
173
84
  alias of_kind? added?
174
85
 
175
- ##
176
- # Iterates over each error, yielding the attribute and message.
177
- # Flattens the error structure so each message is yielded individually.
86
+ # Iterates over each error, yielding the attribute name and error message.
178
87
  #
179
- # @yieldparam key [Symbol] the attribute name
180
- # @yieldparam val [String] the error message
181
- # @return [Enumerator] if no block given
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
182
91
  #
183
- # @example
184
- # errors.add(:name, "is required")
92
+ # @return [Hash] the errors hash when no block given
93
+ #
94
+ # @example Iterate over all errors
95
+ # errors.add(:name, "can't be blank")
185
96
  # errors.add(:email, "is invalid")
186
97
  # errors.each { |attr, msg| puts "#{attr}: #{msg}" }
187
98
  # # Output:
188
- # # name: is required
99
+ # # name: can't be blank
189
100
  # # email: is invalid
190
101
  def each
191
102
  errors.each_key do |key|
@@ -193,32 +104,31 @@ module CMDx
193
104
  end
194
105
  end
195
106
 
196
- ##
197
- # Generates a full error message by combining attribute name and message.
107
+ # Formats an error message by combining the attribute name and error value.
198
108
  #
199
109
  # @param key [Symbol, String] the attribute name
200
- # @param value [String] the error message
201
- # @return [String] formatted full message
110
+ # @param value [String, Object] the error message
111
+ #
112
+ # @return [String] the formatted full error message
202
113
  #
203
- # @example
204
- # errors.full_message(:email, "is required") #=> "email is required"
205
- # errors.full_message(:age, "must be positive") #=> "age must be positive"
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"
206
117
  def full_message(key, value)
207
118
  "#{key} #{value}"
208
119
  end
209
120
 
210
- ##
211
- # Returns an array of all full error messages.
212
- # Combines attribute names with their error messages.
121
+ # Returns all error messages formatted with their attribute names.
213
122
  #
214
123
  # @return [Array<String>] array of formatted error messages
215
124
  #
216
- # @example
217
- # errors.add(:email, "is required")
125
+ # @example Get all formatted messages
126
+ # errors.add(:name, "can't be blank")
218
127
  # errors.add(:email, "is invalid")
219
- # errors.add(:password, "is too short")
220
- # errors.full_messages
221
- # #=> ["email is required", "email is invalid", "password is too short"]
128
+ # errors.full_messages # => ["name can't be blank", "email is invalid"]
129
+ #
130
+ # @example Empty errors collection
131
+ # errors.full_messages # => []
222
132
  def full_messages
223
133
  errors.each_with_object([]) do |(key, arr), memo|
224
134
  arr.each { |val| memo << full_message(key, val) }
@@ -226,54 +136,80 @@ module CMDx
226
136
  end
227
137
  alias to_a full_messages
228
138
 
229
- ##
230
- # Returns full error messages for a specific attribute.
139
+ # Returns formatted error messages for a specific attribute.
231
140
  #
232
- # @param key [Symbol, String] the attribute name
233
- # @return [Array<String>] array of full messages for the attribute
141
+ # @param key [Symbol, String] the attribute name to get messages for
234
142
  #
235
- # @example
236
- # errors.add(:email, "is required")
237
- # errors.add(:email, "is invalid")
238
- # errors.full_messages_for(:email)
239
- # #=> ["email is required", "email is invalid"]
240
- # errors.full_messages_for(:missing) #=> []
143
+ # @return [Array<String>] array of formatted error messages for the attribute
144
+ #
145
+ # @example Get messages for existing attribute
146
+ # errors.add(:name, "can't be blank")
147
+ # errors.add(:name, "is too short")
148
+ # errors.full_messages_for(:name) # => ["name can't be blank", "name is too short"]
149
+ #
150
+ # @example Get messages for non-existent attribute
151
+ # errors.full_messages_for(:nonexistent) # => []
241
152
  def full_messages_for(key)
242
153
  return [] unless key?(key)
243
154
 
244
155
  errors[key].map { |val| full_message(key, val) }
245
156
  end
246
157
 
247
- ##
248
- # Checks if any errors are present.
158
+ # Checks if the errors collection contains any validation errors.
159
+ #
160
+ # @return [Boolean] true if there are any errors present, false otherwise
249
161
  #
250
- # @return [Boolean] true if errors exist
162
+ # @example Check invalid state
163
+ # errors.add(:name, "can't be blank")
164
+ # errors.invalid? # => true
251
165
  #
252
- # @example
253
- # errors = Errors.new
254
- # errors.invalid? #=> false
255
- # errors.add(:name, "is required")
256
- # errors.invalid? #=> true
166
+ # @example Check valid state
167
+ # errors.invalid? # => false
257
168
  def invalid?
258
169
  !valid?
259
170
  end
260
171
 
261
- ##
262
- # Merges another hash of errors into this collection.
263
- # Combines arrays of messages for attributes that exist in both collections.
172
+ # Transforms each error using the provided block and returns results as an array.
173
+ #
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
264
178
  #
265
- # @param hash [Hash] hash of attribute => messages to merge
266
- # @return [Hash] the updated errors hash
179
+ # @return [Array] array of transformed error values
180
+ #
181
+ # @example Transform errors to uppercase messages
182
+ # errors.add(:name, "can't be blank")
183
+ # errors.add(:email, "is invalid")
184
+ # errors.map { |attr, msg| msg.upcase } # => ["CAN'T BE BLANK", "IS INVALID"]
185
+ #
186
+ # @example Create custom error objects
187
+ # errors.map { |attr, msg| { attribute: attr, message: msg } }
188
+ # # => [{ attribute: :name, message: "can't be blank" }]
189
+ def map
190
+ errors.each_with_object([]) do |(key, _arr), memo|
191
+ memo.concat(errors[key].map { |val| yield(key, val) })
192
+ end
193
+ end
194
+
195
+ # Merges another errors hash into this collection, combining arrays for duplicate keys.
267
196
  #
268
- # @example
269
- # errors1 = Errors.new
270
- # errors1.add(:email, "is required")
197
+ # @param hash [Hash] hash of errors to merge, with attribute keys and message arrays as values
271
198
  #
272
- # errors2 = { email: ["is invalid"], password: ["is too short"] }
273
- # errors1.merge!(errors2)
199
+ # @return [Hash] the updated internal errors hash
274
200
  #
275
- # errors1[:email] #=> ["is required", "is invalid"]
276
- # errors1[:password] #=> ["is too short"]
201
+ # @example Merge additional errors
202
+ # errors.add(:name, "can't be blank")
203
+ # other_errors = { email: ["is invalid"], name: ["is too short"] }
204
+ # errors.merge!(other_errors)
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"]
277
213
  def merge!(hash)
278
214
  errors.merge!(hash) do |_, arr1, arr2|
279
215
  arr3 = arr1 + arr2
@@ -282,17 +218,19 @@ module CMDx
282
218
  end
283
219
  end
284
220
 
285
- ##
286
- # Returns error messages for a specific attribute.
221
+ # Returns the raw error messages for a specific attribute without formatting.
287
222
  #
288
- # @param key [Symbol, String] the attribute name
289
- # @return [Array<String>] array of error messages for the attribute
223
+ # @param key [Symbol, String] the attribute name to get messages for
290
224
  #
291
- # @example
292
- # errors.add(:email, "is required")
293
- # errors.add(:email, "is invalid")
294
- # errors.messages_for(:email) #=> ["is required", "is invalid"]
295
- # errors[:email] #=> ["is required", "is invalid"] (alias)
225
+ # @return [Array] array of raw error messages for the attribute
226
+ #
227
+ # @example Get raw messages for existing attribute
228
+ # errors.add(:name, "can't be blank")
229
+ # errors.add(:name, "is too short")
230
+ # errors.messages_for(:name) # => ["can't be blank", "is too short"]
231
+ #
232
+ # @example Get messages for non-existent attribute
233
+ # errors.messages_for(:nonexistent) # => []
296
234
  def messages_for(key)
297
235
  return [] unless key?(key)
298
236
 
@@ -300,38 +238,37 @@ module CMDx
300
238
  end
301
239
  alias [] messages_for
302
240
 
303
- ##
304
- # Checks if any errors are present.
241
+ # Checks if the errors collection contains any validation errors.
242
+ #
243
+ # @return [Boolean] true if there are any errors present, false otherwise
305
244
  #
306
- # @return [Boolean] true if errors exist
245
+ # @example Check for errors presence
246
+ # errors.add(:name, "can't be blank")
247
+ # errors.present? # => true
307
248
  #
308
- # @example
309
- # errors = Errors.new
310
- # errors.present? #=> false
311
- # errors.add(:name, "is required")
312
- # errors.present? #=> true
249
+ # @example Check empty collection
250
+ # errors.present? # => false
313
251
  def present?
314
252
  !blank?
315
253
  end
316
254
 
317
- ##
318
- # Converts the errors collection to a hash representation.
255
+ # Converts the errors collection to a hash format, optionally with full formatted messages.
256
+ #
257
+ # @param full_messages [Boolean] whether to format messages with attribute names
319
258
  #
320
- # @param full_messages [Boolean] whether to include full formatted messages
321
259
  # @return [Hash] hash representation of errors
260
+ # @option return [Array<String>] attribute_name array of error messages (raw or formatted)
322
261
  #
323
- # @example Raw messages
324
- # errors.add(:email, "is required")
325
- # errors.to_hash #=> {:email => ["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"] }
326
266
  #
327
- # @example Full messages
328
- # errors.add(:email, "is required")
329
- # errors.to_hash(true) #=> {:email => ["email is required"]}
267
+ # @example Get formatted errors hash
268
+ # errors.to_hash(true) # => { :name => ["name can't be blank"], :email => ["email is invalid"] }
330
269
  #
331
- # @example Method aliases
332
- # errors.messages #=> same as to_hash
333
- # errors.group_by_attribute #=> same as to_hash
334
- # errors.as_json #=> same as to_hash
270
+ # @example Empty errors collection
271
+ # errors.to_hash # => {}
335
272
  def to_hash(full_messages = false)
336
273
  return errors unless full_messages
337
274