trailblazer-operation 0.0.13 → 0.1.1

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