trailblazer-operation 0.0.13 → 0.1.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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -3
  3. data/CHANGES.md +44 -0
  4. data/Gemfile +13 -2
  5. data/Rakefile +8 -6
  6. data/lib/trailblazer/operation.rb +86 -12
  7. data/lib/trailblazer/operation/deprecated_macro.rb +19 -0
  8. data/lib/trailblazer/operation/inject.rb +34 -0
  9. data/lib/trailblazer/operation/inspect.rb +80 -0
  10. data/lib/trailblazer/operation/public_call.rb +62 -0
  11. data/lib/trailblazer/operation/railway.rb +32 -0
  12. data/lib/trailblazer/operation/railway/fast_track.rb +13 -0
  13. data/lib/trailblazer/operation/railway/normalizer.rb +73 -0
  14. data/lib/trailblazer/operation/railway/task_builder.rb +44 -0
  15. data/lib/trailblazer/operation/result.rb +6 -4
  16. data/lib/trailblazer/operation/skill.rb +8 -24
  17. data/lib/trailblazer/operation/task_wrap.rb +68 -0
  18. data/lib/trailblazer/operation/trace.rb +49 -0
  19. data/lib/trailblazer/operation/variable_mapping.rb +91 -0
  20. data/lib/trailblazer/operation/version.rb +1 -1
  21. data/test/call_test.rb +27 -8
  22. data/test/class_dependencies_test.rb +16 -0
  23. data/test/docs/doormat_test.rb +189 -0
  24. data/test/docs/wiring_test.rb +421 -0
  25. data/test/dry_container_test.rb +4 -0
  26. data/test/fast_track_test.rb +197 -0
  27. data/test/gemfiles/Gemfile.ruby-2.0 +1 -2
  28. data/test/gemfiles/Gemfile.ruby-2.0.lock +40 -0
  29. data/test/inheritance_test.rb +1 -1
  30. data/test/inspect_test.rb +43 -0
  31. data/test/introspect_test.rb +50 -0
  32. data/test/macro_test.rb +61 -0
  33. data/test/operation_test.rb +94 -0
  34. data/test/result_test.rb +14 -8
  35. data/test/ruby-2.0.0/operation_test.rb +73 -0
  36. data/test/ruby-2.0.0/step_test.rb +136 -0
  37. data/test/skill_test.rb +66 -48
  38. data/test/step_test.rb +228 -0
  39. data/test/task_wrap_test.rb +91 -0
  40. data/test/test_helper.rb +37 -0
  41. data/test/trace_test.rb +62 -0
  42. data/test/variable_mapping_test.rb +66 -0
  43. data/test/wire_test.rb +113 -0
  44. data/test/wiring/defaults_test.rb +197 -0
  45. data/test/wiring/subprocess_test.rb +70 -0
  46. data/trailblazer-operation.gemspec +3 -5
  47. metadata +62 -36
  48. data/lib/trailblazer/operation/1.9.3/option.rb +0 -36
  49. data/lib/trailblazer/operation/generic.rb +0 -12
  50. data/lib/trailblazer/operation/option.rb +0 -54
  51. data/lib/trailblazer/operation/pipetree.rb +0 -142
  52. data/lib/trailblazer/skill.rb +0 -70
  53. data/test/2.0.0-pipetree_test.rb +0 -100
  54. data/test/2.1.0-pipetree_test.rb +0 -100
  55. data/test/operation_skill_test.rb +0 -89
  56. data/test/pipetree_test.rb +0 -185
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c4a0c7253d802e980c74cd74a538803a76fee4a4
4
- data.tar.gz: 0e44522d1373cfa7733fdc5591bad5df8b94dd57
3
+ metadata.gz: e11de8247c38298cb548d31fb21ca138949204da
4
+ data.tar.gz: fd3a2a39355bc6e064d174e098d542f69e8f0fbc
5
5
  SHA512:
