cmdx 0.1.0

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 (103) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.rspec +4 -0
  4. data/.rubocop.yml +64 -0
  5. data/.ruby-version +1 -0
  6. data/CHANGELOG.md +5 -0
  7. data/CODE_OF_CONDUCT.md +132 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +76 -0
  10. data/Rakefile +12 -0
  11. data/docs/basics/call.md +31 -0
  12. data/docs/basics/context.md +67 -0
  13. data/docs/basics/run.md +34 -0
  14. data/docs/basics/setup.md +32 -0
  15. data/docs/batch.md +53 -0
  16. data/docs/configuration.md +57 -0
  17. data/docs/example.md +82 -0
  18. data/docs/getting_started.md +47 -0
  19. data/docs/hooks.md +59 -0
  20. data/docs/interruptions/exceptions.md +29 -0
  21. data/docs/interruptions/faults.md +89 -0
  22. data/docs/interruptions/halt.md +80 -0
  23. data/docs/logging.md +102 -0
  24. data/docs/outcomes/result.md +17 -0
  25. data/docs/outcomes/states.md +31 -0
  26. data/docs/outcomes/statuses.md +33 -0
  27. data/docs/parameters/coercions.md +52 -0
  28. data/docs/parameters/defaults.md +47 -0
  29. data/docs/parameters/definitions.md +123 -0
  30. data/docs/parameters/namespacing.md +57 -0
  31. data/docs/parameters/validations.md +312 -0
  32. data/docs/tips_and_tricks.md +79 -0
  33. data/lib/cmdx/.DS_Store +0 -0
  34. data/lib/cmdx/batch.rb +43 -0
  35. data/lib/cmdx/coercions/array.rb +15 -0
  36. data/lib/cmdx/coercions/big_decimal.rb +23 -0
  37. data/lib/cmdx/coercions/boolean.rb +27 -0
  38. data/lib/cmdx/coercions/complex.rb +21 -0
  39. data/lib/cmdx/coercions/date.rb +26 -0
  40. data/lib/cmdx/coercions/date_time.rb +26 -0
  41. data/lib/cmdx/coercions/float.rb +21 -0
  42. data/lib/cmdx/coercions/hash.rb +31 -0
  43. data/lib/cmdx/coercions/integer.rb +21 -0
  44. data/lib/cmdx/coercions/rational.rb +21 -0
  45. data/lib/cmdx/coercions/string.rb +15 -0
  46. data/lib/cmdx/coercions/time.rb +26 -0
  47. data/lib/cmdx/coercions/virtual.rb +15 -0
  48. data/lib/cmdx/configuration.rb +25 -0
  49. data/lib/cmdx/context.rb +15 -0
  50. data/lib/cmdx/core_ext/hash.rb +36 -0
  51. data/lib/cmdx/core_ext/module.rb +48 -0
  52. data/lib/cmdx/core_ext/object.rb +55 -0
  53. data/lib/cmdx/error.rb +23 -0
  54. data/lib/cmdx/errors.rb +92 -0
  55. data/lib/cmdx/fault.rb +47 -0
  56. data/lib/cmdx/faults.rb +11 -0
  57. data/lib/cmdx/immutator.rb +21 -0
  58. data/lib/cmdx/lazy_struct.rb +79 -0
  59. data/lib/cmdx/log_formatters/json.rb +13 -0
  60. data/lib/cmdx/log_formatters/key_value.rb +13 -0
  61. data/lib/cmdx/log_formatters/line.rb +14 -0
  62. data/lib/cmdx/log_formatters/logstash.rb +18 -0
  63. data/lib/cmdx/log_formatters/raw.rb +13 -0
  64. data/lib/cmdx/logger.rb +16 -0
  65. data/lib/cmdx/parameter.rb +101 -0
  66. data/lib/cmdx/parameter_inspector.rb +23 -0
  67. data/lib/cmdx/parameter_serializer.rb +20 -0
  68. data/lib/cmdx/parameter_validator.rb +19 -0
  69. data/lib/cmdx/parameter_value.rb +136 -0
  70. data/lib/cmdx/parameters.rb +34 -0
  71. data/lib/cmdx/parameters_inspector.rb +13 -0
  72. data/lib/cmdx/parameters_serializer.rb +13 -0
  73. data/lib/cmdx/railtie.rb +32 -0
  74. data/lib/cmdx/result.rb +170 -0
  75. data/lib/cmdx/result_inspector.rb +31 -0
  76. data/lib/cmdx/result_logger.rb +22 -0
  77. data/lib/cmdx/result_serializer.rb +38 -0
  78. data/lib/cmdx/run.rb +33 -0
  79. data/lib/cmdx/run_inspector.rb +21 -0
  80. data/lib/cmdx/run_serializer.rb +16 -0
  81. data/lib/cmdx/task.rb +151 -0
  82. data/lib/cmdx/task_hook.rb +18 -0
  83. data/lib/cmdx/utils/datetime_formatter.rb +17 -0
  84. data/lib/cmdx/utils/method_name.rb +24 -0
  85. data/lib/cmdx/utils/runtime.rb +19 -0
  86. data/lib/cmdx/validators/custom.rb +20 -0
  87. data/lib/cmdx/validators/exclusion.rb +51 -0
  88. data/lib/cmdx/validators/format.rb +27 -0
  89. data/lib/cmdx/validators/inclusion.rb +51 -0
  90. data/lib/cmdx/validators/length.rb +114 -0
  91. data/lib/cmdx/validators/numeric.rb +114 -0
  92. data/lib/cmdx/validators/presence.rb +27 -0
  93. data/lib/cmdx/version.rb +7 -0
  94. data/lib/cmdx.rb +80 -0
  95. data/lib/generators/cmdx/batch_generator.rb +30 -0
  96. data/lib/generators/cmdx/install_generator.rb +15 -0
  97. data/lib/generators/cmdx/task_generator.rb +30 -0
  98. data/lib/generators/cmdx/templates/batch.rb.tt +7 -0
  99. data/lib/generators/cmdx/templates/install.rb +23 -0
  100. data/lib/generators/cmdx/templates/task.rb.tt +9 -0
  101. data/lib/locales/en.yml +36 -0
  102. data/lib/locales/es.yml +36 -0
  103. metadata +288 -0
