trailblazer-activity 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|