trailblazer-future 2.1.0.rc1

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 (161) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.travis.yml +6 -0
  4. data/CHANGES.md +4 -0
  5. data/LICENSE.txt +9 -0
  6. data/README.md +48 -0
  7. data/Rakefile +10 -0
  8. data/bin/console +14 -0
  9. data/bin/setup +8 -0
  10. data/gems.rb +3 -0
  11. data/lib/trailblazer/future.rb +9 -0
  12. data/lib/trailblazer/future/version.rb +5 -0
  13. data/lib/trailblazer/v2_1/activity.rb +123 -0
  14. data/lib/trailblazer/v2_1/activity/config.rb +37 -0
  15. data/lib/trailblazer/v2_1/activity/dsl/add_task.rb +22 -0
  16. data/lib/trailblazer/v2_1/activity/dsl/helper.rb +68 -0
  17. data/lib/trailblazer/v2_1/activity/dsl/magnetic.rb +36 -0
  18. data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder.rb +101 -0
  19. data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/default_normalizer.rb +26 -0
  20. data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/fast_track.rb +118 -0
  21. data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/normalizer.rb +113 -0
  22. data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/path.rb +105 -0
  23. data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/railway.rb +97 -0
  24. data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/state.rb +58 -0
  25. data/lib/trailblazer/v2_1/activity/dsl/magnetic/finalizer.rb +51 -0
  26. data/lib/trailblazer/v2_1/activity/dsl/magnetic/generate.rb +62 -0
  27. data/lib/trailblazer/v2_1/activity/dsl/magnetic/merge.rb +16 -0
  28. data/lib/trailblazer/v2_1/activity/dsl/magnetic/process_options.rb +76 -0
  29. data/lib/trailblazer/v2_1/activity/dsl/magnetic/structure/alterations.rb +44 -0
  30. data/lib/trailblazer/v2_1/activity/dsl/magnetic/structure/plus_poles.rb +85 -0
  31. data/lib/trailblazer/v2_1/activity/dsl/magnetic/structure/polarization.rb +23 -0
  32. data/lib/trailblazer/v2_1/activity/dsl/record.rb +11 -0
  33. data/lib/trailblazer/v2_1/activity/dsl/schema/dependencies.rb +46 -0
  34. data/lib/trailblazer/v2_1/activity/dsl/schema/sequence.rb +46 -0
  35. data/lib/trailblazer/v2_1/activity/dsl/strategy/build_state.rb +32 -0
  36. data/lib/trailblazer/v2_1/activity/dsl/strategy/fast_track.rb +24 -0
  37. data/lib/trailblazer/v2_1/activity/dsl/strategy/path.rb +26 -0
  38. data/lib/trailblazer/v2_1/activity/dsl/strategy/plan.rb +36 -0
  39. data/lib/trailblazer/v2_1/activity/dsl/strategy/railway.rb +23 -0
  40. data/lib/trailblazer/v2_1/activity/interface.rb +16 -0
  41. data/lib/trailblazer/v2_1/activity/introspect.rb +167 -0
  42. data/lib/trailblazer/v2_1/activity/present.rb +95 -0
  43. data/lib/trailblazer/v2_1/activity/structures.rb +57 -0
  44. data/lib/trailblazer/v2_1/activity/task_builder.rb +41 -0
  45. data/lib/trailblazer/v2_1/activity/task_wrap.rb +40 -0
  46. data/lib/trailblazer/v2_1/activity/task_wrap/call_task.rb +19 -0
  47. data/lib/trailblazer/v2_1/activity/task_wrap/merge.rb +23 -0
  48. data/lib/trailblazer/v2_1/activity/task_wrap/runner.rb +62 -0
  49. data/lib/trailblazer/v2_1/activity/task_wrap/trace.rb +44 -0
  50. data/lib/trailblazer/v2_1/activity/task_wrap/variable_mapping.rb +162 -0
  51. data/lib/trailblazer/v2_1/activity/testing.rb +32 -0
  52. data/lib/trailblazer/v2_1/activity/trace.rb +97 -0
  53. data/lib/trailblazer/v2_1/circuit.rb +71 -0
  54. data/lib/trailblazer/v2_1/container_chain.rb +45 -0
  55. data/lib/trailblazer/v2_1/context.rb +79 -0
  56. data/lib/trailblazer/v2_1/deprecation/call.rb +46 -0
  57. data/lib/trailblazer/v2_1/deprecation/context.rb +43 -0
  58. data/lib/trailblazer/v2_1/dsl.rb +47 -0
  59. data/lib/trailblazer/v2_1/macro.rb +11 -0
  60. data/lib/trailblazer/v2_1/macro/contract.rb +5 -0
  61. data/lib/trailblazer/v2_1/operation.rb +91 -0
  62. data/lib/trailblazer/v2_1/operation/auto_inject.rb +47 -0
  63. data/lib/trailblazer/v2_1/operation/callable.rb +42 -0
  64. data/lib/trailblazer/v2_1/operation/class_dependencies.rb +25 -0
  65. data/lib/trailblazer/v2_1/operation/contract.rb +61 -0
  66. data/lib/trailblazer/v2_1/operation/deprecated_macro.rb +19 -0
  67. data/lib/trailblazer/v2_1/operation/deprecations.rb +21 -0
  68. data/lib/trailblazer/v2_1/operation/guard.rb +18 -0
  69. data/lib/trailblazer/v2_1/operation/heritage.rb +30 -0
  70. data/lib/trailblazer/v2_1/operation/inject.rb +36 -0
  71. data/lib/trailblazer/v2_1/operation/inspect.rb +79 -0
  72. data/lib/trailblazer/v2_1/operation/model.rb +50 -0
  73. data/lib/trailblazer/v2_1/operation/module.rb +29 -0
  74. data/lib/trailblazer/v2_1/operation/nested.rb +98 -0
  75. data/lib/trailblazer/v2_1/operation/persist.rb +14 -0
  76. data/lib/trailblazer/v2_1/operation/policy.rb +44 -0
  77. data/lib/trailblazer/v2_1/operation/public_call.rb +55 -0
  78. data/lib/trailblazer/v2_1/operation/pundit.rb +38 -0
  79. data/lib/trailblazer/v2_1/operation/railway.rb +32 -0
  80. data/lib/trailblazer/v2_1/operation/railway/fast_track.rb +13 -0
  81. data/lib/trailblazer/v2_1/operation/railway/macaroni.rb +23 -0
  82. data/lib/trailblazer/v2_1/operation/railway/normalizer.rb +58 -0
  83. data/lib/trailblazer/v2_1/operation/railway/task_builder.rb +37 -0
  84. data/lib/trailblazer/v2_1/operation/rescue.rb +42 -0
  85. data/lib/trailblazer/v2_1/operation/responder.rb +18 -0
  86. data/lib/trailblazer/v2_1/operation/result.rb +30 -0
  87. data/lib/trailblazer/v2_1/operation/test.rb +17 -0
  88. data/lib/trailblazer/v2_1/operation/trace.rb +46 -0
  89. data/lib/trailblazer/v2_1/operation/validate.rb +76 -0
  90. data/lib/trailblazer/v2_1/operation/wrap.rb +83 -0
  91. data/lib/trailblazer/v2_1/option.rb +78 -0
  92. data/lib/trailblazer/v2_1/rails.rb +12 -0
  93. data/lib/trailblazer/v2_1/rails/cell.rb +22 -0
  94. data/lib/trailblazer/v2_1/rails/controller.rb +66 -0
  95. data/lib/trailblazer/v2_1/rails/form.rb +21 -0
  96. data/lib/trailblazer/v2_1/rails/railtie.rb +31 -0
  97. data/lib/trailblazer/v2_1/rails/railtie/extend_application_controller.rb +28 -0
  98. data/lib/trailblazer/v2_1/rails/railtie/loader.rb +58 -0
  99. data/lib/trailblazer/v2_1/rails/test/integration.rb +6 -0
  100. data/lib/trailblazer/v2_1/versions.txt +7 -0
  101. data/test/rails5.0/.gitignore +17 -0
  102. data/test/rails5.0/Gemfile +21 -0
  103. data/test/rails5.0/Rakefile +3 -0
  104. data/test/rails5.0/app/concepts/artist/cell/dashboard.rb +7 -0
  105. data/test/rails5.0/app/concepts/artist/cell/show.rb +4 -0
  106. data/test/rails5.0/app/concepts/artist/view/dashboard.erb +4 -0
  107. data/test/rails5.0/app/concepts/artist/view/show.erb +1 -0
  108. data/test/rails5.0/app/concepts/params/operation/with_args.rb +5 -0
  109. data/test/rails5.0/app/concepts/song/contract/form.rb +6 -0
  110. data/test/rails5.0/app/concepts/song/operation/create.rb +15 -0
  111. data/test/rails5.0/app/concepts/song/operation/show.rb +3 -0
  112. data/test/rails5.0/app/concepts/song/operation/update.rb +15 -0
  113. data/test/rails5.0/app/controllers/application_controller.rb +46 -0
  114. data/test/rails5.0/app/controllers/args_controller.rb +5 -0
  115. data/test/rails5.0/app/controllers/artists_controller.rb +24 -0
  116. data/test/rails5.0/app/controllers/params_controller.rb +11 -0
  117. data/test/rails5.0/app/controllers/songs_controller.rb +35 -0
  118. data/test/rails5.0/app/models/artist.rb +2 -0
  119. data/test/rails5.0/app/models/song.rb +2 -0
  120. data/test/rails5.0/app/views/args/with_args.html.erb +1 -0
  121. data/test/rails5.0/app/views/layouts/application.html.erb +2 -0
  122. data/test/rails5.0/app/views/songs/edit.html.erb +4 -0
  123. data/test/rails5.0/app/views/songs/new.html.erb +5 -0
  124. data/test/rails5.0/app/views/songs/new_with_result.html.erb +3 -0
  125. data/test/rails5.0/app/views/songs/show.html.erb +3 -0
  126. data/test/rails5.0/bin/bundle +3 -0
  127. data/test/rails5.0/bin/rails +4 -0
  128. data/test/rails5.0/bin/rake +4 -0
  129. data/test/rails5.0/bin/setup +29 -0
  130. data/test/rails5.0/config.ru +4 -0
  131. data/test/rails5.0/config/application.rb +25 -0
  132. data/test/rails5.0/config/boot.rb +3 -0
  133. data/test/rails5.0/config/database.yml +21 -0
  134. data/test/rails5.0/config/environment.rb +5 -0
  135. data/test/rails5.0/config/environments/development.rb +41 -0
  136. data/test/rails5.0/config/environments/test.rb +42 -0
  137. data/test/rails5.0/config/initializers/assets.rb +11 -0
  138. data/test/rails5.0/config/initializers/backtrace_silencers.rb +7 -0
  139. data/test/rails5.0/config/initializers/cookies_serializer.rb +3 -0
  140. data/test/rails5.0/config/initializers/filter_parameter_logging.rb +4 -0
  141. data/test/rails5.0/config/initializers/inflections.rb +16 -0
  142. data/test/rails5.0/config/initializers/mime_types.rb +4 -0
  143. data/test/rails5.0/config/initializers/session_store.rb +3 -0
  144. data/test/rails5.0/config/initializers/wrap_parameters.rb +14 -0
  145. data/test/rails5.0/config/locales/en.yml +23 -0
  146. data/test/rails5.0/config/routes.rb +15 -0
  147. data/test/rails5.0/config/secrets.yml +17 -0
  148. data/test/rails5.0/db/migrate/20161122145124_create_songs.rb +8 -0
  149. data/test/rails5.0/db/schema.rb +19 -0
  150. data/test/rails5.0/log/.keep +0 -0
  151. data/test/rails5.0/test/concepts/song/operation/create_test.rb +11 -0
  152. data/test/rails5.0/test/concepts/song/operation/update_test.rb +34 -0
  153. data/test/rails5.0/test/integration/.keep +0 -0
  154. data/test/rails5.0/test/integration/args_controller_test.rb +8 -0
  155. data/test/rails5.0/test/integration/artists_controller_test.rb +28 -0
  156. data/test/rails5.0/test/integration/params_controller_test.rb +8 -0
  157. data/test/rails5.0/test/integration/songs_controller_test.rb +40 -0
  158. data/test/rails5.0/test/test_helper.rb +7 -0
  159. data/test/test_helper.rb +5 -0
  160. data/trailblazer-future.gemspec +25 -0
  161. metadata +246 -0
