trailblazer-future 2.1.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.travis.yml +6 -0
- data/CHANGES.md +4 -0
- data/LICENSE.txt +9 -0
- data/README.md +48 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/gems.rb +3 -0
- data/lib/trailblazer/future.rb +9 -0
- data/lib/trailblazer/future/version.rb +5 -0
- data/lib/trailblazer/v2_1/activity.rb +123 -0
- data/lib/trailblazer/v2_1/activity/config.rb +37 -0
- data/lib/trailblazer/v2_1/activity/dsl/add_task.rb +22 -0
- data/lib/trailblazer/v2_1/activity/dsl/helper.rb +68 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic.rb +36 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder.rb +101 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/default_normalizer.rb +26 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/fast_track.rb +118 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/normalizer.rb +113 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/path.rb +105 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/railway.rb +97 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/builder/state.rb +58 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/finalizer.rb +51 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/generate.rb +62 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/merge.rb +16 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/process_options.rb +76 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/structure/alterations.rb +44 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/structure/plus_poles.rb +85 -0
- data/lib/trailblazer/v2_1/activity/dsl/magnetic/structure/polarization.rb +23 -0
- data/lib/trailblazer/v2_1/activity/dsl/record.rb +11 -0
- data/lib/trailblazer/v2_1/activity/dsl/schema/dependencies.rb +46 -0
- data/lib/trailblazer/v2_1/activity/dsl/schema/sequence.rb +46 -0
- data/lib/trailblazer/v2_1/activity/dsl/strategy/build_state.rb +32 -0
- data/lib/trailblazer/v2_1/activity/dsl/strategy/fast_track.rb +24 -0
- data/lib/trailblazer/v2_1/activity/dsl/strategy/path.rb +26 -0
- data/lib/trailblazer/v2_1/activity/dsl/strategy/plan.rb +36 -0
- data/lib/trailblazer/v2_1/activity/dsl/strategy/railway.rb +23 -0
- data/lib/trailblazer/v2_1/activity/interface.rb +16 -0
- data/lib/trailblazer/v2_1/activity/introspect.rb +167 -0
- data/lib/trailblazer/v2_1/activity/present.rb +95 -0
- data/lib/trailblazer/v2_1/activity/structures.rb +57 -0
- data/lib/trailblazer/v2_1/activity/task_builder.rb +41 -0
- data/lib/trailblazer/v2_1/activity/task_wrap.rb +40 -0
- data/lib/trailblazer/v2_1/activity/task_wrap/call_task.rb +19 -0
- data/lib/trailblazer/v2_1/activity/task_wrap/merge.rb +23 -0
- data/lib/trailblazer/v2_1/activity/task_wrap/runner.rb +62 -0
- data/lib/trailblazer/v2_1/activity/task_wrap/trace.rb +44 -0
- data/lib/trailblazer/v2_1/activity/task_wrap/variable_mapping.rb +162 -0
- data/lib/trailblazer/v2_1/activity/testing.rb +32 -0
- data/lib/trailblazer/v2_1/activity/trace.rb +97 -0
- data/lib/trailblazer/v2_1/circuit.rb +71 -0
- data/lib/trailblazer/v2_1/container_chain.rb +45 -0
- data/lib/trailblazer/v2_1/context.rb +79 -0
- data/lib/trailblazer/v2_1/deprecation/call.rb +46 -0
- data/lib/trailblazer/v2_1/deprecation/context.rb +43 -0
- data/lib/trailblazer/v2_1/dsl.rb +47 -0
- data/lib/trailblazer/v2_1/macro.rb +11 -0
- data/lib/trailblazer/v2_1/macro/contract.rb +5 -0
- data/lib/trailblazer/v2_1/operation.rb +91 -0
- data/lib/trailblazer/v2_1/operation/auto_inject.rb +47 -0
- data/lib/trailblazer/v2_1/operation/callable.rb +42 -0
- data/lib/trailblazer/v2_1/operation/class_dependencies.rb +25 -0
- data/lib/trailblazer/v2_1/operation/contract.rb +61 -0
- data/lib/trailblazer/v2_1/operation/deprecated_macro.rb +19 -0
- data/lib/trailblazer/v2_1/operation/deprecations.rb +21 -0
- data/lib/trailblazer/v2_1/operation/guard.rb +18 -0
- data/lib/trailblazer/v2_1/operation/heritage.rb +30 -0
- data/lib/trailblazer/v2_1/operation/inject.rb +36 -0
- data/lib/trailblazer/v2_1/operation/inspect.rb +79 -0
- data/lib/trailblazer/v2_1/operation/model.rb +50 -0
- data/lib/trailblazer/v2_1/operation/module.rb +29 -0
- data/lib/trailblazer/v2_1/operation/nested.rb +98 -0
- data/lib/trailblazer/v2_1/operation/persist.rb +14 -0
- data/lib/trailblazer/v2_1/operation/policy.rb +44 -0
- data/lib/trailblazer/v2_1/operation/public_call.rb +55 -0
- data/lib/trailblazer/v2_1/operation/pundit.rb +38 -0
- data/lib/trailblazer/v2_1/operation/railway.rb +32 -0
- data/lib/trailblazer/v2_1/operation/railway/fast_track.rb +13 -0
- data/lib/trailblazer/v2_1/operation/railway/macaroni.rb +23 -0
- data/lib/trailblazer/v2_1/operation/railway/normalizer.rb +58 -0
- data/lib/trailblazer/v2_1/operation/railway/task_builder.rb +37 -0
- data/lib/trailblazer/v2_1/operation/rescue.rb +42 -0
- data/lib/trailblazer/v2_1/operation/responder.rb +18 -0
- data/lib/trailblazer/v2_1/operation/result.rb +30 -0
- data/lib/trailblazer/v2_1/operation/test.rb +17 -0
- data/lib/trailblazer/v2_1/operation/trace.rb +46 -0
- data/lib/trailblazer/v2_1/operation/validate.rb +76 -0
- data/lib/trailblazer/v2_1/operation/wrap.rb +83 -0
- data/lib/trailblazer/v2_1/option.rb +78 -0
- data/lib/trailblazer/v2_1/rails.rb +12 -0
- data/lib/trailblazer/v2_1/rails/cell.rb +22 -0
- data/lib/trailblazer/v2_1/rails/controller.rb +66 -0
- data/lib/trailblazer/v2_1/rails/form.rb +21 -0
- data/lib/trailblazer/v2_1/rails/railtie.rb +31 -0
- data/lib/trailblazer/v2_1/rails/railtie/extend_application_controller.rb +28 -0
- data/lib/trailblazer/v2_1/rails/railtie/loader.rb +58 -0
- data/lib/trailblazer/v2_1/rails/test/integration.rb +6 -0
- data/lib/trailblazer/v2_1/versions.txt +7 -0
- data/test/rails5.0/.gitignore +17 -0
- data/test/rails5.0/Gemfile +21 -0
- data/test/rails5.0/Rakefile +3 -0
- data/test/rails5.0/app/concepts/artist/cell/dashboard.rb +7 -0
- data/test/rails5.0/app/concepts/artist/cell/show.rb +4 -0
- data/test/rails5.0/app/concepts/artist/view/dashboard.erb +4 -0
- data/test/rails5.0/app/concepts/artist/view/show.erb +1 -0
- data/test/rails5.0/app/concepts/params/operation/with_args.rb +5 -0
- data/test/rails5.0/app/concepts/song/contract/form.rb +6 -0
- data/test/rails5.0/app/concepts/song/operation/create.rb +15 -0
- data/test/rails5.0/app/concepts/song/operation/show.rb +3 -0
- data/test/rails5.0/app/concepts/song/operation/update.rb +15 -0
- data/test/rails5.0/app/controllers/application_controller.rb +46 -0
- data/test/rails5.0/app/controllers/args_controller.rb +5 -0
- data/test/rails5.0/app/controllers/artists_controller.rb +24 -0
- data/test/rails5.0/app/controllers/params_controller.rb +11 -0
- data/test/rails5.0/app/controllers/songs_controller.rb +35 -0
- data/test/rails5.0/app/models/artist.rb +2 -0
- data/test/rails5.0/app/models/song.rb +2 -0
- data/test/rails5.0/app/views/args/with_args.html.erb +1 -0
- data/test/rails5.0/app/views/layouts/application.html.erb +2 -0
- data/test/rails5.0/app/views/songs/edit.html.erb +4 -0
- data/test/rails5.0/app/views/songs/new.html.erb +5 -0
- data/test/rails5.0/app/views/songs/new_with_result.html.erb +3 -0
- data/test/rails5.0/app/views/songs/show.html.erb +3 -0
- data/test/rails5.0/bin/bundle +3 -0
- data/test/rails5.0/bin/rails +4 -0
- data/test/rails5.0/bin/rake +4 -0
- data/test/rails5.0/bin/setup +29 -0
- data/test/rails5.0/config.ru +4 -0
- data/test/rails5.0/config/application.rb +25 -0
- data/test/rails5.0/config/boot.rb +3 -0
- data/test/rails5.0/config/database.yml +21 -0
- data/test/rails5.0/config/environment.rb +5 -0
- data/test/rails5.0/config/environments/development.rb +41 -0
- data/test/rails5.0/config/environments/test.rb +42 -0
- data/test/rails5.0/config/initializers/assets.rb +11 -0
- data/test/rails5.0/config/initializers/backtrace_silencers.rb +7 -0
- data/test/rails5.0/config/initializers/cookies_serializer.rb +3 -0
- data/test/rails5.0/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/rails5.0/config/initializers/inflections.rb +16 -0
- data/test/rails5.0/config/initializers/mime_types.rb +4 -0
- data/test/rails5.0/config/initializers/session_store.rb +3 -0
- data/test/rails5.0/config/initializers/wrap_parameters.rb +14 -0
- data/test/rails5.0/config/locales/en.yml +23 -0
- data/test/rails5.0/config/routes.rb +15 -0
- data/test/rails5.0/config/secrets.yml +17 -0
- data/test/rails5.0/db/migrate/20161122145124_create_songs.rb +8 -0
- data/test/rails5.0/db/schema.rb +19 -0
- data/test/rails5.0/log/.keep +0 -0
- data/test/rails5.0/test/concepts/song/operation/create_test.rb +11 -0
- data/test/rails5.0/test/concepts/song/operation/update_test.rb +34 -0
- data/test/rails5.0/test/integration/.keep +0 -0
- data/test/rails5.0/test/integration/args_controller_test.rb +8 -0
- data/test/rails5.0/test/integration/artists_controller_test.rb +28 -0
- data/test/rails5.0/test/integration/params_controller_test.rb +8 -0
- data/test/rails5.0/test/integration/songs_controller_test.rb +40 -0
- data/test/rails5.0/test/test_helper.rb +7 -0
- data/test/test_helper.rb +5 -0
- data/trailblazer-future.gemspec +25 -0
- 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"
|