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,37 @@
1
+ module Trailblazer::V2_1
2
+ module Operation::Railway
3
+ # every step is wrapped by this proc/decider. this is executed in the circuit as the actual task.
4
+ # Step calls step.(options, **options, flow_options)
5
+ # Output direction binary: true=>Right, false=>Left.
6
+ # Passes through all subclasses of Direction.~~~~~~~~~~~~~~~~~
7
+ module TaskBuilder
8
+ def self.call(user_proc)
9
+ Task.new( Trailblazer::V2_1::Option::KW( user_proc ), user_proc )
10
+ end
11
+
12
+ # Translates the return value of the user step into a valid signal.
13
+ # Note that it passes through subclasses of {Signal}.
14
+ def self.binary_direction_for(result, on_true, on_false)
15
+ result.is_a?(Class) && result < Activity::Signal ? result : (result ? on_true : on_false)
16
+ end
17
+ end
18
+
19
+ class Task
20
+ def initialize(task, user_proc)
21
+ @task = task
22
+ @user_proc = user_proc
23
+ freeze
24
+ end
25
+
26
+ def call( (options, *args), **circuit_args )
27
+ # Execute the user step with TRB's kw args.
28
+ result = @task.( options, **circuit_args ) # circuit_args contains :exec_context.
29
+
30
+ # Return an appropriate signal which direction to go next.
31
+ direction = TaskBuilder.binary_direction_for( result, Activity::Right, Activity::Left )
32
+
33
+ [ direction, [ options, *args ], **circuit_args ]
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,42 @@
1
+ class Trailblazer::V2_1::Operation
2
+ NoopHandler = lambda { |*| }
3
+
4
+ def self.Rescue(*exceptions, handler: NoopHandler, &block)
5
+ exceptions = [StandardError] unless exceptions.any?
6
+
7
+ handler = Rescue.deprecate_positional_handler_signature(handler)
8
+ handler = Trailblazer::V2_1::Option(handler)
9
+
10
+ # This block is evaluated by {Wrap}.
11
+ rescue_block = ->((ctx, flow_options), **circuit_options, &nested_activity) do
12
+ begin
13
+ nested_activity.call
14
+ rescue *exceptions => exception
15
+ # DISCUSS: should we deprecate this signature and rather apply the Task API here?
16
+ handler.call(exception, ctx, **circuit_options) # FIXME: when there's an error here, it shows the wrong exception!
17
+
18
+ [ Trailblazer::V2_1::Operation::Railway.fail!, [ctx, flow_options] ]
19
+ end
20
+ end
21
+
22
+ Wrap( rescue_block, id: "Rescue(#{rand(100)})", &block )
23
+ # FIXME: name
24
+ # [ step, name: "Rescue:#{block.source_location.last}" ]
25
+ end
26
+
27
+ # TODO: remove me in 2.2.
28
+ module Rescue
29
+ def self.deprecate_positional_handler_signature(handler)
30
+ return handler if handler.is_a?(Symbol) # can't do nutting about this.
31
+
32
+ arity = handler.is_a?(Class) ? handler.method(:call).arity : handler.arity
33
+
34
+ return handler if arity != 2 # means (exception, (ctx, flow_options), *, &block), "new style"
35
+
36
+ ->(exception, (ctx, flow_options), **circuit_options, &block) do
37
+ warn "[Trailblazer::V2_1] Rescue handlers have a new signature: (exception, *, &block)"
38
+ handler.(exception, ctx, &block)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,18 @@
1
+ module Trailblazer::V2_1::Operation::Responder
2
+ def self.included(base)
3
+ base.extend ClassMethods
4
+ end
5
+
6
+ module ClassMethods
7
+ def model_name
8
+ model_class.model_name
9
+ end
10
+ end
11
+
12
+ extend Forwardable
13
+ def_delegators :@model, :to_param, :destroyed?, :persisted?
14
+
15
+ def to_model
16
+ @model
17
+ end
18
+ end
@@ -0,0 +1,30 @@
1
+ class Trailblazer::V2_1::Operation
2
+ class Result
3
+ # @param success Boolean validity of the result object
4
+ # @param data Context
5
+ def initialize(success, data)
6
+ @success, @data = success, data
7
+ end
8
+
9
+ def success?
10
+ @success
11
+ end
12
+
13
+ def failure?
14
+ ! success?
15
+ end
16
+
17
+ extend Forwardable
18
+ def_delegators :@data, :[] # DISCUSS: make it a real delegator? see Nested.
19
+
20
+ # DISCUSS: the two methods below are more for testing.
21
+ def inspect(*slices)
22
+ return "<Result:#{success?} #{slice(*slices).inspect} >" if slices.any?
23
+ "<Result:#{success?} #{@data.inspect} >"
24
+ end
25
+
26
+ def slice(*keys)
27
+ keys.collect { |k| self[k] }
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ module Trailblazer::V2_1
2
+ module Test
3
+ module Run
4
+ # DISCUSS: use Endpoint here?
5
+ # DISCUSS: use Controller code here?
6
+ module_function
7
+ def run(operation_class, *args)
8
+ result = operation_class.(*args)
9
+
10
+ raise "[Trailblazer::V2_1] #{operation_class} wasn't run successfully. #{result.inspect}" if result.failure?
11
+
12
+ result
13
+
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,46 @@
1
+ module Trailblazer::V2_1
2
+ class Operation
3
+ module Trace
4
+ # @note The problem in this method is, we have redundancy with Operation::PublicCall
5
+ def self.call(operation, *args)
6
+ ctx = PublicCall.options_for_public_call(*args) # redundant with PublicCall::call.
7
+
8
+ # Prepare the tracing-specific arguments. This is only run once for the entire circuit!
9
+ operation, *args = Trailblazer::V2_1::Activity::Trace.arguments_for_call( operation, [ctx, {}], {} )
10
+
11
+ last_signal, (ctx, flow_options) = Activity::TaskWrap.invoke(operation, *args )
12
+
13
+ result = Railway::Result(last_signal, ctx) # redundant with PublicCall::call.
14
+
15
+ Result.new(result, flow_options[:stack].to_a)
16
+ end
17
+
18
+ # `Operation::trace` is included for simple tracing of the flow.
19
+ # It simply forwards all arguments to `Trace.call`.
20
+ #
21
+ # @public
22
+ #
23
+ # Operation.trace(params, "current_user" => current_user).wtf
24
+ def trace(*args)
25
+ Trace.(self, *args)
26
+ end
27
+
28
+ # Presentation of the traced stack via the returned result object.
29
+ # This object is wrapped around the original result in {Trace.call}.
30
+ class Result < SimpleDelegator
31
+ def initialize(result, stack)
32
+ super(result)
33
+ @stack = stack
34
+ end
35
+
36
+ def wtf
37
+ Trailblazer::V2_1::Activity::Trace::Present.(@stack)
38
+ end
39
+
40
+ def wtf?
41
+ puts wtf
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,76 @@
1
+ module Trailblazer::V2_1
2
+ class Operation
3
+ module Contract
4
+ # result.contract = {..}
5
+ # result.contract.errors = {..}
6
+ # Deviate to left track if optional key is not found in params.
7
+ # Deviate to left if validation result falsey.
8
+ def self.Validate(skip_extract: false, name: "default", representer: false, key: nil, constant: nil) # DISCUSS: should we introduce something like Validate::Deserializer?
9
+ params_path = "contract.#{name}.params" # extract_params! save extracted params here.
10
+
11
+ extract = Validate::Extract.new( key: key, params_path: params_path ).freeze
12
+ validate = Validate.new( name: name, representer: representer, params_path: params_path, constant: constant ).freeze
13
+
14
+ # Build a simple Railway {Activity} for the internal flow.
15
+ activity = Module.new do
16
+ extend Activity::Railway(name: "Contract::Validate")
17
+
18
+ step extract, id: "#{params_path}_extract", Activity::DSL.Output(:failure) => Activity::DSL.End(:extract_failure) unless skip_extract || representer
19
+ step validate, id: "contract.#{name}.call"
20
+ end
21
+
22
+ options = { task: activity, id: "contract.#{name}.validate", outputs: activity.outputs}
23
+
24
+ # Deviate End.extract_failure to the standard failure track as a default. This can be changed from the user side.
25
+ options = options.merge(Activity::DSL.Output(:extract_failure) => Activity::DSL.Track(:failure)) unless skip_extract
26
+
27
+ options
28
+ end
29
+
30
+ class Validate
31
+ # Task: extract the contract's input from params by reading `:key`.
32
+ class Extract
33
+ def initialize(key:nil, params_path:nil)
34
+ @key, @params_path = key, params_path
35
+ end
36
+
37
+ def call( ctx, params:, ** )
38
+ ctx[@params_path] = @key ? params[@key] : params
39
+ end
40
+ end
41
+
42
+ def initialize(name:"default", representer:false, params_path:nil, constant: nil)
43
+ @name, @representer, @params_path, @constant = name, representer, params_path, constant
44
+ end
45
+
46
+ # Task: Validates contract `:name`.
47
+ def call( ctx, ** )
48
+ validate!(
49
+ ctx,
50
+ representer: ctx["representer.#{@name}.class"] ||= @representer, # FIXME: maybe @representer should use DI.
51
+ params_path: @params_path
52
+ )
53
+ end
54
+
55
+ def validate!(options, representer:false, from: :document, params_path:nil)
56
+ path = "contract.#{@name}"
57
+ contract = @constant || options[path]
58
+
59
+ # this is for 1.1-style compatibility and should be removed once we have Deserializer in place:
60
+ options["result.#{path}"] = result =
61
+ if representer
62
+ # use :document as the body and let the representer deserialize to the contract.
63
+ # this will be simplified once we have Deserializer.
64
+ # translates to contract.("{document: bla}") { MyRepresenter.new(contract).from_json .. }
65
+ contract.(options[from]) { |document| representer.new(contract).parse(document) }
66
+ else
67
+ # let Reform handle the deserialization.
68
+ contract.(options[params_path])
69
+ end
70
+
71
+ result.success?
72
+ end
73
+ end
74
+ end
75
+ end # Operation
76
+ end
@@ -0,0 +1,83 @@
1
+ class Trailblazer::V2_1::Operation
2
+ def self.Wrap(user_wrap, id: "Wrap/#{rand(100)}", &block)
3
+ operation_class = Wrap.create_operation(block)
4
+ wrapped = Wrap::Wrapped.new(operation_class, user_wrap)
5
+
6
+ { task: wrapped, id: id, outputs: operation_class.outputs }
7
+ end
8
+
9
+ module Wrap
10
+ def self.create_operation(block)
11
+ Class.new( Nested.operation_class, &block ) # Usually resolves to Trailblazer::V2_1::Operation.
12
+ end
13
+
14
+ # behaves like an operation so it plays with Nested and simply calls the operation in the user-provided block.
15
+ class Wrapped #< Trailblazer::V2_1::Operation # FIXME: the inheritance is only to satisfy Nested( Wrapped.new )
16
+ include Trailblazer::V2_1::Activity::Interface
17
+
18
+ private def deprecate_positional_wrap_signature(user_wrap)
19
+ parameters = user_wrap.is_a?(Module) ? user_wrap.method(:call).parameters : user_wrap.parameters
20
+
21
+ return user_wrap if parameters[0] == [:req] # means ((ctx, flow_options), *, &block), "new style"
22
+
23
+ ->((ctx, flow_options), **circuit_options, &block) do
24
+ warn "[Trailblazer::V2_1] Wrap handlers have a new signature: ((ctx), *, &block)"
25
+ user_wrap.(ctx, &block)
26
+ end
27
+ end
28
+
29
+ def initialize(operation, user_wrap)
30
+ user_wrap = deprecate_positional_wrap_signature(user_wrap)
31
+
32
+ @operation = operation
33
+ @user_wrap = user_wrap
34
+
35
+ # Since in the user block, you can return Railway.pass! etc, we need to map
36
+ # those to the actual wrapped operation's end.
37
+ outputs = @operation.outputs
38
+ @signal_to_output = {
39
+ Railway.pass! => outputs[:success].signal,
40
+ Railway.fail! => outputs[:failure].signal,
41
+ Railway.pass_fast! => outputs[:pass_fast].signal,
42
+ Railway.fail_fast! => outputs[:fail_fast].signal,
43
+ true => outputs[:success].signal,
44
+ false => outputs[:failure].signal,
45
+ nil => outputs[:failure].signal,
46
+ }
47
+ end
48
+
49
+ def call( (ctx, flow_options), **circuit_options )
50
+ block_calling_wrapped = -> {
51
+ activity = @operation.to_h[:activity]
52
+
53
+ activity.( [ctx, flow_options], **circuit_options )
54
+ }
55
+
56
+ # call the user's Wrap {} block in the operation.
57
+ # This will invoke block_calling_wrapped above if the user block yields.
58
+ returned = @user_wrap.( [ctx, flow_options], **circuit_options, &block_calling_wrapped )
59
+
60
+ # {returned} can be
61
+ # 1. {circuit interface return} from the begin block, because the wrapped OP passed
62
+ # 2. {task interface return} because the user block returns "customized" signals, true of fale
63
+
64
+ if returned.is_a?(Array) # 1. {circuit interface return}, new style.
65
+ signal, (ctx, flow_options) = returned
66
+ else # 2. {task interface return}, only a signal (or true/false)
67
+ # TODO: deprecate this?
68
+ signal = returned
69
+ end
70
+
71
+ # Use the original {signal} if there's no mapping.
72
+ # This usually means signal is an End instance or a custom signal.
73
+ signal = @signal_to_output.fetch(signal, signal)
74
+
75
+ return signal, [ctx, flow_options]
76
+ end
77
+
78
+ def outputs
79
+ @operation.outputs
80
+ end
81
+ end
82
+ end # Wrap
83
+ end
@@ -0,0 +1,78 @@
1
+ module Trailblazer::V2_1
2
+ # @note This might go to trailblazer-args along with `Context` at some point.
3
+ def self.Option(proc)
4
+ Option.build(Option, proc)
5
+ end
6
+
7
+ class Option
8
+ # Generic builder for a callable "option".
9
+ # @param call_implementation [Class, Module] implements the process of calling the proc
10
+ # while passing arguments/options to it in a specific style (e.g. kw args, step interface).
11
+ # @return [Proc] when called, this proc will evaluate its option (at run-time).
12
+ def self.build(call_implementation, proc)
13
+ if proc.is_a? Symbol
14
+ ->(*args, &block) { call_implementation.evaluate_method(proc, *args, &block) }
15
+ else
16
+ ->(*args, &block) { call_implementation.evaluate_callable(proc, *args, &block) }
17
+ end
18
+ end
19
+
20
+ # A call implementation invoking `proc.(*args)` and plainly forwarding all arguments.
21
+ # Override this for your own step strategy (see KW#call!).
22
+ # @private
23
+ def self.call!(proc, *args, &block)
24
+ proc.(*args, &block)
25
+ end
26
+
27
+ # Note that both #evaluate_callable and #evaluate_method drop most of the args.
28
+ # If you need those, override this class.
29
+ # @private
30
+ def self.evaluate_callable(proc, *args, **flow_options, &block)
31
+ call!(proc, *args, &block)
32
+ end
33
+
34
+ # Make the context's instance method a "lambda" and reuse #call!.
35
+ # @private
36
+ def self.evaluate_method(proc, *args, exec_context:raise("No :exec_context given."), **flow_options, &block)
37
+ call!(exec_context.method(proc), *args, &block)
38
+ end
39
+
40
+ # Returns a {Proc} that, when called, invokes the `proc` argument with keyword arguments.
41
+ # This is known as "step (call) interface".
42
+ #
43
+ # This is commonly used by `Operation::step` to wrap the argument and make it
44
+ # callable in the circuit.
45
+ #
46
+ # my_proc = ->(options, **kws) { options["i got called"] = true }
47
+ # task = Trailblazer::V2_1::Option::KW(my_proc)
48
+ # task.(options = {})
49
+ # options["i got called"] #=> true
50
+ #
51
+ # Alternatively, you can pass a symbol and an `:exec_context`.
52
+ #
53
+ # my_proc = :some_method
54
+ # task = Trailblazer::V2_1::Option::KW(my_proc)
55
+ #
56
+ # class A
57
+ # def some_method(options, **kws)
58
+ # options["i got called"] = true
59
+ # end
60
+ # end
61
+ #
62
+ # task.(options = {}, exec_context: A.new)
63
+ # options["i got called"] #=> true
64
+ def self.KW(proc)
65
+ Option.build(KW, proc)
66
+ end
67
+
68
+ # TODO: It would be cool if call! was typed and had `options SymbolizedHash` or something.
69
+ class KW < Option
70
+ # A different call implementation that calls `proc` with a "step interface".
71
+ # your_code.(options, **options)
72
+ # @private
73
+ def self.call!(proc, options, *)
74
+ proc.(options, **options.to_hash) # Step interface: (options, **)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,12 @@
1
+ require "trailblazer/v2_1/rails/version"
2
+
3
+ module Trailblazer::V2_1
4
+ module Rails
5
+ end
6
+ end
7
+
8
+ require "trailblazer/v2_1/rails/controller"
9
+ require "trailblazer/v2_1/rails/cell"
10
+ require "trailblazer/v2_1/rails/form"
11
+ require "trailblazer/v2_1/rails/railtie"
12
+ require "trailblazer/v2_1"