ruby_reactor 0.5.3 → 0.5.4
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/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +7 -0
- data/README.md +95 -41
- data/lib/ruby_reactor/dsl/template_helpers.rb +7 -1
- data/lib/ruby_reactor/step.rb +4 -0
- data/lib/ruby_reactor/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b41cd34a4e2437fdeead7ad0b9ddeebc493f711e0cc7db8a31ab7c61f3e2be8d
|
|
4
|
+
data.tar.gz: 4179974d1156150a84ca60ef75e04b619dc9a7a2b26fefa5ef7e40beba5c8e14
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bc39fd70ce8a3318dbd41cd49deb4e7ebd66c084e7fb007ff5651d26cf1853c97cf32577ffda863c754328a49518b629aa41f1341398c49e43cd4e5df5070439
|
|
7
|
+
data.tar.gz: 83eddedaebfe39c7b3dad5b88eeaab15c08e11d78a8c894473ab82044047d8c392a4387674199fbb80d4542cf1698f64ce95b7448b8a8cf421ef7ca703b1cbb6
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.4](https://github.com/arturictus/ruby_reactor/compare/v0.5.3...v0.5.4) (2026-06-18)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### documentation
|
|
7
|
+
|
|
8
|
+
* emphasize class-based steps as preferred way ([#38](https://github.com/arturictus/ruby_reactor/issues/38)) ([0ee6234](https://github.com/arturictus/ruby_reactor/commit/0ee62346fd0c49d97c57cef780a6a7135d4253cd))
|
|
9
|
+
|
|
3
10
|
## [0.5.3](https://github.com/arturictus/ruby_reactor/compare/v0.5.2...v0.5.3) (2026-06-17)
|
|
4
11
|
|
|
5
12
|
|
data/README.md
CHANGED
|
@@ -52,6 +52,7 @@ The key value is **Reliability**: if any part of your workflow fails, Ruby React
|
|
|
52
52
|
- [Installation](#installation)
|
|
53
53
|
- [Configuration](#configuration)
|
|
54
54
|
- [Quick Start](#quick-start)
|
|
55
|
+
- [Defining Steps](#defining-steps)
|
|
55
56
|
- [Web Dashboard](#web-dashboard)
|
|
56
57
|
- [Rails Installation](#rails-installation)
|
|
57
58
|
- [Usage](#usage)
|
|
@@ -198,6 +199,60 @@ result = HelloReactor.run
|
|
|
198
199
|
puts result.value # => "Hello from Ruby Reactor!"
|
|
199
200
|
```
|
|
200
201
|
|
|
202
|
+
> **Note:** Examples in this README use inline `step` blocks where a step is trivial. For production workflows, prefer [class-based steps](#defining-steps).
|
|
203
|
+
|
|
204
|
+
## Defining Steps
|
|
205
|
+
|
|
206
|
+
RubyReactor supports two ways to define step logic:
|
|
207
|
+
|
|
208
|
+
| Style | Best for |
|
|
209
|
+
|-------|----------|
|
|
210
|
+
| **Class steps** (preferred) | Real business logic, compensation/undo, shared steps, testability |
|
|
211
|
+
| **Inline blocks** | Quick prototypes, trivial one-liners, documentation examples |
|
|
212
|
+
|
|
213
|
+
Whichever style you use, a step's `run` returns one of three signals — all exposed as bare helpers in both class steps and inline blocks:
|
|
214
|
+
|
|
215
|
+
- **`Success(value)`** — step succeeded; `value` flows to dependent steps.
|
|
216
|
+
- **`Failure(error)`** — step failed; the reactor rolls back completed steps (compensate/undo).
|
|
217
|
+
- **`Skipped(reason:)`** — clean halt: stop the reactor, keep partial progress, **no rollback**. See [Skipping a reactor cleanly](documentation/core_concepts.md#skipping-a-reactor-cleanly).
|
|
218
|
+
|
|
219
|
+
**Class steps** are plain Ruby classes that include `RubyReactor::Step` and implement `run`, and optionally `compensate` and `undo`:
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
class ReserveInventoryStep
|
|
223
|
+
include RubyReactor::Step
|
|
224
|
+
|
|
225
|
+
def self.run(arguments, context)
|
|
226
|
+
reservation_id = InventoryService.reserve(arguments[:order][:items])
|
|
227
|
+
Success(reservation_id: reservation_id)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def self.compensate(error, arguments, context)
|
|
231
|
+
InventoryService.release_partial(arguments[:order][:items])
|
|
232
|
+
Success()
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def self.undo(result, arguments, context)
|
|
236
|
+
InventoryService.release(result[:reservation_id])
|
|
237
|
+
Success()
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
class OrderProcessingReactor < RubyReactor::Reactor
|
|
242
|
+
step :reserve_inventory, ReserveInventoryStep do
|
|
243
|
+
argument :order, result(:validate_order)
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Why prefer class steps?**
|
|
249
|
+
|
|
250
|
+
- **Testability** — unit-test `run`, `compensate`, and `undo` in isolation without booting the whole reactor
|
|
251
|
+
- **Composability** — share the same step class across multiple reactors and compose larger workflows from small, focused units
|
|
252
|
+
- **Readability** — reactor files stay orchestration-only; business logic lives in named classes instead of growing inline blocks
|
|
253
|
+
|
|
254
|
+
See [Core Concepts — Step Classes](documentation/core_concepts.md#step-classes-preferred) for the full reference. Usage examples below mix class and inline steps — inline where the logic is trivial.
|
|
255
|
+
|
|
201
256
|
## Web Dashboard
|
|
202
257
|
|
|
203
258
|
RubyReactor ships with a built-in web dashboard to inspect reactor executions, view logs, and retry failed steps. The dashboard is a Rack app (a [Roda](https://roda.jeremyevans.net/) application) bundled inside the gem with its pre-compiled JS/CSS assets — no extra install or asset build step is required.
|
|
@@ -237,60 +292,59 @@ You can secure the dashboard using standard Rails authentication methods (e.g.,
|
|
|
237
292
|
|
|
238
293
|
## Usage
|
|
239
294
|
|
|
240
|
-
RubyReactor allows you to define complex workflows as "reactors" with steps that can depend on each other, handle failures with compensations, and validate inputs.
|
|
295
|
+
RubyReactor allows you to define complex workflows as "reactors" with steps that can depend on each other, handle failures with compensations, and validate inputs. Examples in this section mix class steps with inline blocks; see [Defining Steps](#defining-steps) for guidance.
|
|
241
296
|
|
|
242
297
|
### Basic Example: User Registration
|
|
243
298
|
|
|
244
299
|
```ruby
|
|
245
300
|
require 'ruby_reactor'
|
|
246
301
|
|
|
302
|
+
class ValidateEmailStep
|
|
303
|
+
include RubyReactor::Step
|
|
304
|
+
|
|
305
|
+
def self.run(arguments, _context)
|
|
306
|
+
email = arguments[:email]
|
|
307
|
+
email&.include?('@') ? Success(email.strip) : Failure("Email must contain @")
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
class CreateUserStep
|
|
312
|
+
include RubyReactor::Step
|
|
313
|
+
|
|
314
|
+
def self.run(arguments, _context)
|
|
315
|
+
Success(
|
|
316
|
+
id: rand(10000),
|
|
317
|
+
email: arguments[:email],
|
|
318
|
+
password_hash: arguments[:password_hash],
|
|
319
|
+
created_at: Time.now
|
|
320
|
+
)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def self.compensate(_error, arguments, _context)
|
|
324
|
+
Notify.to(arguments[:email])
|
|
325
|
+
Success()
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
247
329
|
class UserRegistrationReactor < RubyReactor::Reactor
|
|
248
|
-
# Define inputs with optional validation
|
|
249
330
|
input :email
|
|
250
331
|
input :password
|
|
251
332
|
|
|
252
|
-
|
|
253
|
-
step :validate_email do
|
|
333
|
+
step :validate_email, ValidateEmailStep do
|
|
254
334
|
argument :email, input(:email)
|
|
255
|
-
|
|
256
|
-
run do |args, context|
|
|
257
|
-
if args[:email] && args[:email].include?('@')
|
|
258
|
-
Success(args[:email].strip)
|
|
259
|
-
else
|
|
260
|
-
Failure("Email must contain @")
|
|
261
|
-
end
|
|
262
|
-
end
|
|
263
335
|
end
|
|
264
336
|
|
|
265
337
|
step :hash_password do
|
|
266
338
|
argument :password, input(:password)
|
|
267
|
-
|
|
268
|
-
run do |args, context|
|
|
339
|
+
run do |args, _context|
|
|
269
340
|
require 'digest'
|
|
270
|
-
|
|
271
|
-
Success(hashed)
|
|
341
|
+
Success(Digest::SHA256.hexdigest(args[:password]))
|
|
272
342
|
end
|
|
273
343
|
end
|
|
274
344
|
|
|
275
|
-
step :create_user do
|
|
276
|
-
# Arguments can reference results from other steps
|
|
345
|
+
step :create_user, CreateUserStep do
|
|
277
346
|
argument :email, result(:validate_email)
|
|
278
347
|
argument :password_hash, result(:hash_password)
|
|
279
|
-
|
|
280
|
-
run do |args, context|
|
|
281
|
-
user = {
|
|
282
|
-
id: rand(10000),
|
|
283
|
-
email: args[:email],
|
|
284
|
-
password_hash: args[:password_hash],
|
|
285
|
-
created_at: Time.now
|
|
286
|
-
}
|
|
287
|
-
Success(user)
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
compensate do |error, args, context|
|
|
291
|
-
Notify.to(args[:email])
|
|
292
|
-
Success()
|
|
293
|
-
end
|
|
294
348
|
end
|
|
295
349
|
|
|
296
350
|
step :notify_user do
|
|
@@ -302,12 +356,12 @@ class UserRegistrationReactor < RubyReactor::Reactor
|
|
|
302
356
|
Success()
|
|
303
357
|
end
|
|
304
358
|
|
|
305
|
-
compensate do |
|
|
359
|
+
compensate do |_error, args, _context|
|
|
306
360
|
Email.send("support@acme.com", "Email verification for #{args[:email]} couldn't be sent")
|
|
307
361
|
Success()
|
|
308
362
|
end
|
|
309
363
|
end
|
|
310
|
-
|
|
364
|
+
|
|
311
365
|
returns :create_user
|
|
312
366
|
end
|
|
313
367
|
|
|
@@ -613,14 +667,14 @@ result.success? # true (Skipped is a Success subclass)
|
|
|
613
667
|
result.skipped? # true on dedup hit, false otherwise
|
|
614
668
|
```
|
|
615
669
|
|
|
616
|
-
A step's `run` block can also return `
|
|
670
|
+
A step's `run` block can also return `Skipped(reason: "...")` to halt the reactor cleanly — remaining steps don't execute, **and already-completed steps are NOT compensated**. Use it when the rest of the workflow is unnecessary and partial progress should be kept (`Failure` is for "stop and roll back"). `Skipped` is a bare helper just like `Success`/`Failure` (or use the fully-qualified `RubyReactor.Skipped(...)`).
|
|
617
671
|
|
|
618
672
|
```ruby
|
|
619
673
|
step :ensure_active do
|
|
620
674
|
argument :user, result(:fetch_user)
|
|
621
|
-
run do |args|
|
|
622
|
-
next
|
|
623
|
-
|
|
675
|
+
run do |args, _ctx|
|
|
676
|
+
next Skipped(reason: "user_opted_out") if args[:user].opted_out?
|
|
677
|
+
Success(args[:user])
|
|
624
678
|
end
|
|
625
679
|
end
|
|
626
680
|
```
|
|
@@ -1089,7 +1143,7 @@ end
|
|
|
1089
1143
|
|
|
1090
1144
|
### Testing
|
|
1091
1145
|
|
|
1092
|
-
RubyReactor provides testing utilities for RSpec. See the [Testing with RSpec](documentation/testing.md) guide for comprehensive documentation.
|
|
1146
|
+
RubyReactor provides testing utilities for RSpec. See the [Testing with RSpec](documentation/testing.md) guide for comprehensive documentation — including [unit-testing class-based steps](documentation/testing.md#testing-step-classes) directly.
|
|
1093
1147
|
|
|
1094
1148
|
```ruby
|
|
1095
1149
|
RSpec.describe PaymentReactor do
|
|
@@ -1116,7 +1170,7 @@ end
|
|
|
1116
1170
|
For detailed documentation, see the following guides:
|
|
1117
1171
|
|
|
1118
1172
|
### [Core Concepts](documentation/core_concepts.md)
|
|
1119
|
-
Learn about the fundamental building blocks of RubyReactor: Reactors, Steps, Context, and Results.
|
|
1173
|
+
Learn about the fundamental building blocks of RubyReactor: Reactors, Steps, Context, and Results. Covers class-based steps (the preferred approach) and inline blocks, how data flows between steps, and how the context maintains state throughout execution.
|
|
1120
1174
|
|
|
1121
1175
|
### [DAG (Directed Acyclic Graph)](documentation/DAG.md)
|
|
1122
1176
|
Deep dive into how RubyReactor manages dependencies. This guide explains how the Directed Acyclic Graph is constructed to ensure steps execute in the correct topological order, enabling automatic parallelization of independent steps.
|
|
@@ -19,7 +19,7 @@ module RubyReactor
|
|
|
19
19
|
RubyReactor::Template::Element.new(map_name, path)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
# Make Success and
|
|
22
|
+
# Make Success, Failure, and Skipped available in DSL contexts
|
|
23
23
|
# rubocop:disable Naming/MethodName
|
|
24
24
|
def Success(value = nil)
|
|
25
25
|
# rubocop:enable Naming/MethodName
|
|
@@ -31,6 +31,12 @@ module RubyReactor
|
|
|
31
31
|
# rubocop:enable Naming/MethodName
|
|
32
32
|
RubyReactor.Failure(error)
|
|
33
33
|
end
|
|
34
|
+
|
|
35
|
+
# rubocop:disable Naming/MethodName
|
|
36
|
+
def Skipped(reason: nil, **kwargs)
|
|
37
|
+
# rubocop:enable Naming/MethodName
|
|
38
|
+
RubyReactor.Skipped(reason: reason, **kwargs)
|
|
39
|
+
end
|
|
34
40
|
end
|
|
35
41
|
end
|
|
36
42
|
end
|
data/lib/ruby_reactor/step.rb
CHANGED
data/lib/ruby_reactor/version.rb
CHANGED