@@ -0,0 +1,123 @@
1
+ ## Parameters - Definitions
2
+
3
+ Parameters provide a contract to verify that a task only executes if the arguments
4
+ of a call match.
5
+
6
+ ## Basics
7
+
8
+ Parameters are defined based on methods that can be delegated to a source object
9
+ (default `:context`) or keys on a hash. Parameters are automatically defined as
10
+ attributes within the task instance. `optional` parameters that do not respond or
11
+ missing the hash key will still delegate but return `nil` as a value.
12
+
13
+ ```ruby
14
+ class DetermineBoxSizeTask < CMDx::Task
15
+
16
+ # Must be passed as call arguments
17
+ required :material
18
+
19
+ # Returns value if passed as a call arguments, else returns nil
20
+ optional :depth
21
+
22
+ # Define multiple parameters one line
23
+ optional :width, :height
24
+
25
+ def call
26
+ material #=> "cardboard"
27
+ depth #=> nil
28
+ height #=> 12
29
+ width #=> 24
30
+ end
31
+
32
+ end
33
+
34
+ # Initializes local variables matching the parameter name
35
+ DetermineBoxSizeTask.call(material: "cardboard", height: 12, width: 24)
36
+ ```
37
+
38
+ ## Source
39
+
40
+ Parameters will be delegated to the task context by default but any delegatable
41
+ object within the task will do.
42
+
43
+ ```ruby
44
+ class UpdateUserDetailsTask < CMDx::Task
45
+
46
+ # Default (:context)
47
+ required :user
48
+
49
+ # Defined parameter
50
+ required :email, source: :user
51
+
52
+ # Proc or lambda
53
+ optional :address, source: -> { user.address }
54
+
55
+ # Symbol or string
56
+ optional :name, source: :company
57
+
58
+ def call
59
+ user #=> <User #a1b2c3d>
60
+ email #=> "bill@bigcorp.com"
61
+ address #=> "123 Maple St, Miami, Fl 33023"
62
+ name #=> "Big Corp."
63
+ end
64
+
65
+ private
66
+
67
+ def company
68
+ user.account.company
69
+ end
70
+
71
+ end
72
+
73
+ # Hash or delegatable object
74
+ user = User.new(email: "bill@bigcorp.com")
75
+ UpdateUserDetailsTask.call(user: user)
76
+ ```
77
+
78
+ ## Nesting
79
+
80
+ Nesting builds upon parameter source option. Build complex parameter blocks that
81
+ delegate to the parent parameter automatically.
82
+
83
+ ```ruby
84
+ class UpdateUserDetailsTask < CMDx::Task
85
+
86
+ required :address do
87
+ required :street1
88
+ optional :street2
89
+ end
90
+
91
+ optional :locality do
92
+ required :city, :state # Required if locality argument is passed
93
+ optional :zipcode
94
+ end
95
+
96
+ def call
97
+ address #=> { city: "Miami", state: "Fl" }
98
+ street1 #=> "123 Maple St."
99
+ street2 #=> nil
100
+
101
+ locality #=> { city: "Miami", state: "Fl" }
102
+ city #=> "Miami"
103
+ state #=> "Fl"
104
+ zipcode #=> nil
105
+ end
106
+
107
+ end
108
+
109
+ # Hash or delegatable object
110
+ address = Address.new(street1: "123 Maple St.")
111
+ locality = { city: "Miami", state: "Fl" }
112
+ UpdateUserDetailsTask.call(address: address, locality: locality)
113
+ ```
114
+
115
+ > [!NOTE]
116
+ > Optional parent parameters that have required child parameters will only have
117
+ > the child parameters be required if the parent option is a delegatable source
118
+ > or default value.
119
+
120
+ ---
121
+
122
+ - **Prev:** [Parameters](https://github.com/drexed/cmdx/blob/main/docs/parameters.md)
123
+ - **Next:** [Namespacing](https://github.com/drexed/cmdx/blob/main/docs/parameters/namespacing.md)
@@ -0,0 +1,57 @@
1
+ # Parameters - Namespacing
2
+
3
+ Parameters can have the delegated method prefixed and/or suffixed to
4
+ prevent clashing where the source object have methods with the same name.
5
+
6
+ `:prefix` and `:suffix` can be used independently or both at the same time.
7
+
8
+ ## With fixed value
9
+
10
+ ```ruby
11
+ class DetermineBoxSizeTask < CMDx::Task
12
+
13
+ required :width, prefix: :before_
14
+ required :height, suffix: "_after"
15
+
16
+ def call
17
+ before_width #=> 1
18
+ height_after #=> 2
19
+ end
20
+
21
+ end
22
+
23
+ # Call arguments match the parameter names
24
+ DetermineBoxSizeTask.call(width: 1, height: 2)
25
+ ```
26
+
27
+ ## With source name
28
+
29
+ ```ruby
30
+ class DetermineBoxSizeTask < CMDx::Task
31
+
32
+ # Default (:context) as source
33
+ optional :height, prefix: true
34
+
35
+ # Custom source
36
+ optional :width, source: :account, suffix: true
37
+
38
+ def call
39
+ context_height #=> 1
40
+ width_account #=> 2
41
+ end
42
+
43
+ end
44
+
45
+ # Call arguments match the parameter names
46
+ account = Account.new(width: 2)
47
+ DetermineBoxSizeTask.call(height: 1, account: account)
48
+ ```
49
+
50
+ > [!NOTE]
51
+ > `:prefix` or `:suffix` with a custom source and a fixed value
52
+ > will always return the fixed value without the source.
53
+
54
+ ---
55
+
56
+ - **Prev:** [Definitions](https://github.com/drexed/cmdx/blob/main/docs/parameters/definitions.md)
57
+ - **Next:** [Coercions](https://github.com/drexed/cmdx/blob/main/docs/parameters/coercions.md)
@@ -0,0 +1,312 @@
1
+ # Parameters - Validations
2
+
3
+ Parameter values can be validated using one of the built-in validators. Build custom validators
4
+ to check parameter values against your own business logic. `i18n` internalization is supported
5
+ out of the box.
6
+
7
+ Built-in validators are: [Presence](#presence), [Format](#format), [Exclusion](#exclusion),
8
+ [Inclusion](#inclusion), [Length](#length), [Numeric](#numeric), [Custom](#custom)
9
+
10
+ All validators support the following common options:
11
+
12
+ | Option | Description |
13
+ | ------------ | ----------- |
14
+ | `:allow_nil` | Skip validation if the parameter value is `nil`. |
15
+ | `:if` | Specifies a callable method, proc or string to determine if the validation should occur. |
16
+ | `:unless` | Specifies a callable method, proc, or string to determine if the validation should not occur. |
17
+ | `:message` | The error message to use for a violation. Fallback for any error key below that's not provided. |
18
+
19
+ > [!NOTE]
20
+ > Validators on `optional` parameters will only be executed if they are supplied as
21
+ > call arguments.
22
+
23
+ ## Presence
24
+
25
+ Validates that the specified parameter value is not empty. If you want to validate the
26
+ presence of a boolean field (where the real values are true and false), you will
27
+ want to use `inclusion: { in: [true, false] }`.
28
+
29
+ ```ruby
30
+ class UpdateUserDetailsTask < CMDx::Task
31
+
32
+ # Boolean
33
+ required :username, presence: true
34
+
35
+ # With custom error message
36
+ optional :email, presence: { message: "must not be empty" }
37
+
38
+ def call
39
+ # Do work...
40
+ end
41
+
42
+ end
43
+ ```
44
+
45
+ ## Format
46
+
47
+ Validates whether the specified parameter value is of the correct form,
48
+ going by the regular expression provided.
49
+
50
+ ```ruby
51
+ class UpdateUserDetailsTask < CMDx::Task
52
+
53
+ # With
54
+ required :username, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i }
55
+
56
+ # Without (with proc if conditional)
57
+ optional :email, format: { without: /NOSPAM/, if: proc { Current.account.spam_check? } }
58
+
59
+ def call
60
+ # Do work...
61
+ end
62
+
63
+ end
64
+ ```
65
+
66
+ Constraint options:
67
+
68
+ | Option | Description |
69
+ | ---------- | ----------- |
70
+ | `:with` | Regular expression that if the parameter value matches will result in a successful validation. |
71
+ | `:without` | Regular expression that if the parameter value does not match will result in a successful validation. |
72
+
73
+ ## Exclusion
74
+
75
+ Validates that the specified parameter is not in a particular enumerable object.
76
+
77
+ ```ruby
78
+ class DetermineBoxSizeTask < CMDx::Task
79
+
80
+ # Array
81
+ required :width, exclusion: { in: [12, 24, 36] }
82
+
83
+ # Range (with allow nil)
84
+ optional :length, exclusion: { in: 12..36, allow_nil: true }
85
+
86
+ def call
87
+ # Do work...
88
+ end
89
+
90
+ end
91
+ ```
92
+
93
+ Constraint options:
94
+
95
+ | Option | Description |
96
+ | ------------ | ----------- |
97
+ | `:in` | An enumerable object of unavailable items such as an array or range. |
98
+ | `:within` | A synonym (or alias) for `:in` |
99
+
100
+ Other options:
101
+
102
+ | Option | Description |
103
+ | ----------------- | ----------- |
104
+ | `:of_message` | The error message if the parameter value is in array. (default: "must not be one of: %{values}") |
105
+ | `:in_message` | The error message if the parameter value is in range. (default: "must not be within %{min} and %{max}") |
106
+ | `:within_message` | A synonym (or alias) for `:in_message` |
107
+
108
+ ## Inclusion
109
+
110
+ Validates that the specified parameter value is in a particular enumerable object.
111
+
112
+ ```ruby
113
+ class DetermineBoxSizeTask < CMDx::Task
114
+
115
+ # Array
116
+ required :width, inclusion: { in: [12, 24, 36] }
117
+
118
+ # Range (with custom error message)
119
+ optional :length, inclusion: { in: 12..36, unless: :length_check? }
120
+
121
+ def call
122
+ # Do work...
123
+ end
124
+
125
+ private
126
+
127
+ def skip_length_check?
128
+ false
129
+ end
130
+
131
+ end
132
+ ```
133
+
134
+ Constraint options:
135
+
136
+ | Option | Description |
137
+ | ------------ | ----------- |
138
+ | `:in` | An enumerable object of available items such as an array or range. |
139
+ | `:within` | A synonym (or alias) for `:in` |
140
+
141
+ Other options:
142
+
143
+ | Option | Description |
144
+ | ----------------- | ----------- |
145
+ | `:of_message` | The error message if the parameter value is not in array. (default: "must be one of: %{values}") |
146
+ | `:in_message` | The error message if the parameter value is not in range. (default: "must be within %{min} and %{max}") |
147
+ | `:within_message` | A synonym (or alias) for `:in_message` |
148
+
149
+ ## Length
150
+
151
+ Validates that the specified parameter value matches the length restrictions supplied.
152
+ Only one constraint option can be used at a time apart from `:min` and `:max`
153
+ that can be combined together:
154
+
155
+ ```ruby
156
+ class UpdateUserDetailsTask < CMDx::Task
157
+
158
+ # Range (with custom error message)
159
+ required :email, length: { within: 12..36, message: "must be within range" }
160
+ required :username, length: { not_within: 48..96 }
161
+
162
+ # Boundary
163
+ optional :first_name, length: { min: 24 }
164
+ optional :middle_name, length: { max: 48 }
165
+ optional :last_name, length: { min: 24, max: 48 }
166
+
167
+ # Exact
168
+ required :title, length: { is: 24 }
169
+ required :count, length: { is_not: 48 }
170
+
171
+ def call
172
+ # Do work...
173
+ end
174
+
175
+ end
176
+ ```
177
+
178
+ Constraint options:
179
+
180
+ | Option | Description |
181
+ | ------------- | ----------- |
182
+ | `:within` | A range specifying the minimum and maximum size of the parameter value. |
183
+ | `:not_within` | A range specifying the minimum and maximum size of the parameter value it's not to be. |
184
+ | `:in` | A synonym (or alias) for `:within` |
185
+ | `:not_in` | A synonym (or alias) for `:not_within` |
186
+ | `:min` | The minimum size of the parameter value. |
187
+ | `:max` | The maximum size of the parameter value. |
188
+ | `:is` | The exact size of the parameter value. |
189
+ | `:is_not` | The exact size of the parameter value it's not to be. |
190
+
191
+ Other options:
192
+
193
+ | Option | Description |
194
+ | --------------------- | ----------- |
195
+ | `:within_message` | The error message if the parameter value is within the value range. (default: "length must not be within %{min} and %{max}") |
196
+ | `:not_within_message` | The error message if the parameter value is not within the value range. (default: "length must be within %{min} and %{max}") |
197
+ | `:in_message` | A synonym (or alias) for `:within_message` |
198
+ | `:not_in_message` | A synonym (or alias) for `:not_within_message` |
199
+ | `:min_message` | The error message if the parameter value is below the min value. (default: "length must be at least %{min}") |
200
+ | `:max_message` | The error message if the parameter value is above the min value. (default: "length must be at most %{max}") |
201
+ | `:is_message` | The error message if the parameter value is the exact value. (default: "length must be %{is}") |
202
+ | `:is_not_message` | The error message if the parameter value is not the exact value. (default: "length must not be %{is_not}") |
203
+
204
+ ## Numeric
205
+
206
+ Validates that the specified parameter value matches the numeric restrictions supplied.
207
+ Only one constraint option can be used at a time apart from `:min` and `:max`
208
+ that can be combined together:
209
+
210
+ ```ruby
211
+ class UpdateUserDetailsTask < CMDx::Task
212
+
213
+ # Range (with custom error message)
214
+ required :height, numeric: { within: 36..196 }
215
+ required :weight, numeric: { not_within: 0..5 }
216
+
217
+ # Boundary
218
+ optional :dob_year, numeric: { min: 1900 }
219
+ optional :dob_day, numeric: { max: 31 }
220
+ optional :dob_month, numeric: { min: 1, max: 12 }
221
+
222
+ # Exact
223
+ required :age, numeric: { is: 18 }
224
+ required :parents, numeric: { is_not: 0 }
225
+
226
+ def call
227
+ # Do work...
228
+ end
229
+
230
+ end
231
+ ```
232
+
233
+ Constraint options:
234
+
235
+ | Option | Description |
236
+ | ------------- | ----------- |
237
+ | `:within` | A range specifying the minimum and maximum size of the parameter value. |
238
+ | `:not_within` | A range specifying the minimum and maximum size of the parameter value it's not to be. |
239
+ | `:in` | A synonym (or alias) for `:within` |
240
+ | `:not_in` | A synonym (or alias) for `:not_within` |
241
+ | `:min` | The minimum size of the parameter value. |
242
+ | `:max` | The maximum size of the parameter value. |
243
+ | `:is` | The exact size of the parameter value. |
244
+ | `:is_not` | The exact size of the parameter value it's not to be. |
245
+
246
+ Other options:
247
+
248
+ | Option | Description |
249
+ | --------------------- | ----------- |
250
+ | `:within_message` | The error message if the parameter value is within the value range. (default: "must not be within %{min} and %{max}") |
251
+ | `:not_within_message` | The error message if the parameter value is not within the value range. (default: "must be within %{min} and %{max}") |
252
+ | `:in_message` | A synonym (or alias) for `:within_message` |
253
+ | `:not_in_message` | A synonym (or alias) for `:not_within_message` |
254
+ | `:min_message` | The error message if the parameter value is below the min value. (default: "must be at least %{min}") |
255
+ | `:max_message` | The error message if the parameter value is above the min value. (default: "must be at most %{max}") |
256
+ | `:is_message` | The error message if the parameter value is the exact value. (default: "must be %{is}") |
257
+ | `:is_not_message` | The error message if the parameter value is not the exact value. (default: "must not be %{is_not}") |
258
+
259
+ ## Custom
260
+
261
+ Validate the specified parameter value using custom validators.
262
+
263
+ ```ruby
264
+ class TLDValidator
265
+ def self.call(value, options)
266
+ tld = options.dig(:custom, :tld) || %w[com]
267
+ value.ends_with?(tld)
268
+ end
269
+ end
270
+
271
+ class UpdateUserDetailsTask < CMDx::Task
272
+
273
+ # Basic
274
+ required :unconfirmed_email, custom: { validator: TLDValidator }
275
+
276
+ # Passable options (with custom error message)
277
+ optional :confirmed_email, custom: { validator: TLDValidator, tld: %w[com net org], message: "is not valid" }
278
+
279
+ def call
280
+ # Do work...
281
+ end
282
+
283
+ end
284
+ ```
285
+
286
+ Constraint options:
287
+
288
+ | Option | Description |
289
+ | ------------ | ----------- |
290
+ | `:validator` | Callable class that returns true or false. |
291
+
292
+ ## Results
293
+
294
+ The following represents a result output example of a failed validation.
295
+
296
+ ```ruby
297
+ result = DetermineBoxSizeTask.call
298
+ result.state #=> "interrupted"
299
+ result.status #=> "failed"
300
+ result.metadata #=> {
301
+ #=> reason: "email format is not valid. username cannot be empty.",
302
+ #=> messages: {
303
+ #=> email: ["format is not valid"],
304
+ #=> username: ["cannot be empty"]
305
+ #=> }
306
+ #=> }
307
+ ```
308
+
309
+ ---
310
+
311
+ - **Prev:** [Coercions](https://github.com/drexed/cmdx/blob/main/docs/parameters/coercions.md)
312
+ - **Next:** [Defaults](https://github.com/drexed/cmdx/blob/main/docs/parameters/defaults.md)
@@ -0,0 +1,79 @@
1
+ # Tips & Tricks
2
+
3
+ ## Configuration
4
+
5
+ Configure `CMDx` to get the most out of your Rails application.
6
+
7
+ ```ruby
8
+ CMDx.configure do |config|
9
+ # Redirect your logs through the app defined logger:
10
+ config.logger = Rails.logger
11
+
12
+ # Adjust the log level to write depending on the environment:
13
+ config.logger.level = Rails.env.development? ? Logger::DEBUG : Logger::INFO
14
+
15
+ # Structure log lines using a pre-built or custom formatter:
16
+ config.logger.formatter = CMDx::LogFormatters::Logstash.new
17
+ end
18
+ ```
19
+
20
+ ## Setup
21
+
22
+ While not required, a common setup involves creating an `app/cmds` directory
23
+ to place all of your tasks and batches under, eg:
24
+
25
+ ```txt
26
+ /app
27
+ /cmds
28
+ /notifications
29
+ - deliver_email_task.rb
30
+ - post_slack_message_task.rb
31
+ - send_carrier_pigeon_task.rb
32
+ - batch_deliver_all.rb
33
+ - process_order_task.rb
34
+ - application_batch.rb
35
+ - application_task.rb
36
+ ```
37
+
38
+ > [!TIP]
39
+ > Prefix batches with `batch_` and suffix tasks with `_task` to they convey their function.
40
+ > Use a verb+noun naming structure to convey the work that will be performed, eg:
41
+ > `BatchDeliverNotifications` or `DeliverEmailTask`
42
+
43
+ ## Parameters
44
+
45
+ Use the Rails `with_options` as an elegant way to factor duplication
46
+ out of options passed to a series of parameter definitions. The following
47
+ are a few common example:
48
+
49
+ ```ruby
50
+ class UpdateUserDetailsTask < CMDx::Task
51
+
52
+ # Apply `type: :string, presence: true` to this set of parameters:
53
+ with_options(type: :string, presence: true) do
54
+ required :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i }
55
+ optional :first_name, :last_name
56
+ end
57
+
58
+ required :address do
59
+ # Apply the `address_*` prefix to this set of nested parameters:
60
+ with_options(prefix: :address_) do
61
+ required :city, :country
62
+ optional :state
63
+ end
64
+ end
65
+
66
+ def call
67
+ # Do work
68
+ end
69
+
70
+ end
71
+ ```
72
+
73
+ [Learn More](https://api.rubyonrails.org/classes/Object.html#method-i-with_options)
74
+ about its usages on the official Rails docs.
75
+
76
+ ---
77
+
78
+ - **Prev:** [Logging](https://github.com/drexed/cmdx/blob/main/docs/logging.md)
79
+ - **Next:** [Example](https://github.com/drexed/cmdx/blob/main/docs/example.md)
Binary file
data/lib/cmdx/batch.rb ADDED
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ class Batch < Task
5
+
6
+ Group = Struct.new(:tasks, :options)
7
+
8
+ class << self
9
+
10
+ def batch_groups
11
+ @batch_groups ||= []
12
+ end
13
+
14
+ def process(*tasks, **options)
15
+ batch_groups << Group.new(
16
+ tasks.flatten.map do |task|
17
+ next task if task <= Task
18
+
19
+ raise ArgumentError, "must be a Batch or Task"
20
+ end,
21
+ options
22
+ )
23
+ end
24
+
25
+ end
26
+
27
+ def call
28
+ self.class.batch_groups.each do |group|
29
+ next unless __cmdx_eval(group.options)
30
+
31
+ batch_halt = group.options[:batch_halt] || task_setting(:batch_halt)
32
+
33
+ group.tasks.each do |task|
34
+ task_result = task.call(context)
35
+ next unless Array(batch_halt).include?(task_result.status)
36
+
37
+ throw!(task_result)
38
+ end
39
+ end
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module Coercions
5
+ module Array
6
+
7
+ module_function
8
+
9
+ def call(value, _options = {})
10
+ Array(value)
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module Coercions
5
+ module BigDecimal
6
+
7
+ DEFAULT_PRECISION = 14
8
+
9
+ module_function
10
+
11
+ def call(value, options = {})
12
+ BigDecimal(value, options[:precision] || DEFAULT_PRECISION)
13
+ rescue ArgumentError, TypeError
14
+ raise CoercionError, I18n.t(
15
+ "cmdx.coercions.into_a",
16
+ type: "big decimal",
17
+ default: "could not coerce into a big decimal"
18
+ )
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module Coercions
5
+ module Boolean
6
+
7
+ FALSEY = /^(false|f|no|n|0)$/i
8
+ TRUTHY = /^(true|t|yes|y|1)$/i
9
+
10
+ module_function
11
+
12
+ def call(value, _options = {})
13
+ case value.to_s
14
+ when FALSEY then false
15
+ when TRUTHY then true
16
+ else
17
+ raise CoercionError, I18n.t(
18
+ "cmdx.coercions.into_a",
19
+ type: "boolean",
20
+ default: "could not coerce into a boolean"
21
+ )
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module Coercions
5
+ module Complex
6
+
7
+ module_function
8
+
9
+ def call(value, _options = {})
10
+ Complex(value)
11
+ rescue ArgumentError, TypeError
12
+ raise CoercionError, I18n.t(
13
+ "cmdx.coercions.into_a",
14
+ type: "complex",
15
+ default: "could not coerce into a complex"
16
+ )
17
+ end
18
+
19
+ end
20
+ end
21
+ end