@@ -0,0 +1,50 @@
1
+ class Trailblazer::V2_1::Operation
2
+ def self.Model(model_class, action=nil, find_by_key=nil)
3
+ task = Trailblazer::V2_1::Activity::TaskBuilder::Binary(Model.new)
4
+
5
+ extension = Trailblazer::V2_1::Activity::TaskWrap::Merge.new(
6
+ Wrap::Inject::Defaults(
7
+ "model.class" => model_class,
8
+ "model.action" => action,
9
+ "model.find_by_key" => find_by_key
10
+ )
11
+ )
12
+
13
+ { task: task, id: "model.build", Trailblazer::V2_1::Activity::DSL::Extension.new(extension) => true }
14
+ end
15
+
16
+ class Model
17
+ def call(options, params:, **)
18
+ builder = Model::Builder.new
19
+ options[:model] = model = builder.call(options, params)
20
+ options["result.model"] = result = Result.new(!model.nil?, {})
21
+
22
+ result.success?
23
+ end
24
+
25
+ class Builder
26
+ def call(options, params)
27
+ action = options["model.action"] || :new
28
+ model_class = options["model.class"]
29
+ find_by_key = options["model.find_by_key"] || :id
30
+ action = :pass_through unless %i[new find_by].include?(action)
31
+
32
+ send("#{action}!", model_class, params, options["model.action"], find_by_key)
33
+ end
34
+
35
+ def new!(model_class, params, *)
36
+ model_class.new
37
+ end
38
+
39
+ # Doesn't throw an exception and will return false to divert to Left.
40
+ def find_by!(model_class, params, action, find_by_key, *)
41
+ model_class.find_by(find_by_key.to_sym => params[find_by_key])
42
+ end
43
+
44
+ # Call any method on the model class and pass find_by_key, for example find(params[:id]).
45
+ def pass_through!(model_class, params, action, find_by_key, *)
46
+ model_class.send(action, params[find_by_key])
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,29 @@
1
+ module Trailblazer::V2_1::Operation::Module
2
+ def self.included(base)
3
+ base.extend ClassMethods
4
+ base.extend Included
5
+ end
6
+
7
+ module Included # TODO: use representable's inheritance mechanism.
8
+ def included(base)
9
+ super
10
+ instructions.each { |cfg|
11
+ method = cfg[0]
12
+ args = cfg[1].dup
13
+ block = cfg[2]
14
+ # options = args.extract_options!.dup # we need to duplicate options has as AM::Validations messes it up later.
15
+
16
+ base.send(method, *args, &block) } # property :name, {} do .. end
17
+ end
18
+ end
19
+
20
+ module ClassMethods
21
+ def method_missing(method, *args, &block)
22
+ instructions << [method, args, block]
23
+ end
24
+
25
+ def instructions
26
+ @instructions ||= []
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,98 @@
1
+ # per default, everything we pass into a circuit is immutable. it's the ops/act's job to allow writing (via a Context)
2
+ module Trailblazer::V2_1
3
+ class Operation
4
+ # {Nested} macro.
5
+ def self.Nested(callable, id: "Nested(#{callable})", input: nil, output: nil)
6
+ task_wrap_extensions = Module.new do
7
+ extend Activity::Path::Plan()
8
+ end
9
+
10
+ input_output = Nested.input_output_extensions_for(input, output) # TODO: deprecate this?
11
+
12
+ task, operation, is_dynamic = Nested.build(callable)
13
+
14
+ if is_dynamic
15
+ task_wrap_extensions.task task.method(:compute_nested_activity), id: ".compute_nested_activity", after: "Start.default", group: :start
16
+ task_wrap_extensions.task task.method(:compute_return_signal), id: ".compute_return_signal", after: "task_wrap.call_task"
17
+ end
18
+
19
+ options = {
20
+ task: task,
21
+ id: id,
22
+ Trailblazer::V2_1::Activity::DSL::Extension.new(Trailblazer::V2_1::Activity::TaskWrap::Merge.new(task_wrap_extensions)) => true,
23
+ outputs: operation.outputs,
24
+ }.merge(input_output)
25
+ end
26
+
27
+ # @private
28
+ module Nested
29
+ def self.input_output_extensions_for(input, output)
30
+ return {} unless input || output
31
+
32
+ input = input || ->(original_ctx, **) { original_ctx }
33
+ output = output || ->(new_ctx, **) { new_ctx.decompose.last } # merges "mutable" part into original, since it's in Unscoped.
34
+
35
+ {
36
+ Activity::TaskWrap::VariableMapping(input: input, output: output) => true
37
+ }
38
+ end
39
+
40
+ # DISCUSS: use builders here?
41
+ def self.build(nested_operation)
42
+ return dynamic = Dynamic.new(nested_operation), dynamic, true unless nestable_object?(nested_operation)
43
+
44
+ # The returned {Nested} instance is a valid circuit element and will be `call`ed in the circuit.
45
+ # It simply returns the nested activity's `signal,options,flow_options` return set.
46
+ # The actual wiring - where to go with that - is done by the step DSL.
47
+ return nested_operation, nested_operation, false
48
+ end
49
+
50
+ def self.nestable_object?(object)
51
+ object.is_a?( Trailblazer::V2_1::Activity::Interface )
52
+ end
53
+
54
+ def self.operation_class
55
+ Operation
56
+ end
57
+
58
+ # For dynamic `Nested`s that do not expose an {Activity} interface.
59
+ # Since we do not know its outputs, we have to map them to :success and :failure, only.
60
+ #
61
+ # This is what {Nested} in 2.0 used to do, where the outcome could only be true/false (or success/failure).
62
+ class Dynamic
63
+ def initialize(nested_activity)
64
+ @nested_activity = Trailblazer::V2_1::Option::KW(nested_activity)
65
+ @outputs = {
66
+ :success => Activity::Output( Railway::End::Success.new(semantic: :success), :success ),
67
+ :failure => Activity::Output( Railway::End::Failure.new(semantic: :failure), :failure ),
68
+ }
69
+ end
70
+
71
+ attr_reader :outputs
72
+
73
+ # TaskWrap step.
74
+ def compute_nested_activity((wrap_ctx, original_args), **circuit_options)
75
+ (ctx,), original_circuit_options = original_args
76
+
77
+ # TODO: evaluate the option to get the actual "object" to call.
78
+ activity = @nested_activity.call(ctx, original_circuit_options)
79
+
80
+ # Overwrite :task so task_wrap.call_task will call this activity.
81
+ # This is a trick so we don't have to repeat logic from #call_task here.
82
+ wrap_ctx[:task] = activity
83
+
84
+ return Activity::Right, [wrap_ctx, original_args]
85
+ end
86
+
87
+ def compute_return_signal((wrap_ctx, original_args), **circuit_options)
88
+ # Translate the genuine nested signal to the generic Dynamic end (success/failure, only).
89
+ # Note that here we lose information about what specific event was emitted.
90
+ wrap_ctx[:return_signal] = wrap_ctx[:return_signal].kind_of?(Railway::End::Success) ?
91
+ @outputs[:success].signal : @outputs[:failure].signal
92
+
93
+ return Activity::Right, [wrap_ctx, original_args]
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,14 @@
1
+ module Trailblazer::V2_1
2
+ class Operation
3
+ module Contract
4
+ def self.Persist(method: :save, name: "default")
5
+ path = "contract.#{name}"
6
+ step = ->(options, **) { options[path].send(method) }
7
+
8
+ task = Activity::TaskBuilder::Binary( step )
9
+
10
+ { task: task, id: "persist.save" }
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,44 @@
1
+ class Trailblazer::V2_1::Operation
2
+ module Policy
3
+ # Step: This generically `call`s a policy and then pushes its result to `options`.
4
+ # You can use any callable object as a policy with this step.
5
+ class Eval
6
+ def initialize(name:nil, path:nil)
7
+ @name = name
8
+ @path = path
9
+ end
10
+
11
+ # incoming low-level {Task API}.
12
+ # outgoing Task::Binary API.
13
+ def call((options, flow_options), **circuit_options)
14
+ condition = options[ @path ] # this allows dependency injection.
15
+ result = condition.( [options, flow_options], **circuit_options )
16
+
17
+ options["policy.#{@name}"] = result["policy"] # assign the policy as a skill.
18
+ options["result.policy.#{@name}"] = result
19
+
20
+ # flow control
21
+ signal = result.success? ? Trailblazer::V2_1::Activity::Right : Trailblazer::V2_1::Activity::Left # since we & this, it's only executed OnRight and the return boolean decides the direction, input is passed straight through.
22
+
23
+ return signal, [ options, flow_options ]
24
+ end
25
+ end
26
+
27
+ # Adds the `yield` result to the pipe and treats it like a
28
+ # policy-compatible object at runtime.
29
+ def self.step(condition, options, &block)
30
+ name = options[:name]
31
+ path = "policy.#{name}.eval"
32
+
33
+ task = Eval.new( name: name, path: path )
34
+
35
+ extension = Trailblazer::V2_1::Activity::TaskWrap::Merge.new(
36
+ Trailblazer::V2_1::Operation::Wrap::Inject::Defaults(
37
+ path => condition
38
+ )
39
+ )
40
+
41
+ { task: task, id: path, Trailblazer::V2_1::Activity::DSL::Extension.new(extension) => true }
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,55 @@
1
+ module Trailblazer::V2_1
2
+ module Operation::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
+ #
14
+ # @note Do not override this method as it will be removed in future versions. Also, you will break tracing.
15
+ # @return Operation::Railway::Result binary result object
16
+ def call(*args)
17
+ return call_with_circuit_interface(*args) if args.any? && args[0].is_a?(Array) # This is kind of a hack that could be well hidden if Ruby had method overloading. Goal is to simplify the call/__call__ thing as we're fading out Operation::call anyway.
18
+ call_with_public_interface(*args)
19
+ end
20
+
21
+ def call_with_public_interface(*args)
22
+ ctx = Operation::PublicCall.options_for_public_call(*args)
23
+
24
+ # call the activity.
25
+ # This will result in invoking {::call_with_circuit_interface}.
26
+ last_signal, (options, flow_options) = Activity::TaskWrap.invoke(self, [ctx, {}], {})
27
+
28
+ # Result is successful if the activity ended with an End event derived from Railway::End::Success.
29
+ Operation::Railway::Result(last_signal, options, flow_options)
30
+ end
31
+
32
+ # This interface is used for all nested OPs (and the outer-most, too).
33
+ def call_with_circuit_interface(args, circuit_options)
34
+ @activity.(
35
+ args,
36
+ circuit_options.merge(
37
+ exec_context: new
38
+ )
39
+ )
40
+ end
41
+
42
+ # Compile a Context object to be passed into the Activity::call.
43
+ # @private
44
+ def self.options_for_public_call(options={}, *containers)
45
+ # generate the skill hash that embraces runtime options plus potential containers, the so called Runtime options.
46
+ # This wrapping is supposed to happen once in the entire system.
47
+
48
+ hash_transformer = ->(containers) { containers[0].to_hash } # FIXME: don't transform any containers into kw args.
49
+
50
+ immutable_options = Trailblazer::V2_1::Context::ContainerChain.new( [options, *containers], to_hash: hash_transformer ) # Runtime options, immutable.
51
+
52
+ ctx = Trailblazer::V2_1::Context(immutable_options)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,38 @@
1
+ class Trailblazer::V2_1::Operation
2
+ module Policy
3
+ def self.Pundit(policy_class, action, name: :default)
4
+ Policy.step( Pundit.build(policy_class, action), name: name )
5
+ end
6
+
7
+ module Pundit
8
+ def self.build(*args, &block)
9
+ Condition.new(*args, &block)
10
+ end
11
+
12
+ # Pundit::Condition is invoked at runtime when iterating the pipe.
13
+ class Condition
14
+ def initialize(policy_class, action)
15
+ @policy_class, @action = policy_class, action
16
+ end
17
+
18
+ # Instantiate the actual policy object, and call it.
19
+ def call((options), *)
20
+ policy = build_policy(options) # this translates to Pundit interface.
21
+ result!(policy.send(@action), policy)
22
+ end
23
+
24
+ private
25
+ def build_policy(options)
26
+ @policy_class.new(options[:current_user], options[:model])
27
+ end
28
+
29
+ def result!(success, policy)
30
+ data = { "policy" => policy }
31
+ data["message"] = "Breach" if !success # TODO: how to allow messages here?
32
+
33
+ Result.new(success, data)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,32 @@
1
+ module Trailblazer::V2_1
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
@@ -0,0 +1,13 @@
1
+ module Trailblazer::V2_1
2
+ module Operation::Railway
3
+ def self.fail! ; Activity::Left end
4
+ def self.pass! ; Activity::Right end
5
+ def self.fail_fast!; Activity::FastTrack::FailFast end
6
+ def self.pass_fast!; Activity::FastTrack::PassFast end
7
+
8
+ module End
9
+ FailFast = Class.new(Operation::Railway::End::Failure)
10
+ PassFast = Class.new(Operation::Railway::End::Success)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+ module Trailblazer::V2_1
2
+ module Operation::Railway
3
+ # Call the user's steps with a differing API (inspired by Maciej Mensfeld) that
4
+ # only receives keyword args. The `options` keyword is the stateful context object
5
+ #
6
+ # def my_step( params:, ** )
7
+ # def my_step( params:, options:, ** )
8
+ module Macaroni
9
+ def self.call(user_proc)
10
+ Activity::TaskBuilder::Task.new( Trailblazer::V2_1::Option.build( Macaroni::Option, user_proc ), user_proc )
11
+ end
12
+
13
+ class Option < Trailblazer::V2_1::Option
14
+ # The Option#call! method prepares the arguments.
15
+ def self.call!(proc, options, *)
16
+ proc.( **options.to_hash.merge( options: options ) )
17
+ end
18
+ end
19
+ end
20
+
21
+ KwSignature = Macaroni
22
+ end
23
+ end
@@ -0,0 +1,58 @@
1
+ module Trailblazer::V2_1
2
+ module Operation::Railway
3
+ # The {Normalizer} is called for every DSL call (step/pass/fail etc.) and normalizes/defaults
4
+ # the user options, such as setting `:id`, connecting the task's outputs or wrapping the user's
5
+ # task via {TaskBuilder} in order to translate true/false to `Right` or `Left`.
6
+ #
7
+ # The Normalizer sits in the `@builder`, which receives all DSL calls from the Operation subclass.
8
+ module Normalizer
9
+ Pipeline = Activity::Magnetic::Normalizer::Pipeline.clone
10
+
11
+ Pipeline.module_eval do
12
+ # Handle the :override option which is specific to Operation.
13
+ def self.override(ctx, task:, options:, sequence_options:, **)
14
+ options, locals = Activity::Magnetic::Options.normalize(options, [:override])
15
+ sequence_options = sequence_options.merge( replace: options[:id] ) if locals[:override]
16
+
17
+ ctx[:options], ctx[:sequence_options] = options, sequence_options
18
+ end
19
+
20
+ # TODO remove in 2.2
21
+ def self.deprecate_macro_with_two_args(ctx, task:, **)
22
+ return true unless task.is_a?(Array) # TODO remove in 2.2
23
+
24
+ ctx[:options] = Operation::DeprecatedMacro.( *task )
25
+ end
26
+
27
+ # TODO remove in 2.2
28
+ def self.deprecate_name(ctx, local_options:, connection_options:, **)
29
+ connection_options, deprecated_options = Activity::Magnetic::Options.normalize(connection_options, [:name])
30
+ local_options, _deprecated_options = Activity::Magnetic::Options.normalize(local_options, [:name])
31
+
32
+ deprecated_options = deprecated_options.merge(_deprecated_options)
33
+
34
+ local_options = local_options.merge( name: deprecated_options[:name] ) if deprecated_options[:name]
35
+
36
+ local_options, locals = Activity::Magnetic::Options.normalize(local_options, [:name])
37
+ if locals[:name]
38
+ warn "[Trailblazer::V2_1] The :name option for #step, #success and #failure has been renamed to :id."
39
+ local_options = local_options.merge(id: locals[:name])
40
+ end
41
+
42
+ ctx[:local_options], ctx[:connection_options] = local_options, connection_options
43
+ end
44
+
45
+ def self.raise_on_missing_id(ctx, local_options:, **)
46
+ raise "No :id given for #{local_options[:task]}" unless local_options[:id]
47
+ true
48
+ end
49
+
50
+ # add more normalization tasks to the existing Magnetic::Normalizer::Pipeline
51
+ task Activity::TaskBuilder::Binary( method(:deprecate_macro_with_two_args) ), before: "split_options"
52
+ task Activity::TaskBuilder::Binary( method(:deprecate_name) )
53
+ task Activity::TaskBuilder::Binary( method(:override) )
54
+ task Activity::TaskBuilder::Binary( method(:raise_on_missing_id) )
55
+ end
56
+ end # Normalizer
57
+ end
58
+ end