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