6
- metadata.gz: 1a6187f6f92373ed7dc908ca819ff8b72503e3dfdf72addc8aa2c3140c05a5502d241e8109482fe51e3ea34f6aaa012f3c8751df898d3eb7aac4ec62641a0a23
7
- data.tar.gz: 7486af11d465c14f21506bcf48201d2b917766e9906e72c610d14f6d6598c1a498a7558f578c37f562def2cbe0b7929e6313d5714e584f63a048153be7770e90
6
+ metadata.gz: 2b65963cfacea4a758ee64ede04d7c67a6e84433503d0278ebf8e163509205c1a75360fad353a76a271eb2392f5bdeecba66faebe6d78438b6c9b9d1eda36834
7
+ data.tar.gz: e8edfcc04c39863635ee3123e90c9ea1a024240b61b54ea3356906a687c62a4929041949dad345f9e214ef104b4a6060d655d4461ecff637966d73c495e676a5
data/.travis.yml CHANGED
@@ -3,13 +3,15 @@ before_install:
3
3
  - gem install bundler
4
4
  matrix:
5
5
  include:
6
- - rvm: 1.9.3
7
- gemfile: "test/gemfiles/Gemfile.ruby-1.9"
8
6
  # - rvm: 2.0.0
9
7
  # gemfile: "test/gemfiles/Gemfile.ruby-2.0"
10
8
  - rvm: 2.1
11
9
  gemfile: Gemfile
12
10
  - rvm: 2.2.4
13
11
  gemfile: Gemfile
14
- - rvm: 2.3.1
12
+ - rvm: 2.3.3
15
13
  gemfile: Gemfile
