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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 778bc5305c6d1f20833819afd9ccd46f5a5b4c2c135d8e63344d6530f9a733f1
4
- data.tar.gz: 32e769816eba846f419e3f31e8290b94e8ff04fe6ea71fef125bb128b3085b82
3
+ metadata.gz: b41cd34a4e2437fdeead7ad0b9ddeebc493f711e0cc7db8a31ab7c61f3e2be8d
4
+ data.tar.gz: 4179974d1156150a84ca60ef75e04b619dc9a7a2b26fefa5ef7e40beba5c8e14
5
5
  SHA512:
6
- metadata.gz: 592db1d0ef94153a4ea028aa3cdeb59f7e4c73929ebec5afd5a9795a93d27a0cfb0c4bd3e135ccf1b14c0329123be6cf0905ad4a141018993832d21212754db3
7
- data.tar.gz: 2fd90e47af8e26cf2d58468a1f0629fd1dc04890df0a6e1704e8d6cba5c65b1e050f10f56628c27c7c90b0545d081e33169e3bfd62f416affd761b62edeb24a0
6
+ metadata.gz: bc39fd70ce8a3318dbd41cd49deb4e7ebd66c084e7fb007ff5651d26cf1853c97cf32577ffda863c754328a49518b629aa41f1341398c49e43cd4e5df5070439
7
+ data.tar.gz: 83eddedaebfe39c7b3dad5b88eeaab15c08e11d78a8c894473ab82044047d8c392a4387674199fbb80d4542cf1698f64ce95b7448b8a8cf421ef7ca703b1cbb6
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.5.3"
2
+ ".": "0.5.4"
3
3
  }
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
- # Define steps with their dependencies
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
- hashed = Digest::SHA256.hexdigest(args[:password])
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 |error, args, context|
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
- # Specify which step's result to return
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 `RubyReactor.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").
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 RubyReactor.Skipped(reason: "user_opted_out") if args[:user].opted_out?
623
- RubyReactor.Success(args[:user])
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. Understand how steps are defined, how data flows between them, and how the context maintains state throughout execution.
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 Failure available in DSL contexts
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
@@ -15,6 +15,10 @@ module RubyReactor
15
15
  def Failure(error = nil)
16
16
  RubyReactor::Failure(error)
17
17
  end
18
+
19
+ def Skipped(reason: nil, **kwargs)
20
+ RubyReactor.Skipped(reason: reason, **kwargs)
21
+ end
18
22
  # rubocop:enable Naming/MethodName
19
23
 
20
24
  def run(arguments, context)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyReactor
4
- VERSION = "0.5.3"
4
+ VERSION = "0.5.4"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_reactor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.5.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Artur