cmdx 1.13.0 → 1.15.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +92 -76
- data/LICENSE.txt +3 -20
- data/README.md +16 -8
- data/lib/cmdx/attribute.rb +42 -5
- data/lib/cmdx/context.rb +16 -0
- data/lib/cmdx/executor.rb +9 -9
- data/lib/cmdx/result.rb +27 -7
- data/lib/cmdx/task.rb +19 -0
- data/lib/cmdx/validator_registry.rb +1 -0
- data/lib/cmdx/validators/absence.rb +61 -0
- data/lib/cmdx/version.rb +1 -1
- data/lib/locales/af.yml +1 -0
- data/lib/locales/ar.yml +1 -0
- data/lib/locales/az.yml +1 -0
- data/lib/locales/be.yml +1 -0
- data/lib/locales/bg.yml +1 -0
- data/lib/locales/bn.yml +1 -0
- data/lib/locales/bs.yml +1 -0
- data/lib/locales/ca.yml +1 -0
- data/lib/locales/cnr.yml +1 -0
- data/lib/locales/cs.yml +1 -0
- data/lib/locales/cy.yml +1 -0
- data/lib/locales/da.yml +1 -0
- data/lib/locales/de.yml +1 -0
- data/lib/locales/dz.yml +1 -0
- data/lib/locales/el.yml +1 -0
- data/lib/locales/en.yml +1 -0
- data/lib/locales/eo.yml +1 -0
- data/lib/locales/es.yml +1 -0
- data/lib/locales/et.yml +1 -0
- data/lib/locales/eu.yml +1 -0
- data/lib/locales/fa.yml +1 -0
- data/lib/locales/fi.yml +1 -0
- data/lib/locales/fr.yml +1 -0
- data/lib/locales/fy.yml +1 -0
- data/lib/locales/gd.yml +1 -0
- data/lib/locales/gl.yml +1 -0
- data/lib/locales/he.yml +1 -0
- data/lib/locales/hi.yml +1 -0
- data/lib/locales/hr.yml +1 -0
- data/lib/locales/hu.yml +1 -0
- data/lib/locales/hy.yml +1 -0
- data/lib/locales/id.yml +1 -0
- data/lib/locales/is.yml +1 -0
- data/lib/locales/it.yml +1 -0
- data/lib/locales/ja.yml +1 -0
- data/lib/locales/ka.yml +1 -0
- data/lib/locales/kk.yml +1 -0
- data/lib/locales/km.yml +1 -0
- data/lib/locales/kn.yml +1 -0
- data/lib/locales/ko.yml +1 -0
- data/lib/locales/lb.yml +1 -0
- data/lib/locales/lo.yml +1 -0
- data/lib/locales/lt.yml +1 -0
- data/lib/locales/lv.yml +1 -0
- data/lib/locales/mg.yml +1 -0
- data/lib/locales/mk.yml +1 -0
- data/lib/locales/ml.yml +1 -0
- data/lib/locales/mn.yml +1 -0
- data/lib/locales/mr-IN.yml +1 -0
- data/lib/locales/ms.yml +1 -0
- data/lib/locales/nb.yml +1 -0
- data/lib/locales/ne.yml +1 -0
- data/lib/locales/nl.yml +1 -0
- data/lib/locales/nn.yml +1 -0
- data/lib/locales/oc.yml +1 -0
- data/lib/locales/or.yml +1 -0
- data/lib/locales/pa.yml +1 -0
- data/lib/locales/pl.yml +1 -0
- data/lib/locales/pt.yml +1 -0
- data/lib/locales/rm.yml +1 -0
- data/lib/locales/ro.yml +1 -0
- data/lib/locales/ru.yml +1 -0
- data/lib/locales/sc.yml +1 -0
- data/lib/locales/sk.yml +1 -0
- data/lib/locales/sl.yml +1 -0
- data/lib/locales/sq.yml +1 -0
- data/lib/locales/sr.yml +1 -0
- data/lib/locales/st.yml +1 -0
- data/lib/locales/sv.yml +1 -0
- data/lib/locales/sw.yml +1 -0
- data/lib/locales/ta.yml +1 -0
- data/lib/locales/te.yml +1 -0
- data/lib/locales/th.yml +1 -0
- data/lib/locales/tl.yml +1 -0
- data/lib/locales/tr.yml +1 -0
- data/lib/locales/tt.yml +1 -0
- data/lib/locales/ug.yml +1 -0
- data/lib/locales/uk.yml +1 -0
- data/lib/locales/ur.yml +1 -0
- data/lib/locales/uz.yml +1 -0
- data/lib/locales/vi.yml +1 -0
- data/lib/locales/wo.yml +1 -0
- data/lib/locales/zh-CN.yml +1 -0
- data/lib/locales/zh-HK.yml +1 -0
- data/lib/locales/zh-TW.yml +1 -0
- data/lib/locales/zh-YUE.yml +1 -0
- data/mkdocs.yml +65 -36
- metadata +4 -57
- data/.cursor/prompts/docs.md +0 -12
- data/.cursor/prompts/llms.md +0 -8
- data/.cursor/prompts/rspec.md +0 -24
- data/.cursor/prompts/yardoc.md +0 -15
- data/.cursor/rules/cursor-instructions.mdc +0 -68
- data/.irbrc +0 -18
- data/.rspec +0 -4
- data/.rubocop.yml +0 -95
- data/.ruby-version +0 -1
- data/.yard-lint.yml +0 -174
- data/.yardopts +0 -7
- data/docs/.DS_Store +0 -0
- data/docs/assets/favicon.ico +0 -0
- data/docs/assets/favicon.svg +0 -1
- data/docs/attributes/coercions.md +0 -155
- data/docs/attributes/defaults.md +0 -77
- data/docs/attributes/definitions.md +0 -283
- data/docs/attributes/naming.md +0 -68
- data/docs/attributes/transformations.md +0 -63
- data/docs/attributes/validations.md +0 -336
- data/docs/basics/chain.md +0 -108
- data/docs/basics/context.md +0 -121
- data/docs/basics/execution.md +0 -152
- data/docs/basics/setup.md +0 -107
- data/docs/callbacks.md +0 -157
- data/docs/configuration.md +0 -314
- data/docs/deprecation.md +0 -143
- data/docs/getting_started.md +0 -137
- data/docs/index.md +0 -134
- data/docs/internationalization.md +0 -126
- data/docs/interruptions/exceptions.md +0 -52
- data/docs/interruptions/faults.md +0 -169
- data/docs/interruptions/halt.md +0 -216
- data/docs/logging.md +0 -90
- data/docs/middlewares.md +0 -191
- data/docs/outcomes/result.md +0 -197
- data/docs/outcomes/states.md +0 -66
- data/docs/outcomes/statuses.md +0 -65
- data/docs/retries.md +0 -121
- data/docs/stylesheets/extra.css +0 -42
- data/docs/tips_and_tricks.md +0 -157
- data/docs/workflows.md +0 -226
- data/examples/active_record_database_transaction.md +0 -27
- data/examples/active_record_query_tagging.md +0 -46
- data/examples/flipper_feature_flags.md +0 -50
- data/examples/paper_trail_whatdunnit.md +0 -39
- data/examples/redis_idempotency.md +0 -71
- data/examples/sentry_error_tracking.md +0 -46
- data/examples/sidekiq_async_execution.md +0 -29
- data/examples/stoplight_circuit_breaker.md +0 -36
- data/src/cmdx-dark-logo.png +0 -0
- data/src/cmdx-favicon.svg +0 -1
- data/src/cmdx-light-logo.png +0 -0
- data/src/cmdx-logo.svg +0 -1
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
# Attributes - Definitions
|
|
2
|
-
|
|
3
|
-
Attributes define your task's interface with automatic validation, type coercion, and accessor generation. They're the contract between callers and your business logic.
|
|
4
|
-
|
|
5
|
-
## Declarations
|
|
6
|
-
|
|
7
|
-
!!! warning "Important"
|
|
8
|
-
|
|
9
|
-
Attributes are order-dependent, so if you need to reference them as a source or use them in conditions, make sure they’re defined in the correct order.
|
|
10
|
-
|
|
11
|
-
!!! tip
|
|
12
|
-
|
|
13
|
-
Prefer using the `required` and `optional` alias for `attributes` for brevity and to clearly signal intent.
|
|
14
|
-
|
|
15
|
-
### Optional
|
|
16
|
-
|
|
17
|
-
Optional attributes return `nil` when not provided.
|
|
18
|
-
|
|
19
|
-
```ruby
|
|
20
|
-
class ScheduleEvent < CMDx::Task
|
|
21
|
-
attribute :title
|
|
22
|
-
attributes :duration, :location
|
|
23
|
-
|
|
24
|
-
# Alias for attributes (preferred)
|
|
25
|
-
optional :description
|
|
26
|
-
optional :visibility, :attendees
|
|
27
|
-
|
|
28
|
-
def work
|
|
29
|
-
title #=> "Team Standup"
|
|
30
|
-
duration #=> 30
|
|
31
|
-
location #=> nil
|
|
32
|
-
description #=> nil
|
|
33
|
-
visibility #=> nil
|
|
34
|
-
attendees #=> ["alice@company.com", "bob@company.com"]
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Attributes passed as keyword arguments
|
|
39
|
-
ScheduleEvent.execute(
|
|
40
|
-
title: "Team Standup",
|
|
41
|
-
duration: 30,
|
|
42
|
-
attendees: ["alice@company.com", "bob@company.com"]
|
|
43
|
-
)
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### Required
|
|
47
|
-
|
|
48
|
-
Required attributes must be provided in call arguments or task execution will fail.
|
|
49
|
-
|
|
50
|
-
```ruby
|
|
51
|
-
class PublishArticle < CMDx::Task
|
|
52
|
-
attribute :title, required: true
|
|
53
|
-
attributes :content, :author_id, required: true
|
|
54
|
-
|
|
55
|
-
# Alias for attributes => required: true (preferred)
|
|
56
|
-
required :category
|
|
57
|
-
required :status, :tags
|
|
58
|
-
|
|
59
|
-
# Conditionally required
|
|
60
|
-
required :publisher, if: :magazine?
|
|
61
|
-
attribute :approver, required: true, unless: proc { status == :published }
|
|
62
|
-
|
|
63
|
-
def work
|
|
64
|
-
title #=> "Getting Started with Ruby"
|
|
65
|
-
content #=> "This is a comprehensive guide..."
|
|
66
|
-
author_id #=> 42
|
|
67
|
-
category #=> "programming"
|
|
68
|
-
status #=> :published
|
|
69
|
-
tags #=> ["ruby", "beginner"]
|
|
70
|
-
publisher #=> "Eastbay"
|
|
71
|
-
approver #=> #<Editor ...>
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
private
|
|
75
|
-
|
|
76
|
-
def magazine?
|
|
77
|
-
context.title.ends_with?("[M]")
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
!!! note
|
|
83
|
-
|
|
84
|
-
When a required attribute's condition evaluates to `false`, the attribute behaves as optional. All other attribute features such as coercions, validations, defaults, and transformations still apply normally.
|
|
85
|
-
|
|
86
|
-
## Sources
|
|
87
|
-
|
|
88
|
-
Attributes read from any accessible object—not just context. Use sources to pull data from models, services, or any callable:
|
|
89
|
-
|
|
90
|
-
### Context
|
|
91
|
-
|
|
92
|
-
```ruby
|
|
93
|
-
class BackupDatabase < CMDx::Task
|
|
94
|
-
# Default source is :context
|
|
95
|
-
required :database_name
|
|
96
|
-
optional :compression_level
|
|
97
|
-
|
|
98
|
-
# Explicitly specify context source
|
|
99
|
-
attribute :backup_path, source: :context
|
|
100
|
-
|
|
101
|
-
def work
|
|
102
|
-
database_name #=> context.database_name
|
|
103
|
-
backup_path #=> context.backup_path
|
|
104
|
-
compression_level #=> context.compression_level
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### Symbol References
|
|
110
|
-
|
|
111
|
-
Reference instance methods by symbol for dynamic source values:
|
|
112
|
-
|
|
113
|
-
```ruby
|
|
114
|
-
class BackupDatabase < CMDx::Task
|
|
115
|
-
attributes :host, :credentials, source: :database_config
|
|
116
|
-
|
|
117
|
-
# Access from declared attributes
|
|
118
|
-
attribute :connection_string, source: :credentials
|
|
119
|
-
|
|
120
|
-
def work
|
|
121
|
-
# Your logic here...
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
private
|
|
125
|
-
|
|
126
|
-
def database_config
|
|
127
|
-
@database_config ||= DatabaseConfig.find(context.database_name)
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
### Proc or Lambda
|
|
133
|
-
|
|
134
|
-
Use anonymous functions for dynamic source values:
|
|
135
|
-
|
|
136
|
-
```ruby
|
|
137
|
-
class BackupDatabase < CMDx::Task
|
|
138
|
-
# Proc
|
|
139
|
-
attribute :timestamp, source: proc { Time.current }
|
|
140
|
-
|
|
141
|
-
# Lambda
|
|
142
|
-
attribute :server, source: -> { Current.server }
|
|
143
|
-
end
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### Class or Module
|
|
147
|
-
|
|
148
|
-
For complex source logic, use classes or modules:
|
|
149
|
-
|
|
150
|
-
```ruby
|
|
151
|
-
class DatabaseResolver
|
|
152
|
-
def self.call(task)
|
|
153
|
-
Database.find(task.context.database_name)
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
class BackupDatabase < CMDx::Task
|
|
158
|
-
# Class or Module
|
|
159
|
-
attribute :schema, source: DatabaseResolver
|
|
160
|
-
|
|
161
|
-
# Instance
|
|
162
|
-
attribute :metadata, source: DatabaseResolver.new
|
|
163
|
-
end
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
## Nesting
|
|
167
|
-
|
|
168
|
-
Build complex structures with nested attributes. Children inherit their parent as source and support all attribute options:
|
|
169
|
-
|
|
170
|
-
!!! note
|
|
171
|
-
|
|
172
|
-
Nested attributes support all features: naming, coercions, validations, defaults, and more.
|
|
173
|
-
|
|
174
|
-
```ruby
|
|
175
|
-
class ConfigureServer < CMDx::Task
|
|
176
|
-
# Required parent with required children
|
|
177
|
-
required :network_config do
|
|
178
|
-
required :hostname, :port, :protocol, :subnet
|
|
179
|
-
optional :load_balancer
|
|
180
|
-
attribute :firewall_rules
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
# Optional parent with conditional children
|
|
184
|
-
optional :ssl_config do
|
|
185
|
-
required :certificate_path, :private_key # Only required if ssl_config provided
|
|
186
|
-
optional :enable_http2, prefix: true
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
# Multi-level nesting
|
|
190
|
-
attribute :monitoring do
|
|
191
|
-
required :provider
|
|
192
|
-
|
|
193
|
-
optional :alerting do
|
|
194
|
-
required :threshold_percentage
|
|
195
|
-
optional :notification_channel
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
def work
|
|
200
|
-
network_config #=> { hostname: "api.company.com" ... }
|
|
201
|
-
hostname #=> "api.company.com"
|
|
202
|
-
load_balancer #=> nil
|
|
203
|
-
end
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
ConfigureServer.execute(
|
|
207
|
-
server_id: "srv-001",
|
|
208
|
-
network_config: {
|
|
209
|
-
hostname: "api.company.com",
|
|
210
|
-
port: 443,
|
|
211
|
-
protocol: "https",
|
|
212
|
-
subnet: "10.0.1.0/24",
|
|
213
|
-
firewall_rules: "allow_web_traffic"
|
|
214
|
-
},
|
|
215
|
-
monitoring: {
|
|
216
|
-
provider: "datadog",
|
|
217
|
-
alerting: {
|
|
218
|
-
threshold_percentage: 85.0,
|
|
219
|
-
notification_channel: "slack"
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
)
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
!!! warning "Important"
|
|
226
|
-
|
|
227
|
-
Child requirements only apply when the parent is provided—perfect for optional structures.
|
|
228
|
-
|
|
229
|
-
## Error Handling
|
|
230
|
-
|
|
231
|
-
Validation failures provide detailed, structured error messages:
|
|
232
|
-
|
|
233
|
-
!!! note
|
|
234
|
-
|
|
235
|
-
Nested attributes are only validated when their parent is present and valid.
|
|
236
|
-
|
|
237
|
-
```ruby
|
|
238
|
-
class ConfigureServer < CMDx::Task
|
|
239
|
-
required :server_id, :environment
|
|
240
|
-
required :network_config do
|
|
241
|
-
required :hostname, :port
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
def work
|
|
245
|
-
# Your logic here...
|
|
246
|
-
end
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
# Missing required top-level attributes
|
|
250
|
-
result = ConfigureServer.execute(server_id: "srv-001")
|
|
251
|
-
|
|
252
|
-
result.state #=> "interrupted"
|
|
253
|
-
result.status #=> "failed"
|
|
254
|
-
result.reason #=> "Invalid"
|
|
255
|
-
result.metadata #=> {
|
|
256
|
-
# errors: {
|
|
257
|
-
# full_message: "environment is required. network_config is required.",
|
|
258
|
-
# messages: {
|
|
259
|
-
# environment: ["is required"],
|
|
260
|
-
# network_config: ["is required"]
|
|
261
|
-
# }
|
|
262
|
-
# }
|
|
263
|
-
# }
|
|
264
|
-
|
|
265
|
-
# Missing required nested attributes
|
|
266
|
-
result = ConfigureServer.execute(
|
|
267
|
-
server_id: "srv-001",
|
|
268
|
-
environment: "production",
|
|
269
|
-
network_config: { hostname: "api.company.com" } # Missing port
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
result.state #=> "interrupted"
|
|
273
|
-
result.status #=> "failed"
|
|
274
|
-
result.reason #=> "Invalid"
|
|
275
|
-
result.metadata #=> {
|
|
276
|
-
# errors: {
|
|
277
|
-
# full_message: "port is required.",
|
|
278
|
-
# messages: {
|
|
279
|
-
# port: ["is required"]
|
|
280
|
-
# }
|
|
281
|
-
# }
|
|
282
|
-
# }
|
|
283
|
-
```
|
data/docs/attributes/naming.md
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# Attributes - Naming
|
|
2
|
-
|
|
3
|
-
Customize accessor method names to avoid conflicts and improve clarity. Affixing changes only the generated methods—not the original attribute names.
|
|
4
|
-
|
|
5
|
-
!!! note
|
|
6
|
-
|
|
7
|
-
Use naming when attributes conflict with existing methods or need better clarity in your code.
|
|
8
|
-
|
|
9
|
-
## Prefix
|
|
10
|
-
|
|
11
|
-
Adds a prefix to the generated accessor method name.
|
|
12
|
-
|
|
13
|
-
```ruby
|
|
14
|
-
class GenerateReport < CMDx::Task
|
|
15
|
-
# Dynamic from attribute source
|
|
16
|
-
attribute :template, prefix: true
|
|
17
|
-
|
|
18
|
-
# Static
|
|
19
|
-
attribute :format, prefix: "report_"
|
|
20
|
-
|
|
21
|
-
def work
|
|
22
|
-
context_template #=> "monthly_sales"
|
|
23
|
-
report_format #=> "pdf"
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Attributes passed as original attribute names
|
|
28
|
-
GenerateReport.execute(template: "monthly_sales", format: "pdf")
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Suffix
|
|
32
|
-
|
|
33
|
-
Adds a suffix to the generated accessor method name.
|
|
34
|
-
|
|
35
|
-
```ruby
|
|
36
|
-
class DeployApplication < CMDx::Task
|
|
37
|
-
# Dynamic from attribute source
|
|
38
|
-
attribute :branch, suffix: true
|
|
39
|
-
|
|
40
|
-
# Static
|
|
41
|
-
attribute :version, suffix: "_tag"
|
|
42
|
-
|
|
43
|
-
def work
|
|
44
|
-
branch_context #=> "main"
|
|
45
|
-
version_tag #=> "v1.2.3"
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Attributes passed as original attribute names
|
|
50
|
-
DeployApplication.execute(branch: "main", version: "v1.2.3")
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## As
|
|
54
|
-
|
|
55
|
-
Completely renames the generated accessor method.
|
|
56
|
-
|
|
57
|
-
```ruby
|
|
58
|
-
class ScheduleMaintenance < CMDx::Task
|
|
59
|
-
attribute :scheduled_at, as: :when
|
|
60
|
-
|
|
61
|
-
def work
|
|
62
|
-
when #=> <DateTime>
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# Attributes passed as original attribute names
|
|
67
|
-
ScheduleMaintenance.execute(scheduled_at: DateTime.new(2024, 12, 15, 2, 0, 0))
|
|
68
|
-
```
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
# Attributes - Transformations
|
|
2
|
-
|
|
3
|
-
Modify attribute values after coercion but before validation. Perfect for normalization, formatting, and data cleanup.
|
|
4
|
-
|
|
5
|
-
## Declarations
|
|
6
|
-
|
|
7
|
-
### Symbol References
|
|
8
|
-
|
|
9
|
-
Reference instance methods by symbol for dynamic value transformations:
|
|
10
|
-
|
|
11
|
-
```ruby
|
|
12
|
-
class ProcessAnalytics < CMDx::Task
|
|
13
|
-
attribute :options, transform: :compact_blank
|
|
14
|
-
end
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
### Proc or Lambda
|
|
18
|
-
|
|
19
|
-
Use anonymous functions for dynamic value transformations:
|
|
20
|
-
|
|
21
|
-
```ruby
|
|
22
|
-
class CacheContent < CMDx::Task
|
|
23
|
-
# Proc
|
|
24
|
-
attribute :expire_hours, transform: proc { |v| v * 2 }
|
|
25
|
-
|
|
26
|
-
# Lambda
|
|
27
|
-
attribute :compression, transform: ->(v) { v.to_s.upcase.strip[0..2] }
|
|
28
|
-
end
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### Class or Module
|
|
32
|
-
|
|
33
|
-
Use any object that responds to `call` for reusable transformation logic:
|
|
34
|
-
|
|
35
|
-
```ruby
|
|
36
|
-
class EmailNormalizer
|
|
37
|
-
def call(value)
|
|
38
|
-
value.to_s.downcase.strip
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
class ProcessContacts < CMDx::Task
|
|
43
|
-
# Class or Module
|
|
44
|
-
attribute :email, transform: EmailNormalizer
|
|
45
|
-
|
|
46
|
-
# Instance
|
|
47
|
-
attribute :email, transform: EmailNormalizer.new
|
|
48
|
-
end
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## Validations
|
|
52
|
-
|
|
53
|
-
Validations run on transformed values, ensuring data consistency:
|
|
54
|
-
|
|
55
|
-
```ruby
|
|
56
|
-
class ScheduleBackup < CMDx::Task
|
|
57
|
-
# Coercions
|
|
58
|
-
attribute :retention_days, type: :integer, transform: proc { |v| v.clamp(1, 5) }
|
|
59
|
-
|
|
60
|
-
# Validations
|
|
61
|
-
optional :frequency, transform: :downcase, inclusion: { in: %w[hourly daily weekly monthly] }
|
|
62
|
-
end
|
|
63
|
-
```
|