trailblazer-future 2.1.0.rc1

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