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,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"