ask-graph 0.5.0 → 0.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +125 -21
- data/README.md +145 -116
- data/lib/ask/graph/context.rb +21 -0
- data/lib/ask/graph/runner.rb +3 -18
- data/lib/ask/graph/version.rb +1 -1
- data/lib/ask/graph.rb +45 -11
- 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: 99d66a3b6b63d97a13187b67bbefd25c291d5ab80a63146e61639a0b7949689d
|
|
4
|
+
data.tar.gz: ab66b1d0586ee8570e71f2612938d6d87a9e3c5a3d44b93b046ef95343a5951a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8cd5ca7104c5ea6f643e9f9333ec8a7578b53f0fcda6c0f8a5a4273ac9cbb856025024e192a9f89c4a30fc3c8072b6b2b46283ccdf3805db05faade627fef81c
|
|
7
|
+
data.tar.gz: f8f4e2494be0350b17b86f568c1de1428a1731b73f9599132f6fe408a70fed15649c803847cf01a36ba009b819f2bcfaf3f37df6d8db2a8dd36dface2c8ae01b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,56 +1,160 @@
|
|
|
1
|
+
## [0.6.1] — 2026-07-27
|
|
2
|
+
|
|
3
|
+
### Fixed
|
|
4
|
+
|
|
5
|
+
- **Hooks leak between classes** — `inherited` now deep-copies lifecycle
|
|
6
|
+
hook arrays instead of sharing them. Previously, a child class's
|
|
7
|
+
`after_step :foo` would mutate the parent class's hook arrays, causing
|
|
8
|
+
hooks to fire multiple times in sibling classes.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **Docs updated** to use the `Module::Workflow` convention throughout.
|
|
13
|
+
Recommended layout:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
app/workflows/
|
|
17
|
+
notify_customer/
|
|
18
|
+
workflow.rb # module NotifyCustomer; class Workflow < Ask::Graph
|
|
19
|
+
steps/
|
|
20
|
+
send_email.rb
|
|
21
|
+
log_notification.rb
|
|
22
|
+
order_fulfillment/
|
|
23
|
+
workflow.rb
|
|
24
|
+
steps/
|
|
25
|
+
validate_payment.rb
|
|
26
|
+
notify_customer.rb
|
|
27
|
+
ship_order.rb
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Sub-graph composition via `Workflow.call(context)` or `context.run(Workflow)`.
|
|
31
|
+
|
|
32
|
+
### Tested
|
|
33
|
+
|
|
34
|
+
- 85 tests, 111 assertions, 0 failures
|
|
35
|
+
|
|
36
|
+
## [0.6.0] — 2026-07-27
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- **All steps must be POROs** — the framework no longer auto-detects
|
|
41
|
+
`Ask::Graph` subclasses used directly as steps. Every `step` declaration
|
|
42
|
+
must be a plain Ruby class with a `call(context)` method. This eliminates
|
|
43
|
+
ambiguity at the `step` call site.
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
# Before — worked but was ambiguous
|
|
47
|
+
step NotifyCustomer # is this a Graph or a PORO?
|
|
48
|
+
|
|
49
|
+
# After — always a PORO
|
|
50
|
+
step NotifyCustomer # definitely a PORO
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
- **Sub-graph composition via `Workflow.call(context)`** — compose workflows
|
|
54
|
+
by calling another graph's class method with the current context. The
|
|
55
|
+
method auto-detects a Context argument and handles export → run → import.
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
class NotifyCustomer
|
|
59
|
+
def call(context)
|
|
60
|
+
NotifyCustomerWorkflow.call(context)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`Graph.call(ctx)` is the same API users already know from controllers:
|
|
66
|
+
`Graph.call(params)`. Just pass a Context instead of data.
|
|
67
|
+
|
|
68
|
+
### Added
|
|
69
|
+
|
|
70
|
+
- **`Context#run(graph_class)`** — convenience wrapper around the export →
|
|
71
|
+
create → call → import pattern.
|
|
72
|
+
|
|
73
|
+
- **Hash inputs flatten into context keys** — when a Hash is passed as
|
|
74
|
+
input, its keys are directly accessible on the context in addition to
|
|
75
|
+
`context.input`. Enables sub-graphs to transparently read outer values.
|
|
76
|
+
|
|
77
|
+
- **`Context#export_data`** and **`Context#import(other)`** — public API
|
|
78
|
+
for sub-graph data passing.
|
|
79
|
+
|
|
80
|
+
### Tested
|
|
81
|
+
|
|
82
|
+
- 85 tests, 111 assertions, 0 failures
|
|
83
|
+
|
|
1
84
|
## [0.5.0] — 2026-07-27
|
|
2
85
|
|
|
3
86
|
### Added
|
|
4
87
|
|
|
5
|
-
- **Sub-graph composition
|
|
6
|
-
|
|
7
|
-
|
|
88
|
+
- **Sub-graph composition via `Workflow.call(context)`** — compose workflows
|
|
89
|
+
by calling another graph's `call` class method with the current context.
|
|
90
|
+
The method detects the Context argument and handles export → run → import
|
|
91
|
+
automatically.
|
|
8
92
|
|
|
9
93
|
```ruby
|
|
10
|
-
|
|
94
|
+
# Define a workflow
|
|
95
|
+
class NotifyCustomerWorkflow < Ask::Graph
|
|
11
96
|
step SendEmail
|
|
12
97
|
step LogNotification
|
|
13
98
|
end
|
|
14
99
|
|
|
100
|
+
# Use it as a step via a PORO wrapper
|
|
101
|
+
class NotifyCustomer
|
|
102
|
+
def call(context)
|
|
103
|
+
NotifyCustomerWorkflow.call(context)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Compose — every step is a PORO
|
|
15
108
|
class HandleOrder < Ask::Graph
|
|
16
109
|
step ValidatePayment
|
|
17
|
-
step NotifyCustomer
|
|
110
|
+
step NotifyCustomer
|
|
18
111
|
step ShipOrder
|
|
19
112
|
end
|
|
20
113
|
```
|
|
21
114
|
|
|
22
|
-
-
|
|
23
|
-
|
|
24
|
-
-
|
|
115
|
+
- `Graph.call(context)` exports the context data, creates the sub-graph,
|
|
116
|
+
runs it, and imports results back — all automatically
|
|
117
|
+
- Same API users already know from `Graph.call(params)` in controllers
|
|
118
|
+
- Supports nesting (sub-graphs calling sub-graphs)
|
|
25
119
|
- Supports `timeout:`, `retry:`, and `if:`/`unless:` on the outer step
|
|
26
120
|
- Supports parallel steps inside sub-graphs
|
|
27
|
-
|
|
121
|
+
|
|
122
|
+
- **Hash inputs flatten into context keys** — when a Hash is passed as
|
|
123
|
+
input, its keys are directly accessible on the context in addition to
|
|
124
|
+
`context.input`. This enables sub-graphs to transparently read outer
|
|
125
|
+
context values.
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
g.new({ value: 42 }).call
|
|
129
|
+
ctx.value # => 42 (directly accessible)
|
|
130
|
+
ctx.input # => { value: 42 } (still works as before)
|
|
131
|
+
```
|
|
28
132
|
|
|
29
133
|
- **`Context#export_data`** — returns the raw store hash without JSON
|
|
30
134
|
serialization. Used internally for sub-graph data passing.
|
|
31
135
|
|
|
32
136
|
- **`Context#import(other)`** — merges data from another context or hash
|
|
33
|
-
into this one.
|
|
137
|
+
into this one.
|
|
34
138
|
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
addition to `context.input`. This enables sub-graphs to transparently
|
|
38
|
-
read outer context values.
|
|
139
|
+
- **`Context#run(graph_class)`** — convenience method wrapping the
|
|
140
|
+
export → create → call → import pattern.
|
|
39
141
|
|
|
40
142
|
```ruby
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
143
|
+
def call(context)
|
|
144
|
+
context.run(NotifyCustomerWorkflow)
|
|
145
|
+
end
|
|
44
146
|
```
|
|
45
147
|
|
|
46
|
-
###
|
|
148
|
+
### Changed
|
|
47
149
|
|
|
48
|
-
-
|
|
49
|
-
|
|
150
|
+
- **All steps are POROs** — the framework no longer detects `Ask::Graph`
|
|
151
|
+
subclasses used directly as steps. Every `step` declaration must be a
|
|
152
|
+
plain Ruby class with a `call(context)` method. This eliminates the
|
|
153
|
+
ambiguity of "is this step a graph or a PORO?"
|
|
50
154
|
|
|
51
155
|
### Tested
|
|
52
156
|
|
|
53
|
-
-
|
|
157
|
+
- 85 tests, 111 assertions, 0 failures
|
|
54
158
|
|
|
55
159
|
## [0.4.0] — 2026-07-27
|
|
56
160
|
|
data/README.md
CHANGED
|
@@ -6,28 +6,30 @@
|
|
|
6
6
|
gem "ask-graph"
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
## Quick Start
|
|
10
|
-
|
|
11
|
-
```ruby
|
|
12
|
-
require "ask-graph"
|
|
13
|
-
|
|
14
|
-
# Define a workflow
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
require "ask-graph"
|
|
13
|
+
|
|
14
|
+
# Define a workflow
|
|
15
|
+
module ProcessOrder
|
|
16
|
+
class Workflow < Ask::Graph
|
|
17
|
+
step ValidateOrder
|
|
18
|
+
step ChargeCustomer, if: :valid?
|
|
19
|
+
step SendConfirmation, if: :valid?
|
|
20
|
+
step NotifyAdmin, unless: :valid?
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def valid?
|
|
25
|
+
context.order.valid?
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Run it
|
|
31
|
+
result = ProcessOrder::Workflow.call(order: order)
|
|
32
|
+
```
|
|
31
33
|
|
|
32
34
|
## Steps
|
|
33
35
|
|
|
@@ -41,86 +43,96 @@ class ValidateOrder
|
|
|
41
43
|
end
|
|
42
44
|
```
|
|
43
45
|
|
|
44
|
-
### Sequential steps
|
|
45
|
-
|
|
46
|
-
```ruby
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
class
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
end
|
|
123
|
-
|
|
46
|
+
### Sequential steps
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
module HandleCall
|
|
50
|
+
class Workflow < Ask::Graph
|
|
51
|
+
step Transcribe
|
|
52
|
+
step Classify
|
|
53
|
+
step Respond
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Conditional steps
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
module HandleCall
|
|
62
|
+
class Workflow < Ask::Graph
|
|
63
|
+
step BookAppointment, if: :booking?
|
|
64
|
+
step EmergencyAlert, if: :emergency?
|
|
65
|
+
step HandleInquiry, unless: :known_intent?
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def booking? = context.intent == "booking"
|
|
70
|
+
def emergency? = context.intent == "emergency"
|
|
71
|
+
def known_intent? = %w[booking emergency inquiry].include?(context.intent)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Parallel steps
|
|
77
|
+
|
|
78
|
+
Use `steps` (plural) to run multiple steps simultaneously:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
module SyncData
|
|
82
|
+
class Workflow < Ask::Graph
|
|
83
|
+
step FetchRecords
|
|
84
|
+
|
|
85
|
+
# All three run in parallel
|
|
86
|
+
steps CrmUpdate, CalendarSync, SendNotification
|
|
87
|
+
|
|
88
|
+
step ConfirmResponse
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Human-in-the-loop (approve)
|
|
94
|
+
|
|
95
|
+
`approve` runs a step, then pauses the workflow and waits for external input:
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
module ProcessBooking
|
|
99
|
+
class Workflow < Ask::Graph
|
|
100
|
+
step BookAppointment
|
|
101
|
+
approve ReviewBooking, if: :expensive?
|
|
102
|
+
step ConfirmBooking
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
After `approve` pauses, resume with:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
graph = ProcessBooking::Workflow.new
|
|
111
|
+
result = graph.run # runs, pauses after ReviewBooking
|
|
112
|
+
result = graph.resume(input: "approved") # resumes, runs ConfirmBooking
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Per-item loops
|
|
116
|
+
|
|
117
|
+
Use `context.each` inside a step for iteration with automatic checkpointing:
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
class SendVoiceReminders
|
|
121
|
+
def call(context)
|
|
122
|
+
context.each(context.appointments) do |appt|
|
|
123
|
+
PhoneService.call(appt.number, appt.message)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
module DailyReminders
|
|
129
|
+
class Workflow < Ask::Graph
|
|
130
|
+
step FetchAppointments
|
|
131
|
+
step SendVoiceReminders # loops with per-item checkpointing
|
|
132
|
+
step MarkComplete
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
```
|
|
124
136
|
|
|
125
137
|
If the process crashes mid-loop, it resumes from the last completed item.
|
|
126
138
|
|
|
@@ -146,33 +158,48 @@ end
|
|
|
146
158
|
|
|
147
159
|
### Sub-workflows
|
|
148
160
|
|
|
149
|
-
A step can delegate to another workflow
|
|
161
|
+
A step can delegate to another workflow by calling `Workflow.call(context)`.
|
|
162
|
+
The context is automatically exported, passed to the sub-workflow, and
|
|
163
|
+
results are merged back on completion:
|
|
150
164
|
|
|
151
165
|
```ruby
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
BookingWorkflow.new.call(context) # sub-workflow as a step
|
|
157
|
-
when "emergency"
|
|
158
|
-
EmergencyWorkflow.new.call(context)
|
|
166
|
+
module OrderFulfillment
|
|
167
|
+
class NotifyCustomer
|
|
168
|
+
def call(context)
|
|
169
|
+
NotifyCustomer::Workflow.call(context)
|
|
159
170
|
end
|
|
160
171
|
end
|
|
172
|
+
|
|
173
|
+
class Workflow < Ask::Graph
|
|
174
|
+
step ValidatePayment
|
|
175
|
+
step NotifyCustomer
|
|
176
|
+
step ShipOrder
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Or use `context.run` for the same thing:
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
class NotifyCustomer
|
|
185
|
+
def call(context)
|
|
186
|
+
context.run(NotifyCustomer::Workflow)
|
|
187
|
+
end
|
|
161
188
|
end
|
|
162
189
|
```
|
|
163
190
|
|
|
164
191
|
### Checkpointing
|
|
165
192
|
|
|
166
|
-
Pass a
|
|
193
|
+
Pass a `storage` backend to make workflows durable across crashes:
|
|
167
194
|
|
|
168
195
|
```ruby
|
|
169
196
|
store = Ask::State::Memory.new # or Redis, SQLite, etc.
|
|
170
197
|
|
|
171
198
|
# Runs through all steps, saving after each
|
|
172
|
-
result =
|
|
199
|
+
result = OrderFulfillment::Workflow.call(input, storage: store)
|
|
173
200
|
|
|
174
201
|
# If a crash occurs, resume from the last completed step
|
|
175
|
-
result =
|
|
202
|
+
result = OrderFulfillment::Workflow.call(input, storage: store)
|
|
176
203
|
```
|
|
177
204
|
|
|
178
205
|
### Error handling
|
|
@@ -180,12 +207,14 @@ result = MyWorkflow.run(input, checkpoint_store: store)
|
|
|
180
207
|
Failed steps raise `Ask::Graph::StepFailed` with the step name:
|
|
181
208
|
|
|
182
209
|
```ruby
|
|
183
|
-
|
|
184
|
-
|
|
210
|
+
module MyWorkflow
|
|
211
|
+
class Workflow < Ask::Graph
|
|
212
|
+
step RiskyOperation
|
|
213
|
+
end
|
|
185
214
|
end
|
|
186
215
|
|
|
187
216
|
begin
|
|
188
|
-
|
|
217
|
+
MyWorkflow::Workflow.call
|
|
189
218
|
rescue Ask::Graph::StepFailed => e
|
|
190
219
|
puts e.message # => "RiskyOperation failed: connection timeout"
|
|
191
220
|
end
|
data/lib/ask/graph/context.rb
CHANGED
|
@@ -123,6 +123,27 @@ module Ask
|
|
|
123
123
|
@mutex.synchronize { @store.merge!(data) }
|
|
124
124
|
end
|
|
125
125
|
|
|
126
|
+
# Run an {Ask::Graph} as a sub-graph, merging its context back.
|
|
127
|
+
#
|
|
128
|
+
# Exports the current context data, creates the sub-graph with it,
|
|
129
|
+
# runs it, and merges any results back into this context.
|
|
130
|
+
#
|
|
131
|
+
# @param graph_class [Class < Ask::Graph] the graph to run
|
|
132
|
+
# @return [Ask::Graph::Context] the sub-graph's context
|
|
133
|
+
# @example
|
|
134
|
+
# # Inside a step PORO
|
|
135
|
+
# class CalculateShipping
|
|
136
|
+
# def call(context)
|
|
137
|
+
# context.run(Shipping::Workflow)
|
|
138
|
+
# end
|
|
139
|
+
# end
|
|
140
|
+
def run(graph_class)
|
|
141
|
+
sub = graph_class.new(export_data)
|
|
142
|
+
sub_ctx = sub.call
|
|
143
|
+
import(sub_ctx)
|
|
144
|
+
sub_ctx
|
|
145
|
+
end
|
|
146
|
+
|
|
126
147
|
private
|
|
127
148
|
|
|
128
149
|
# Recursively convert a value to a JSON-safe structure.
|
data/lib/ask/graph/runner.rb
CHANGED
|
@@ -133,29 +133,14 @@ module Ask
|
|
|
133
133
|
end
|
|
134
134
|
|
|
135
135
|
def run_single(klass, _name, context)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
instance = klass.new
|
|
140
|
-
instance.call(context)
|
|
141
|
-
end
|
|
142
|
-
rescue Paused, ArgumentError
|
|
136
|
+
instance = klass.new
|
|
137
|
+
instance.call(context)
|
|
138
|
+
rescue Paused
|
|
143
139
|
raise
|
|
144
140
|
rescue => e
|
|
145
141
|
raise StepFailed, "#{klass.name} failed: #{e.message}"
|
|
146
142
|
end
|
|
147
143
|
|
|
148
|
-
def run_subgraph(klass, context)
|
|
149
|
-
if klass.respond_to?(:declarations) && klass.declarations.any? { |d| d[:type] == :approve }
|
|
150
|
-
raise ArgumentError,
|
|
151
|
-
"#{klass.name} contains approve steps — sub-graphs with approve are not yet supported"
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
sub = klass.new(context.export_data, storage: @store)
|
|
155
|
-
sub_ctx = sub.call
|
|
156
|
-
context.import(sub_ctx) if sub_ctx
|
|
157
|
-
end
|
|
158
|
-
|
|
159
144
|
def run_parallel(classes, _name, context)
|
|
160
145
|
threads = classes.map do |klass|
|
|
161
146
|
Thread.new do
|
data/lib/ask/graph/version.rb
CHANGED
data/lib/ask/graph.rb
CHANGED
|
@@ -11,21 +11,34 @@ module Ask
|
|
|
11
11
|
# lifecycle hooks.
|
|
12
12
|
#
|
|
13
13
|
# @example Basic workflow
|
|
14
|
-
# class HandleCall < Ask::Graph
|
|
14
|
+
# class HandleCall::Workflow < Ask::Graph
|
|
15
15
|
# step Transcribe
|
|
16
16
|
# step Classify, if: :needs_classification?
|
|
17
17
|
# step BookAppointment
|
|
18
18
|
# end
|
|
19
19
|
#
|
|
20
|
-
# @example Sub-graph composition
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
20
|
+
# @example Sub-graph composition
|
|
21
|
+
# # Define a reusable workflow
|
|
22
|
+
# module NotifyCustomer
|
|
23
|
+
# class Workflow < Ask::Graph
|
|
24
|
+
# step SendEmail
|
|
25
|
+
# step LogNotification
|
|
26
|
+
# end
|
|
24
27
|
# end
|
|
25
28
|
#
|
|
26
|
-
#
|
|
29
|
+
# # Wrap it in a PORO step
|
|
30
|
+
# module OrderFulfillment
|
|
31
|
+
# class NotifyCustomer
|
|
32
|
+
# def call(context)
|
|
33
|
+
# NotifyCustomer::Workflow.call(context)
|
|
34
|
+
# end
|
|
35
|
+
# end
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# # Compose — every step is a PORO
|
|
39
|
+
# class OrderFulfillment::Workflow < Ask::Graph
|
|
27
40
|
# step ValidatePayment
|
|
28
|
-
# step NotifyCustomer
|
|
41
|
+
# step NotifyCustomer
|
|
29
42
|
# step ShipOrder
|
|
30
43
|
# end
|
|
31
44
|
#
|
|
@@ -56,7 +69,8 @@ module Ask
|
|
|
56
69
|
def inherited(subclass)
|
|
57
70
|
super
|
|
58
71
|
subclass.instance_variable_set(:@declarations, declarations.dup)
|
|
59
|
-
subclass.instance_variable_set(:@lifecycle_hooks,
|
|
72
|
+
subclass.instance_variable_set(:@lifecycle_hooks,
|
|
73
|
+
lifecycle_hooks.transform_values(&:dup))
|
|
60
74
|
end
|
|
61
75
|
|
|
62
76
|
# --- Timeout ---
|
|
@@ -187,9 +201,29 @@ module Ask
|
|
|
187
201
|
declarations << build_declaration(:approve, klass, opts)
|
|
188
202
|
end
|
|
189
203
|
|
|
190
|
-
#
|
|
204
|
+
# Create an instance and run it.
|
|
205
|
+
#
|
|
206
|
+
# When +input+ is a {Context}, the graph is run as a sub-graph:
|
|
207
|
+
# the context data is exported, the graph runs with it, and results
|
|
208
|
+
# are merged back into the original context automatically.
|
|
209
|
+
#
|
|
210
|
+
# @example Standalone — pass data
|
|
211
|
+
# OrderFulfillment::Workflow.call({ user: "alice" })
|
|
212
|
+
#
|
|
213
|
+
# @example Inside a step — pass the outer context
|
|
214
|
+
# class ValidatePayment
|
|
215
|
+
# def call(context)
|
|
216
|
+
# PaymentGateway::Workflow.call(context)
|
|
217
|
+
# end
|
|
218
|
+
# end
|
|
191
219
|
def call(input = nil, storage: nil)
|
|
192
|
-
|
|
220
|
+
if input.is_a?(Context)
|
|
221
|
+
ctx = new(input.export_data, storage: storage).call
|
|
222
|
+
input.import(ctx)
|
|
223
|
+
ctx
|
|
224
|
+
else
|
|
225
|
+
new(input, storage: storage).call
|
|
226
|
+
end
|
|
193
227
|
end
|
|
194
228
|
alias run call
|
|
195
229
|
|
|
@@ -224,7 +258,7 @@ module Ask
|
|
|
224
258
|
attr_reader :runner
|
|
225
259
|
attr_reader :context
|
|
226
260
|
|
|
227
|
-
def call
|
|
261
|
+
def call(*)
|
|
228
262
|
@context = Context.new(self, @input)
|
|
229
263
|
@runner.run(@context)
|
|
230
264
|
@context
|