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 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