trailblazer-activity 0.4.0 → 0.4.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/CHANGES.md +5 -0
- data/README.md +55 -85
- data/Rakefile +1 -1
- data/lib/trailblazer/activity/dsl/helper.rb +3 -3
- data/lib/trailblazer/activity/implementation/interface.rb +3 -3
- data/lib/trailblazer/activity/introspect.rb +8 -8
- data/lib/trailblazer/activity/magnetic/builder/fast_track.rb +1 -1
- data/lib/trailblazer/activity/magnetic/builder/path.rb +1 -1
- data/lib/trailblazer/activity/magnetic/builder/railway.rb +1 -1
- data/lib/trailblazer/activity/magnetic/dsl.rb +1 -1
- data/lib/trailblazer/activity/state.rb +1 -1
- data/lib/trailblazer/activity/structures.rb +10 -9
- data/lib/trailblazer/activity/subprocess.rb +2 -2
- data/lib/trailblazer/activity/version.rb +1 -1
- data/lib/trailblazer/circuit.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b2666e0f5190759b775c83c2cd4d04c87d64b230e71e4921a01c6656fd27d5c8
|
|
4
|
+
data.tar.gz: b16fe2deab89048538e6351aabd5b274a55f378e0dc081ea3e3519586a2146be
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3c100c03b31cd3abf3d8c60b16d6e710202c9503e74aadd2f70d8c41ae33a48fcc92b86e862e58c0609fdcb9169ab1dcd3e4a08aa933a78412b397dccbd49591
|
|
7
|
+
data.tar.gz: 73a7aa9fec238f54c07b888038e252948fa3c4db76183b37f6848421c53e3c2a31aab4bb235889517edc8ec4d3b055d1855c475811a4bf692d9653776d9979f1
|
data/CHANGES.md
CHANGED
data/README.md
CHANGED
|
@@ -1,109 +1,79 @@
|
|
|
1
1
|
# Activity
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The `activity` gem brings a light-weight DSL to define business processes, and the runtime logic to run those activities.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
A process is a set of arbitrary pieces of logic you define, chained together and put into a meaningful context by an activity. Activity lets you focus on the implementation of steps while Trailblazer takes care of the control flow.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Please find the [full documentation on the Trailblazer website](http://trailblazer.to/2.1/activity). [Note that the docs are WIP.]
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
gem "trailblazer-activity"
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## Overview
|
|
14
|
-
|
|
15
|
-
> Since TRB 2.1, we use [BPMN](http://www.bpmn.org/) lingo and concepts for describing workflows and processes.
|
|
16
|
-
|
|
17
|
-
An activity is a workflow that contains one or several tasks. It is the main concept to organize control flow in Trailblazer.
|
|
18
|
-
|
|
19
|
-
The following diagram illustrates an exemplary workflow where a user writes and publishes a blog post.
|
|
20
|
-
|
|
21
|
-
<img src="http://trb.to/images/diagrams/blog-bpmn1.png">
|
|
22
|
-
|
|
23
|
-
After writing and spell-checking, the author has the chance to publish the post or, in case of typos, go back, correct, and go through the same flow, again. Note that there's only a handful of defined transistions, or connections. An author, for example, is not allowed to jump from "correct" into "publish" without going through the check.
|
|
24
|
-
|
|
25
|
-
The `activity` gem allows you to define this *activity* and takes care of implementing the control flow, running the activity and making sure no invalid paths are taken.
|
|
26
|
-
|
|
27
|
-
Your job is solely to implement the tasks and deciders put into this activity - Trailblazer makes sure it is executed it in the right order, and so on.
|
|
28
|
-
|
|
29
|
-
To eventually run this activity, three things have to be done.
|
|
30
|
-
|
|
31
|
-
1. The activity needs be defined. Easiest is to use the [Activity.from_hash builder](#activity-fromhash).
|
|
32
|
-
2. It's the programmer's job (that's you!) to implement the actual tasks (the "boxes"). Use [tasks for that](#task).
|
|
33
|
-
3. After defining and implementing, you can run the activity with any data [by `call`ing it](#activity-call).
|
|
34
|
-
|
|
35
|
-
## Operation vs. Activity
|
|
9
|
+
## Example
|
|
36
10
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Please note that an `Operation` simply provides a neat DSL for creating an `Activity` with a railway-oriented wiring (left and right track). An operation _always_ maintains an activity internally.
|
|
11
|
+
The `activity` gem provides three default patterns to model processes: `Path`, `Railway` and `FastTrack`. Here's an example what a railway activity could look like, along with some more complex connections.
|
|
40
12
|
|
|
41
13
|
```ruby
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
14
|
+
module Memo::Update
|
|
15
|
+
extend Trailblazer::Activity::Railway()
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
# here goes your business logic
|
|
19
|
+
#
|
|
20
|
+
def find_model(ctx, id:, **)
|
|
21
|
+
ctx[:model] = Memo.find_by(id: id)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def validate(ctx, params:, **)
|
|
25
|
+
return true if params[:body].is_a?(String) && params[:body].size > 10
|
|
26
|
+
ctx[:errors] = "body not long enough"
|
|
27
|
+
false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def save(ctx, model:, params:, **)
|
|
31
|
+
model.update_attributes(params)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def log_error(ctx, params:, **)
|
|
35
|
+
ctx[:log] = "Some idiot wrote #{params.inspect}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# here comes the DSL describing the layout of the activity
|
|
39
|
+
#
|
|
40
|
+
step method(:find_model)
|
|
41
|
+
step method(:validate), Output(:failure) => End(:validation_error)
|
|
42
|
+
step method(:save)
|
|
43
|
+
fail method(:log_error)
|
|
50
44
|
end
|
|
51
45
|
```
|
|
52
46
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<img src="http://trb.to/images/graph/op-vs-activity.png">
|
|
56
|
-
|
|
57
|
-
When `call`ing an operation, several transformations on the arguments are applied, and those are passed to the `Activity#call` invocation. After the activity finished, its output is transformed into a `Result` object.
|
|
58
|
-
|
|
59
|
-
## Activity
|
|
47
|
+
Visually, this would translate to the following circuit.
|
|
60
48
|
|
|
61
|
-
|
|
49
|
+
<img src="http://trailblazer.to/images/2.1/activity-readme-example.png">
|
|
62
50
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
Instead of using an operation, you can manually define activities by using the `Activity.from_hash` builder.
|
|
51
|
+
You can run the activity by invoking its `call` method.
|
|
66
52
|
|
|
67
53
|
```ruby
|
|
68
|
-
|
|
69
|
-
{
|
|
70
|
-
start => { Trailblazer::Activity::Right => Blog::Write },
|
|
71
|
-
Blog::Write => { Trailblazer::Activity::Right => Blog::SpellCheck },
|
|
72
|
-
Blog::SpellCheck => { Trailblazer::Activity::Right => Blog::Publish,
|
|
73
|
-
Trailblazer::Activity::Left => Blog::Correct },
|
|
74
|
-
Blog::Correct => { Trailblazer::Activity::Right => Blog::SpellCheck },
|
|
75
|
-
Blog::Publish => { Trailblazer::Activity::Right => _end }
|
|
76
|
-
}
|
|
77
|
-
end
|
|
78
|
-
```
|
|
54
|
+
ctx = { id: 1, params: { body: "Awesome!" } }
|
|
79
55
|
|
|
56
|
+
signal, (ctx, *) = Update.( [ctx, {}] )
|
|
80
57
|
|
|
81
|
-
|
|
58
|
+
pp ctx #=>
|
|
59
|
+
{:id=>1,
|
|
60
|
+
:params=>{:body=>"Awesome!"},
|
|
61
|
+
:model=>#<Memo body=nil>,
|
|
62
|
+
:errors=>"body not long enough"}
|
|
82
63
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
To run the activity, you want to `call` it.
|
|
86
|
-
|
|
87
|
-
```ruby
|
|
88
|
-
my_options = {}
|
|
89
|
-
last_signal, options, flow_options, _ = activity.( nil, my_options, {} )
|
|
64
|
+
pp signal #=> #<struct Trailblazer::Activity::End semantic=:validation_error>
|
|
90
65
|
```
|
|
91
66
|
|
|
92
|
-
|
|
93
|
-
2. This emitted (or returned) signal is connected to the next task `Blog::Write`, which is now `call`ed.
|
|
94
|
-
3. `Blog::Write` emits another `Right` signal that leads to `Blog::SpellCheck` being `call`ed.
|
|
95
|
-
4. `Blog::SpellCheck` defines two outgoing signals and hence can decide what next task to call by emitting either `Right` if the spell check was ok, or `Left` if the post contains typos.
|
|
96
|
-
5. ...and so on.
|
|
97
|
-
|
|
98
|
-
<img src="http://trb.to/images/graph/blogpost-activity.png">
|
|
99
|
-
|
|
100
|
-
Visualizing an activity as a graph makes it very straight-forward to understanding the mechanics of the flow.
|
|
101
|
-
|
|
67
|
+
With Activity, modeling business processes turns out to be ridiculously simple: You define what should happen and when, and Trailblazer makes sure _that_ it happens.
|
|
102
68
|
|
|
103
|
-
|
|
69
|
+
## Features
|
|
104
70
|
|
|
105
|
-
|
|
71
|
+
* Activities can model any process with arbitrary flow and connections.
|
|
72
|
+
* Nesting and compositions are allowed and encouraged.
|
|
73
|
+
* Different step interfaces, manual processing of DSL options, etc is all possible.
|
|
74
|
+
* Steps can be any kind of callable objects.
|
|
75
|
+
* Tracing!
|
|
106
76
|
|
|
107
|
-
##
|
|
77
|
+
## Operation
|
|
108
78
|
|
|
109
|
-
|
|
79
|
+
Trailblazer's [`Operation`](http://trailblazer.to/2.1/operation) internally uses an activity to model the processes.
|
data/Rakefile
CHANGED
|
@@ -24,11 +24,11 @@ module Trailblazer
|
|
|
24
24
|
Activity.Output(signal, semantic)
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
def End(
|
|
28
|
-
Activity.End(
|
|
27
|
+
def End(semantic)
|
|
28
|
+
Activity.End(semantic)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
def Path(normalizer, track_color: "track_#{rand}", end_semantic:
|
|
31
|
+
def Path(normalizer, track_color: "track_#{rand}", end_semantic: track_color, **options)
|
|
32
32
|
options = options.merge(track_color: track_color, end_semantic: end_semantic)
|
|
33
33
|
|
|
34
34
|
# Build an anonymous class which will be where the block is evaluated in.
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
class Trailblazer::Activity < Module
|
|
2
2
|
module Interface
|
|
3
3
|
# @return [Process, Hash, Adds] Adds is private and should not be used in your application as it might get removed.
|
|
4
|
-
def
|
|
4
|
+
def to_h # TODO: test me
|
|
5
5
|
@state.to_h
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
def debug # TODO: TEST ME
|
|
9
|
-
|
|
9
|
+
to_h[:debug]
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def outputs
|
|
13
|
-
|
|
13
|
+
to_h[:outputs]
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
end
|
|
@@ -16,15 +16,15 @@ module Trailblazer
|
|
|
16
16
|
|
|
17
17
|
# @api private
|
|
18
18
|
def self.find(activity, &block)
|
|
19
|
-
circuit = activity.
|
|
19
|
+
circuit = activity.to_h[:circuit]
|
|
20
20
|
|
|
21
21
|
circuit.instance_variable_get(:@map).find(&block)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def self.collect(activity, options={}, &block)
|
|
26
|
-
circuit
|
|
27
|
-
circuit_hash
|
|
26
|
+
circuit = activity.to_h[:circuit]
|
|
27
|
+
circuit_hash = circuit.to_h[:map]
|
|
28
28
|
|
|
29
29
|
locals = circuit_hash.collect do |task, connections|
|
|
30
30
|
[
|
|
@@ -39,7 +39,7 @@ module Trailblazer
|
|
|
39
39
|
|
|
40
40
|
# render
|
|
41
41
|
def self.Cct(circuit, **options)
|
|
42
|
-
circuit_hash( circuit.
|
|
42
|
+
circuit_hash( circuit.to_h[:map], **options )
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def self.circuit_hash(circuit_hash, show_ids:false)
|
|
@@ -59,7 +59,7 @@ module Trailblazer
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def self.Ends(activity)
|
|
62
|
-
end_events = activity.
|
|
62
|
+
end_events = activity.to_h[:end_events]
|
|
63
63
|
ends = end_events.collect { |evt| Task(evt) }.join(",")
|
|
64
64
|
"[#{ends}]".gsub(/\d\d+/, "")
|
|
65
65
|
end
|
|
@@ -74,9 +74,9 @@ module Trailblazer
|
|
|
74
74
|
return task.inspect unless task.kind_of?(Trailblazer::Activity::End)
|
|
75
75
|
|
|
76
76
|
class_name = strip(task.class)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
"#<#{class_name}
|
|
77
|
+
options = task.to_h
|
|
78
|
+
|
|
79
|
+
"#<#{class_name}/#{options[:semantic].inspect}>"
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
def self.strip(string)
|
|
@@ -81,7 +81,7 @@ module Trailblazer
|
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
# Adds the End.fail_fast and End.pass_fast end to the Railway sequence.
|
|
84
|
-
def self.InitialAdds(pass_fast_end: Activity.End(
|
|
84
|
+
def self.InitialAdds(pass_fast_end: Activity.End(:pass_fast), fail_fast_end: Activity.End(:fail_fast), **builder_options)
|
|
85
85
|
path_adds = Railway.InitialAdds(**builder_options)
|
|
86
86
|
|
|
87
87
|
ends =
|
|
@@ -28,7 +28,7 @@ module Trailblazer
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
# @return [Adds] list of Adds instances that can be chained or added to an existing sequence.
|
|
31
|
-
def self.InitialAdds(track_color:raise, end_semantic:raise, default_plus_poles: self.default_plus_poles, track_end: Activity.End(
|
|
31
|
+
def self.InitialAdds(track_color:raise, end_semantic:raise, default_plus_poles: self.default_plus_poles, track_end: Activity.End(end_semantic), **)
|
|
32
32
|
|
|
33
33
|
builder_options={ track_color: track_color, end_semantic: end_semantic }
|
|
34
34
|
|
|
@@ -31,7 +31,7 @@ module Trailblazer
|
|
|
31
31
|
|
|
32
32
|
# Adds the End.failure end to the Path sequence.
|
|
33
33
|
# @return [Adds] list of Adds instances that can be chained or added to an existing sequence.
|
|
34
|
-
def self.InitialAdds(failure_color:raise, failure_end: Activity.End(failure_color
|
|
34
|
+
def self.InitialAdds(failure_color:raise, failure_end: Activity.End(failure_color), **builder_options)
|
|
35
35
|
path_adds = Path.InitialAdds(**builder_options)
|
|
36
36
|
|
|
37
37
|
end_adds = adds(
|
|
@@ -45,7 +45,7 @@ module Trailblazer
|
|
|
45
45
|
def self.recompile_outputs(end_events)
|
|
46
46
|
ary = end_events.collect do |evt|
|
|
47
47
|
[
|
|
48
|
-
semantic = evt.
|
|
48
|
+
semantic = evt.to_h[:semantic],
|
|
49
49
|
Activity::Output(evt, semantic)
|
|
50
50
|
]
|
|
51
51
|
end
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
module Trailblazer
|
|
2
2
|
class Activity < Module # End event is just another callable task.
|
|
3
|
+
|
|
3
4
|
# Any instance of subclass of End will halt the circuit's execution when hit.
|
|
4
|
-
class End
|
|
5
|
-
def initialize(name, options={})
|
|
6
|
-
@name = name
|
|
7
|
-
@options = options
|
|
8
|
-
end
|
|
9
5
|
|
|
6
|
+
# An End event is a simple structure typically found as the last task invoked
|
|
7
|
+
# in an activity. The special behavior is that it
|
|
8
|
+
# a) maintains a semantic that is used to further connect that very event
|
|
9
|
+
# b) its `End#call` method returns the end instance itself as the signal.
|
|
10
|
+
End = Struct.new(:semantic) do
|
|
10
11
|
def call(*args)
|
|
11
|
-
|
|
12
|
+
return self, *args
|
|
12
13
|
end
|
|
13
14
|
end
|
|
14
15
|
|
|
@@ -18,9 +19,9 @@
|
|
|
18
19
|
end
|
|
19
20
|
end
|
|
20
21
|
|
|
21
|
-
#
|
|
22
|
-
def self.End(
|
|
23
|
-
Activity::End.new(
|
|
22
|
+
# Builds an Activity::End instance.
|
|
23
|
+
def self.End(semantic)
|
|
24
|
+
Activity::End.new(semantic)
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
class Signal; end
|
data/lib/trailblazer/circuit.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: trailblazer-activity
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nick Sutterer
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2018-01-
|
|
11
|
+
date: 2018-01-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: hirb
|