14
+ - rvm: 2.4.0
15
+ gemfile: Gemfile
16
+ - rvm: jruby-9.1.13.0
17
+ env: JRUBY_OPTS="--profile.api"
data/CHANGES.md CHANGED
@@ -1,3 +1,47 @@
1
+ TODO:
2
+ * api to add your own task.
3
+
4
+ lots of work on the DSL specific parts.
5
+ Graph and Sequence to make it easier to wire anything.
6
+ macros can now add/modify the wiring, e.g. their end to the our end or the next task.
7
+ [ use circuit for actual step_args/initialize process, too? ]
8
+ You can now add an unlimited number of "your own" end events, which can then be interpreted on the outside (e.g. Endpoint)
9
+ * Introduced the `fast_track: true` option for steps. If you were returning `Railway.fail_fast!` and the like, you now need to declare that option, e.g.
10
+
11
+ ```ruby
12
+ step :my_validate!, fast_track: true
13
+ ```
14
+
15
+ params:, rest: ..
16
+
17
+ ## 0.1.0
18
+
19
+ inspect: failure is << and success is >>
20
+
21
+ call vs __call__: it's now designed to be run in a composition where the skills stuff is done only once, and the reslt object is not necessary
22
+
23
+ FastTrack optional
24
+ Wrapped optional
25
+
26
+ * Add `pass` and `fail` as four-character aliases for `success` and `failure`.
27
+ * Remove `Uber::Callable` requirement and treat all non-`:symbol` steps as callable objects.
28
+ * Remove non-kw options for steps. All steps receive keyword args now:
29
+
30
+ ```ruby
31
+ def model(options)
32
+ ```
33
+
34
+ now must have a minimal signature as follows.
35
+
36
+ ```ruby
37
+ def model(options, **)
38
+ ```
39
+ * Remove `Operation#[]` and `Operation#[]=`. Please only change state in `options`.
40
+ * API change for `step Macro()`: the macro's return value is now called with the low-level "Task API" signature `(direction, options, flow_options)`. You need to return `[direction, options, flow_options]`. There's a soft-deprecation warning.
41
+ * Remove support for Ruby 1.9.3 for now. This can be re-introduced on demand.
42
+ * Remove `pipetree` in favor of [`trailblazer-circuit`](https://github.com/trailblazer/trailblazer-circuit). This allows rich workflows and state machines in an operation.
43
+ * Remove `uber` dependency.
44
+
1
45
  ## 0.0.13
2
46
 
3
47
  * Rename `Operation::New` to `:Instantiate` to avoid name clashes with `New` operations in applications.
data/Gemfile CHANGED
@@ -9,5 +9,16 @@ gem "dry-auto_inject"
9
9
 
10
10
  gem "minitest-line"
11
11
  gem "benchmark-ips"
12
- # gem "pipetree", path: "../pipetree"
13
- # gem "pipetree", github: "apotonick/pipetree"
12
+
13
+ # gem "trailblazer-circuit", git: "https://github.com/trailblazer/trailblazer-circuit"
14
+ # gem "trailblazer-activity", path: "../trailblazer-activity"
15
+ # gem "trailblazer-developer", path: "../developer"
16
+ gem "trailblazer-developer", git: "https://github.com/trailblazer/trailblazer-developer"
17
+ # gem "representable", path: "../representable"
18
+
19
+ # gem "raise", path: "../raise"
20
+
21
+ # gem "declarative", path: "../declarative"
22
+
23
+ gem "trailblazer-activity", path: "../circuit"
24
+ # gem "trailblazer-activity", git: "https://github.com/trailblazer/trailblazer-activity"
data/Rakefile CHANGED
@@ -4,15 +4,17 @@ require "rake/testtask"
4
4
  task :default => [:test]
5
5
 
6
6
  Rake::TestTask.new(:test) do |test|
7
- test.libs << 'test'
7
+ test.libs << "test"
8
8
  test.verbose = true
9
9
 
10
- test_files = FileList['test/*_test.rb']
10
+ test_files = FileList["test/**/*_test.rb"]
11
11
 
12
- if RUBY_VERSION == "1.9.3"
13
- test_files = test_files - %w{test/dry_container_test.rb test/2.1.0-pipetree_test.rb test/2.0.0-pipetree_test.rb}
14
- elsif RUBY_VERSION == "2.0.0"
15
- test_files = test_files - %w{test/dry_container_test.rb test/2.1.0-pipetree_test.rb}
12
+ if RUBY_VERSION == "2.0.0"
13
+ # test_files = test_files - %w{test/dry_container_test.rb test/2.1.0-pipetree_test.rb}
14
+ test_files = test_files - %w{test/step_test.rb} + %w{test/ruby-2.0.0/step_test.rb}
15
+ test_files = test_files - %w{test/operation_test.rb} + %w{test/ruby-2.0.0/operation_test.rb}
16
+ else
17
+ test_files -= FileList["test/ruby-2.0.0/*"]
16
18
  end
17
19
 
18
20
  test.test_files = test_files
@@ -1,24 +1,98 @@
1
1
  require "forwardable"
2
- require "declarative"
2
+
3
+ # trailblazer-context
4
+ require "trailblazer/option"
5
+ require "trailblazer/context"
6
+ require "trailblazer/container_chain"
7
+
8
+ require "trailblazer/activity"
9
+ require "trailblazer/activity/magnetic"
10
+ require "trailblazer/activity/wrap"
11
+
12
+ require "trailblazer/operation/variable_mapping"
13
+
14
+ require "trailblazer/operation/public_call" # TODO: Remove in 3.0.
3
15
  require "trailblazer/operation/skill"
4
- require "trailblazer/operation/pipetree"
5
- require "trailblazer/operation/generic"
16
+ require "trailblazer/operation/deprecated_macro" # TODO: remove in 2.2.
17
+ require "trailblazer/operation/result"
18
+ require "trailblazer/operation/railway"
19
+
20
+ require "trailblazer/operation/railway/task_builder"
21
+ require "trailblazer/operation/railway/fast_track"
22
+ require "trailblazer/operation/railway/normalizer"
23
+ require "trailblazer/operation/task_wrap"
24
+ require "trailblazer/operation/trace"
6
25
 
7
26
  module Trailblazer
8
27
  # The Trailblazer-style operation.
9
28
  # Note that you don't have to use our "opinionated" version with result object, skills, etc.
10
29
  class Operation
11
- extend Declarative::Heritage::Inherited
12
- extend Declarative::Heritage::DSL
30
+ extend Skill::Accessors # ::[] and ::[]= # TODO: fade out this usage.
31
+
32
+ def self.inherited(subclass)
33
+ super
34
+ subclass.initialize!
35
+ heritage.(subclass)
36
+ end
37
+
38
+ module Process
39
+ def initialize!
40
+ initialize_activity_dsl!
41
+ recompile_process!
42
+ end
43
+
44
+ # builder is stateless, it's up to you to save @adds somewhere.
45
+ def initialize_activity_dsl!
46
+ builder_options = {
47
+ track_end: Railway::End::Success.new(:success, semantic: :success),
48
+ failure_end: Railway::End::Failure.new(:failure, semantic: :failure),
49
+ pass_fast_end: Railway::End::PassFast.new(:pass_fast, semantic: :pass_fast),
50
+ fail_fast_end: Railway::End::FailFast.new(:fail_fast, semantic: :fail_fast),
51
+ }
52
+
53
+ @builder, @adds = Activity::Magnetic::Builder::FastTrack.for( Railway::Normalizer, builder_options )
54
+ @debug = {}
55
+ end
13
56
 
14
- extend Skill::Accessors # ::[] and ::[]=
57
+ def recompile_process!
58
+ @process, @outputs = Activity::Recompile.( @adds )
59
+ end
15
60
 
16
- include Pipetree # ::call, ::step, ...
17
- # we want the skill dependency-mechanism.
18
- extend Skill::Call # ::call(params: {}, current_user: ..)
19
- extend Skill::Call::Positional # ::call(params, options)
61
+ def outputs
62
+ @outputs
63
+ end
20
64
 
21
- # we want the initializer and the ::call method.
22
- include Generic # #initialize, #call, #process.
65
+ include Activity::Interface
66
+
67
+ # Call the actual {Process} with the options prepared in PublicCall.
68
+ def __call__(args, circuit_options={})
69
+ @process.( args, circuit_options.merge( exec_context: new ) )
70
+ end
71
+ end
72
+
73
+ extend Process # make ::call etc. class methods on Operation.
74
+
75
+ extend Activity::Heritage::Accessor
76
+
77
+ extend Activity::DSL # #_task
78
+ # delegate step, pass and fail via Operation::_task to the @builder, and save results in @adds.
79
+ extend Activity::DSL.def_dsl! :step
80
+ extend Activity::DSL.def_dsl! :pass
81
+ extend Activity::DSL.def_dsl! :fail
82
+ class << self
83
+ alias_method :success, :pass
84
+ alias_method :failure, :fail
85
+
86
+ extend Forwardable # TODO: test those helpers
87
+ def_delegators :@builder, :Path, :Output, :End #, :task
88
+ end
89
+
90
+ extend PublicCall # ::call(params, { current_user: .. })
91
+ extend Trace # ::trace
92
+
93
+
94
+ include Railway::TaskWrap
23
95
  end
24
96
  end
97
+
98
+ require "trailblazer/operation/inspect"
@@ -0,0 +1,19 @@
1
+ # TODO: REMOVE IN 2.2.
2
+ module Trailblazer
3
+ module Operation::DeprecatedMacro
4
+ # Allows old macros with the `(input, options)` signature.
5
+ def self.call(proc, options={})
6
+ warn %{[Trailblazer] Macros with API (input, options) are deprecated. Please use the "Task API" signature (options, flow_options) or use a simpler Callable. (#{proc})}
7
+
8
+ wrapped_proc = ->( (options, flow_options), **circuit_options ) do
9
+ result = proc.(circuit_options[:exec_context], options) # run the macro, with the deprecated signature.
10
+
11
+ direction = Operation::Railway::TaskBuilder.binary_direction_for(result, Activity::Right, Activity::Left)
12
+
13
+ return direction, [options, flow_options]
14
+ end
15
+
16
+ return wrapped_proc, options
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,34 @@
1
+ module Trailblazer
2
+ module Operation::Wrap
3
+ module Inject
4
+ # Returns an Alteration wirings that, when applied, inserts the {ReverseMergeDefaults} task
5
+ # before the {Wrap::Call} task. This is meant for macros and steps that accept a dependency
6
+ # injection but need a default parameter to be set if not injected.
7
+ # @returns ADDS
8
+ def self.Defaults(default_dependencies)
9
+ Activity::Magnetic::Builder::Path.plan do
10
+ task ReverseMergeDefaults.new( default_dependencies ),
11
+ id: "ReverseMergeDefaults#{default_dependencies}",
12
+ before: "task_wrap.call_task"
13
+ end
14
+ end
15
+
16
+ # @api private
17
+ # @returns Task
18
+ # @param Hash list of key/value that should be set if not already assigned/set before (or injected from the outside).
19
+ class ReverseMergeDefaults
20
+ def initialize(defaults)
21
+ @defaults = defaults
22
+ end
23
+
24
+ def call((wrap_ctx, original_args), **circuit_options)
25
+ ctx = original_args[0][0]
26
+
27
+ @defaults.each { |k, v| ctx[k] ||= v }
28
+
29
+ return Activity::Right, [ wrap_ctx, original_args ]
30
+ end
31
+ end
32
+ end # Inject
33
+ end
34
+ end
@@ -0,0 +1,80 @@
1
+ module Trailblazer
2
+ # Operation-specific circuit rendering. This is optimized for a linear railway circuit.
3
+ #
4
+ # @private
5
+ #
6
+ # NOTE: this is absolutely to be considered as prototyping and acts more like a test helper ATM as
7
+ # Inspect is not a mission-critical part.
8
+ class Operation
9
+ def self.introspect(*args)
10
+ Operation::Inspect.(*args)
11
+ end
12
+ end
13
+
14
+ module Operation::Inspect
15
+ module_function
16
+
17
+ def call(operation, options={ style: :line })
18
+ # TODO: better introspection API.
19
+
20
+ adds = operation.instance_variable_get(:@adds)
21
+ alterations = Activity::Magnetic::Builder::Finalizer.adds_to_alterations(adds)
22
+ # DISCUSS: any other way to retrieve the Alterations?
23
+
24
+ # pp alterations
25
+ railway = alterations.instance_variable_get(:@groups).instance_variable_get(:@groups)[:main]
26
+
27
+ rows = railway.each_with_index.collect do |element, i|
28
+ magnetic_to, task, plus_poles = element.configuration
29
+
30
+ created_by =
31
+ if magnetic_to == [:failure]
32
+ :fail
33
+ elsif plus_poles.size > 1
34
+ plus_poles[0].color == plus_poles[1].color ? :pass : :step
35
+ else
36
+ :pass # this is wrong for Nested, sometimes
37
+ end
38
+
39
+ [ i, [ created_by, element.id ] ]
40
+ end
41
+
42
+ return inspect_line(rows) if options[:style] == :line
43
+ return inspect_rows(rows)
44
+ end
45
+
46
+ def inspect_func(step)
47
+ @inspect[step]
48
+ end
49
+
50
+ Operator = { :fail => "<<", :pass => ">>", :step => ">"}
51
+
52
+ def inspect_line(names)
53
+ string = names.collect { |i, (end_of_edge, name)| "#{Operator[end_of_edge]}#{name}" }.join(",")
54
+ "[#{string}]"
55
+ end
56
+
57
+ def inspect_rows(names)
58
+ string = names.collect do |i, (end_of_edge, name)|
59
+ operator = Operator[end_of_edge]
60
+
61
+ op = "#{operator}#{name}"
62
+ padding = 38
63
+
64
+ proc = if operator == "<<"
65
+ sprintf("%- #{padding}s", op)
66
+ elsif [">", ">>", "&"].include?(operator.to_s)
67
+ sprintf("% #{padding}s", op)
68
+ else
69
+ pad = " " * ((padding - op.length) / 2)
70
+ "#{pad}#{op}#{pad}"
71
+ end
72
+
73
+ proc = proc.gsub(" ", "=")
74
+
75
+ sprintf("%2d %s", i, proc)
76
+ end.join("\n")
77
+ "\n#{string}"
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,62 @@
1
+ class Trailblazer::Operation
2
+ module PublicCall
3
+ # This is the outer-most public `call` method that gets invoked when calling `Create.()`.
4
+ # The signature of this is `params, options, *containers`. This was a mistake, as the
5
+ # first argument could've been part of `options` hash in the first place.
6
+ #
7
+ # Create.(params, runtime_data, *containers)
8
+ # #=> Result<Context...>
9
+ #
10
+ # In workflows/Nested compositions, this method is not used anymore and it might probably
11
+ # get removed in future versions of TRB. Currently, we use Operation::__call__ as an alternative.
12
+ #
13
+ # @return Operation::Railway::Result binary result object
14
+ def call(*args)
15
+ ctx = PublicCall.options_for_public_call(*args)
16
+
17
+ # call the activity.
18
+ last_signal, (options, flow_options) = __call__( [ctx, {}] ) # Railway::call # DISCUSS: this could be ::call_with_context.
19
+
20
+ # Result is successful if the activity ended with an End event derived from Railway::End::Success.
21
+ Railway::Result(last_signal, options, flow_options)
22
+ end
23
+
24
+ private
25
+ # Compile a Context object to be passed into the Activity::call.
26
+ def self.options_for_public_call(options={}, *containers)
27
+ # options, *containers = Deprecations.accept_positional_options(params, options, *containers) # TODO: make this optional for "power users".
28
+
29
+ # generate the skill hash that embraces runtime options plus potential containers, the so called Runtime options.
30
+ # This wrapping is supposed to happen once in the entire system.
31
+
32
+ hash_transformer = ->(containers) { containers[0].to_hash } # FIXME: don't transform any containers into kw args.
33
+
34
+ immutable_options = Trailblazer::Context::ContainerChain.new( [options, *containers], to_hash: hash_transformer ) # Runtime options, immutable.
35
+
36
+ ctx = Trailblazer::Context(immutable_options)
37
+ end
38
+
39
+ module Deprecations
40
+ # Merge the first argument to the public Create.() into the second.
41
+ #
42
+ # DISCUSS: this is experimental since we can never know whether the call is the old or new API.
43
+ #
44
+ # The following situations are _not_ covered here:
45
+ # * You're using a Hash instance as a container.
46
+ # * You're using more than one container.
47
+ #
48
+ # If you do so (we're assuming you know what you're doing then), please update your `call`s.
49
+ def self.accept_positional_options( *args )
50
+ if args.size == 1 && args[0].instance_of?(Hash) # new style, you're doing it right.
51
+ args
52
+ elsif args.size == 2 && args[0].instance_of?(Hash) && !args[1].instance_of?(Hash) # new style with container, you're doing it right.
53
+ args
54
+ else
55
+ warn "[Trailblazer] Passing two positional arguments to `Operation.( params, current_user: .. )` is deprecated. Please use one hash like `Operation.( params: params, current_user: .. )`"
56
+ params, options, *containers = args
57
+ [ options.merge("params" => params), *containers ]
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,32 @@
1
+ module Trailblazer
2
+ # Operations is simply a thin API to define, inherit and run circuits by passing the options object.
3
+ # It encourages the linear railway style (http://trb.to/gems/workflow/circuit.html#operation) but can
4
+ # easily be extend for more complex workflows.
5
+ class Operation
6
+ # End event: All subclasses of End:::Success are interpreted as "success".
7
+ module Railway
8
+ # @param options Context
9
+ # @param end_event The last emitted signal in a circuit is usually the end event.
10
+ def self.Result(end_event, options, *)
11
+ Result.new(end_event.kind_of?(End::Success), options, end_event)
12
+ end
13
+
14
+ # The Railway::Result knows about its binary state, the context (data), and the last event in the circuit.
15
+ class Result < Result # Operation::Result
16
+ def initialize(success, data, event)
17
+ super(success, data)
18
+
19
+ @event = event
20
+ end
21
+
22
+ attr_reader :event
23
+ end
24
+
25
+ module End
26
+ class Success < Activity::End; end
27
+ class Failure < Activity::End; end
28
+ end
29
+
30
+ end # Railway
31
+ end
32
+ end