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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 642864a6553c58d2a746291414cc523eadb23fa3e661f100a6f556859fce88f5
4
- data.tar.gz: 04b9cae767f24c32680b4a30cc0e6d1e23449f3ff9a3b665e4a6dbb000b3f35d
3
+ metadata.gz: b2666e0f5190759b775c83c2cd4d04c87d64b230e71e4921a01c6656fd27d5c8
4
+ data.tar.gz: b16fe2deab89048538e6351aabd5b274a55f378e0dc081ea3e3519586a2146be
5
5
  SHA512:
6
- metadata.gz: cee010776ef741794a746dde08b8a691ed60494ad7664a9aa1c7aa136f0e3344031063c45275f81de5f53b61a10d17d050b8985ffdefa7a7522b8bf348e9ea91
7
- data.tar.gz: 3716026cd5f591346757a3b22b40318e227e63a1b206f5cd93f2c7666e9231c7ba6a6913f6a5a8baaf5a2013f2a192033dff13b140e1fae67a9dfbc9e4285ed1
6
+ metadata.gz: 3c100c03b31cd3abf3d8c60b16d6e710202c9503e74aadd2f70d8c41ae33a48fcc92b86e862e58c0609fdcb9169ab1dcd3e4a08aa933a78412b397dccbd49591
7
+ data.tar.gz: 73a7aa9fec238f54c07b888038e252948fa3c4db76183b37f6848421c53e3c2a31aab4bb235889517edc8ec4d3b055d1855c475811a4bf692d9653776d9979f1
data/CHANGES.md CHANGED
@@ -1,3 +1,8 @@
1
+ # 0.4.1
2
+
3
+ * Remove `decompose` and replace it with a better `to_h`.
4
+ * `End` doesn't have a redundant `@name` anymore but only a semantic.
5
+
1
6
  # 0.4.0
2
7
 
3
8
  * We now use the "Module Subclass" pattern, and activities aren't classes anymore but modules.
data/README.md CHANGED
@@ -1,109 +1,79 @@
1
1
  # Activity
2
2
 
3
- An _activity_ is a collection of connected _tasks_ with one _start event_ and one (or many) _end_ events.
3
+ The `activity` gem brings a light-weight DSL to define business processes, and the runtime logic to run those activities.
4
4
 
5
- ## Installation
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
- To use activities you need one gem, only.
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
- ```ruby
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
- An `Activity` allows to define and maintain a graph, that at runtime will be used as a "circuit". Or, in other words, it defines the boxes, circles, arrows and signals between them, and makes sure when running the activity, the circuit with your rules will be executed.
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
- class Create < Trailblazer::Operation
43
- step :exists?, pass_fast: true
44
- step :policy
45
- step :validate
46
- fail :log_err
47
- step :persist
48
- fail :log_db_err
49
- step :notify
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
- Check the operation above. The DSL to create the activity with its graph is very different to `Activity`, but the outcome is a simple activity instance.
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
- To understand how an activity works and what it performs in your application logic, it's easiest to see how activities are defined, and used.
49
+ <img src="http://trailblazer.to/images/2.1/activity-readme-example.png">
62
50
 
63
- ## Activity: From_Hash
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
- activity = Activity.from_hash do |start, _end|
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
- The block yields a generic start and end event instance. You then connect every _task_ in that hash (hash keys) to another task or event via the emitted _signal_.
58
+ pp ctx #=>
59
+ {:id=>1,
60
+ :params=>{:body=>"Awesome!"},
61
+ :model=>#<Memo body=nil>,
62
+ :errors=>"body not long enough"}
82
63
 
83
- ## Activity: Call
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
- 1. The `start` event is `call`ed and per default returns the generic _signal_`Trailblazer::Activity::Right`.
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
- Note how signals translate to edges (or connections) in the graph, and tasks become vertices (or nodes).
69
+ ## Features
104
70
 
105
- The return values are the `last_signal`, which is usually the end event (they return themselves as a signal), the last `options` that usually contains all kinds of data from running the activity, and additional args.
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
- ## More
77
+ ## Operation
108
78
 
109
- The [full documentation](http://trb.to/gems/activity/0.2/api.html) for this gem and many more interesting things can be found on the Trailblazer website.
79
+ Trailblazer's [`Operation`](http://trailblazer.to/2.1/operation) internally uses an activity to model the processes.
data/Rakefile CHANGED
@@ -5,7 +5,7 @@ require "rubocop/rake_task"
5
5
  Rake::TestTask.new(:test) do |t|
6
6
  t.libs << "test"
7
7
  t.libs << "lib"
8
- t.test_files = FileList['test/dsl/*_test.rb', 'test/wrap/*_test.rb', "test/*_test.rb"]
8
+ t.test_files = FileList["**/*_test.rb"]
9
9
  end
10
10
 
11
11
  RuboCop::RakeTask.new
@@ -24,11 +24,11 @@ module Trailblazer
24
24
  Activity.Output(signal, semantic)
25
25
  end
26
26
 
27
- def End(name, semantic)
28
- Activity.End(name, semantic)
27
+ def End(semantic)
28
+ Activity.End(semantic)
29
29
  end
30
30
 
31
- def Path(normalizer, track_color: "track_#{rand}", end_semantic: :success, **options)
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 decompose # TODO: test me
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
- decompose[:debug]
9
+ to_h[:debug]
10
10
  end
11
11
 
12
12
  def outputs
13
- decompose[:outputs]
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.decompose[:circuit]
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 = activity.decompose[:circuit]
27
- circuit_hash, _ = circuit.decompose
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.decompose[0], **options )
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.decompose[1]
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
- name = task.instance_variable_get(:@name)
78
- semantic = task.instance_variable_get(:@options)[:semantic]
79
- "#<#{class_name}:#{name}/#{semantic.inspect}>"
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("pass_fast", :pass_fast), fail_fast_end: Activity.End("fail_fast", :fail_fast), **builder_options)
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(track_color, end_semantic), **)
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, :failure), **builder_options)
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(
@@ -61,7 +61,7 @@ module Trailblazer
61
61
  elsif task.is_a?(Proc)
62
62
  start_color, activity = task.(block)
63
63
 
64
- adds = activity.decompose[:adds]
64
+ adds = activity.to_h[:adds]
65
65
 
66
66
  [
67
67
  Polarization.new( output: output, color: start_color ),
@@ -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.instance_variable_get(:@options)[:semantic], # DISCUSS: better API here?
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
- [ self, *args ]
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
- # Builder for Activity::End.
22
- def self.End(name, semantic=name)
23
- Activity::End.new(name, semantic: semantic)
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
@@ -21,8 +21,8 @@ module Trailblazer
21
21
  end
22
22
 
23
23
  # @private
24
- def decompose
25
- @activity.decompose # TODO: test explicitly
24
+ def to_h
25
+ @activity.to_h # TODO: test explicitly
26
26
  end
27
27
 
28
28
  def debug
@@ -1,5 +1,5 @@
1
1
  module Trailblazer
2
2
  class Activity < Module
3
- VERSION = "0.4.0"
3
+ VERSION = "0.4.1"
4
4
  end
5
5
  end
@@ -53,8 +53,8 @@ module Trailblazer
53
53
  end
54
54
 
55
55
  # Returns the circuit's components.
56
- def decompose
57
- return @map, @stop_events
56
+ def to_h
57
+ { map: @map, end_events: @stop_events }
58
58
  end
59
59
 
60
60
  private
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.0
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-23 00:00:00.000000000 Z
11
+ date: 2018-01-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hirb