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,50 @@
|
|
|
1
|
+
class Trailblazer::V2_1::Operation
|
|
2
|
+
def self.Model(model_class, action=nil, find_by_key=nil)
|
|
3
|
+
task = Trailblazer::V2_1::Activity::TaskBuilder::Binary(Model.new)
|
|
4
|
+
|
|
5
|
+
extension = Trailblazer::V2_1::Activity::TaskWrap::Merge.new(
|
|
6
|
+
Wrap::Inject::Defaults(
|
|
7
|
+
"model.class" => model_class,
|
|
8
|
+
"model.action" => action,
|
|
9
|
+
"model.find_by_key" => find_by_key
|
|
10
|
+
)
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
{ task: task, id: "model.build", Trailblazer::V2_1::Activity::DSL::Extension.new(extension) => true }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class Model
|
|
17
|
+
def call(options, params:, **)
|
|
18
|
+
builder = Model::Builder.new
|
|
19
|
+
options[:model] = model = builder.call(options, params)
|
|
20
|
+
options["result.model"] = result = Result.new(!model.nil?, {})
|
|
21
|
+
|
|
22
|
+
result.success?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class Builder
|
|
26
|
+
def call(options, params)
|
|
27
|
+
action = options["model.action"] || :new
|
|
28
|
+
model_class = options["model.class"]
|
|
29
|
+
find_by_key = options["model.find_by_key"] || :id
|
|
30
|
+
action = :pass_through unless %i[new find_by].include?(action)
|
|
31
|
+
|
|
32
|
+
send("#{action}!", model_class, params, options["model.action"], find_by_key)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def new!(model_class, params, *)
|
|
36
|
+
model_class.new
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Doesn't throw an exception and will return false to divert to Left.
|
|
40
|
+
def find_by!(model_class, params, action, find_by_key, *)
|
|
41
|
+
model_class.find_by(find_by_key.to_sym => params[find_by_key])
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Call any method on the model class and pass find_by_key, for example find(params[:id]).
|
|
45
|
+
def pass_through!(model_class, params, action, find_by_key, *)
|
|
46
|
+
model_class.send(action, params[find_by_key])
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Trailblazer::V2_1::Operation::Module
|
|
2
|
+
def self.included(base)
|
|
3
|
+
base.extend ClassMethods
|
|
4
|
+
base.extend Included
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
module Included # TODO: use representable's inheritance mechanism.
|
|
8
|
+
def included(base)
|
|
9
|
+
super
|
|
10
|
+
instructions.each { |cfg|
|
|
11
|
+
method = cfg[0]
|
|
12
|
+
args = cfg[1].dup
|
|
13
|
+
block = cfg[2]
|
|
14
|
+
# options = args.extract_options!.dup # we need to duplicate options has as AM::Validations messes it up later.
|
|
15
|
+
|
|
16
|
+
base.send(method, *args, &block) } # property :name, {} do .. end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
module ClassMethods
|
|
21
|
+
def method_missing(method, *args, &block)
|
|
22
|
+
instructions << [method, args, block]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def instructions
|
|
26
|
+
@instructions ||= []
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# per default, everything we pass into a circuit is immutable. it's the ops/act's job to allow writing (via a Context)
|
|
2
|
+
module Trailblazer::V2_1
|
|
3
|
+
class Operation
|
|
4
|
+
# {Nested} macro.
|
|
5
|
+
def self.Nested(callable, id: "Nested(#{callable})", input: nil, output: nil)
|
|
6
|
+
task_wrap_extensions = Module.new do
|
|
7
|
+
extend Activity::Path::Plan()
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
input_output = Nested.input_output_extensions_for(input, output) # TODO: deprecate this?
|
|
11
|
+
|
|
12
|
+
task, operation, is_dynamic = Nested.build(callable)
|
|
13
|
+
|
|
14
|
+
if is_dynamic
|
|
15
|
+
task_wrap_extensions.task task.method(:compute_nested_activity), id: ".compute_nested_activity", after: "Start.default", group: :start
|
|
16
|
+
task_wrap_extensions.task task.method(:compute_return_signal), id: ".compute_return_signal", after: "task_wrap.call_task"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
options = {
|
|
20
|
+
task: task,
|
|
21
|
+
id: id,
|
|
22
|
+
Trailblazer::V2_1::Activity::DSL::Extension.new(Trailblazer::V2_1::Activity::TaskWrap::Merge.new(task_wrap_extensions)) => true,
|
|
23
|
+
outputs: operation.outputs,
|
|
24
|
+
}.merge(input_output)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @private
|
|
28
|
+
module Nested
|
|
29
|
+
def self.input_output_extensions_for(input, output)
|
|
30
|
+
return {} unless input || output
|
|
31
|
+
|
|
32
|
+
input = input || ->(original_ctx, **) { original_ctx }
|
|
33
|
+
output = output || ->(new_ctx, **) { new_ctx.decompose.last } # merges "mutable" part into original, since it's in Unscoped.
|
|
34
|
+
|
|
35
|
+
{
|
|
36
|
+
Activity::TaskWrap::VariableMapping(input: input, output: output) => true
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# DISCUSS: use builders here?
|
|
41
|
+
def self.build(nested_operation)
|
|
42
|
+
return dynamic = Dynamic.new(nested_operation), dynamic, true unless nestable_object?(nested_operation)
|
|
43
|
+
|
|
44
|
+
# The returned {Nested} instance is a valid circuit element and will be `call`ed in the circuit.
|
|
45
|
+
# It simply returns the nested activity's `signal,options,flow_options` return set.
|
|
46
|
+
# The actual wiring - where to go with that - is done by the step DSL.
|
|
47
|
+
return nested_operation, nested_operation, false
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.nestable_object?(object)
|
|
51
|
+
object.is_a?( Trailblazer::V2_1::Activity::Interface )
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.operation_class
|
|
55
|
+
Operation
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# For dynamic `Nested`s that do not expose an {Activity} interface.
|
|
59
|
+
# Since we do not know its outputs, we have to map them to :success and :failure, only.
|
|
60
|
+
#
|
|
61
|
+
# This is what {Nested} in 2.0 used to do, where the outcome could only be true/false (or success/failure).
|
|
62
|
+
class Dynamic
|
|
63
|
+
def initialize(nested_activity)
|
|
64
|
+
@nested_activity = Trailblazer::V2_1::Option::KW(nested_activity)
|
|
65
|
+
@outputs = {
|
|
66
|
+
:success => Activity::Output( Railway::End::Success.new(semantic: :success), :success ),
|
|
67
|
+
:failure => Activity::Output( Railway::End::Failure.new(semantic: :failure), :failure ),
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
attr_reader :outputs
|
|
72
|
+
|
|
73
|
+
# TaskWrap step.
|
|
74
|
+
def compute_nested_activity((wrap_ctx, original_args), **circuit_options)
|
|
75
|
+
(ctx,), original_circuit_options = original_args
|
|
76
|
+
|
|
77
|
+
# TODO: evaluate the option to get the actual "object" to call.
|
|
78
|
+
activity = @nested_activity.call(ctx, original_circuit_options)
|
|
79
|
+
|
|
80
|
+
# Overwrite :task so task_wrap.call_task will call this activity.
|
|
81
|
+
# This is a trick so we don't have to repeat logic from #call_task here.
|
|
82
|
+
wrap_ctx[:task] = activity
|
|
83
|
+
|
|
84
|
+
return Activity::Right, [wrap_ctx, original_args]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def compute_return_signal((wrap_ctx, original_args), **circuit_options)
|
|
88
|
+
# Translate the genuine nested signal to the generic Dynamic end (success/failure, only).
|
|
89
|
+
# Note that here we lose information about what specific event was emitted.
|
|
90
|
+
wrap_ctx[:return_signal] = wrap_ctx[:return_signal].kind_of?(Railway::End::Success) ?
|
|
91
|
+
@outputs[:success].signal : @outputs[:failure].signal
|
|
92
|
+
|
|
93
|
+
return Activity::Right, [wrap_ctx, original_args]
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Trailblazer::V2_1
|
|
2
|
+
class Operation
|
|
3
|
+
module Contract
|
|
4
|
+
def self.Persist(method: :save, name: "default")
|
|
5
|
+
path = "contract.#{name}"
|
|
6
|
+
step = ->(options, **) { options[path].send(method) }
|
|
7
|
+
|
|
8
|
+
task = Activity::TaskBuilder::Binary( step )
|
|
9
|
+
|
|
10
|
+
{ task: task, id: "persist.save" }
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
class Trailblazer::V2_1::Operation
|
|
2
|
+
module Policy
|
|
3
|
+
# Step: This generically `call`s a policy and then pushes its result to `options`.
|
|
4
|
+
# You can use any callable object as a policy with this step.
|
|
5
|
+
class Eval
|
|
6
|
+
def initialize(name:nil, path:nil)
|
|
7
|
+
@name = name
|
|
8
|
+
@path = path
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# incoming low-level {Task API}.
|
|
12
|
+
# outgoing Task::Binary API.
|
|
13
|
+
def call((options, flow_options), **circuit_options)
|
|
14
|
+
condition = options[ @path ] # this allows dependency injection.
|
|
15
|
+
result = condition.( [options, flow_options], **circuit_options )
|
|
16
|
+
|
|
17
|
+
options["policy.#{@name}"] = result["policy"] # assign the policy as a skill.
|
|
18
|
+
options["result.policy.#{@name}"] = result
|
|
19
|
+
|
|
20
|
+
# flow control
|
|
21
|
+
signal = result.success? ? Trailblazer::V2_1::Activity::Right : Trailblazer::V2_1::Activity::Left # since we & this, it's only executed OnRight and the return boolean decides the direction, input is passed straight through.
|
|
22
|
+
|
|
23
|
+
return signal, [ options, flow_options ]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Adds the `yield` result to the pipe and treats it like a
|
|
28
|
+
# policy-compatible object at runtime.
|
|
29
|
+
def self.step(condition, options, &block)
|
|
30
|
+
name = options[:name]
|
|
31
|
+
path = "policy.#{name}.eval"
|
|
32
|
+
|
|
33
|
+
task = Eval.new( name: name, path: path )
|
|
34
|
+
|
|
35
|
+
extension = Trailblazer::V2_1::Activity::TaskWrap::Merge.new(
|
|
36
|
+
Trailblazer::V2_1::Operation::Wrap::Inject::Defaults(
|
|
37
|
+
path => condition
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
{ task: task, id: path, Trailblazer::V2_1::Activity::DSL::Extension.new(extension) => true }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module Trailblazer::V2_1
|
|
2
|
+
module Operation::PublicCall
|
|
3
|
+
# This is the outer-most public `call` method that gets invoked when calling `Create.()`.
|
|
4
|
+
# The signature of this is `params, options, *containers`. This was a mistake, as the
|
|
5
|
+
# first argument could've been part of `options` hash in the first place.
|
|
6
|
+
#
|
|
7
|
+
# Create.(params, runtime_data, *containers)
|
|
8
|
+
# #=> Result<Context...>
|
|
9
|
+
#
|
|
10
|
+
# In workflows/Nested compositions, this method is not used anymore and it might probably
|
|
11
|
+
# get removed in future versions of TRB. Currently, we use Operation::__call__ as an alternative.
|
|
12
|
+
#
|
|
13
|
+
#
|
|
14
|
+
# @note Do not override this method as it will be removed in future versions. Also, you will break tracing.
|
|
15
|
+
# @return Operation::Railway::Result binary result object
|
|
16
|
+
def call(*args)
|
|
17
|
+
return call_with_circuit_interface(*args) if args.any? && args[0].is_a?(Array) # This is kind of a hack that could be well hidden if Ruby had method overloading. Goal is to simplify the call/__call__ thing as we're fading out Operation::call anyway.
|
|
18
|
+
call_with_public_interface(*args)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def call_with_public_interface(*args)
|
|
22
|
+
ctx = Operation::PublicCall.options_for_public_call(*args)
|
|
23
|
+
|
|
24
|
+
# call the activity.
|
|
25
|
+
# This will result in invoking {::call_with_circuit_interface}.
|
|
26
|
+
last_signal, (options, flow_options) = Activity::TaskWrap.invoke(self, [ctx, {}], {})
|
|
27
|
+
|
|
28
|
+
# Result is successful if the activity ended with an End event derived from Railway::End::Success.
|
|
29
|
+
Operation::Railway::Result(last_signal, options, flow_options)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# This interface is used for all nested OPs (and the outer-most, too).
|
|
33
|
+
def call_with_circuit_interface(args, circuit_options)
|
|
34
|
+
@activity.(
|
|
35
|
+
args,
|
|
36
|
+
circuit_options.merge(
|
|
37
|
+
exec_context: new
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Compile a Context object to be passed into the Activity::call.
|
|
43
|
+
# @private
|
|
44
|
+
def self.options_for_public_call(options={}, *containers)
|
|
45
|
+
# generate the skill hash that embraces runtime options plus potential containers, the so called Runtime options.
|
|
46
|
+
# This wrapping is supposed to happen once in the entire system.
|
|
47
|
+
|
|
48
|
+
hash_transformer = ->(containers) { containers[0].to_hash } # FIXME: don't transform any containers into kw args.
|
|
49
|
+
|
|
50
|
+
immutable_options = Trailblazer::V2_1::Context::ContainerChain.new( [options, *containers], to_hash: hash_transformer ) # Runtime options, immutable.
|
|
51
|
+
|
|
52
|
+
ctx = Trailblazer::V2_1::Context(immutable_options)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
class Trailblazer::V2_1::Operation
|
|
2
|
+
module Policy
|
|
3
|
+
def self.Pundit(policy_class, action, name: :default)
|
|
4
|
+
Policy.step( Pundit.build(policy_class, action), name: name )
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
module Pundit
|
|
8
|
+
def self.build(*args, &block)
|
|
9
|
+
Condition.new(*args, &block)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Pundit::Condition is invoked at runtime when iterating the pipe.
|
|
13
|
+
class Condition
|
|
14
|
+
def initialize(policy_class, action)
|
|
15
|
+
@policy_class, @action = policy_class, action
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Instantiate the actual policy object, and call it.
|
|
19
|
+
def call((options), *)
|
|
20
|
+
policy = build_policy(options) # this translates to Pundit interface.
|
|
21
|
+
result!(policy.send(@action), policy)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
def build_policy(options)
|
|
26
|
+
@policy_class.new(options[:current_user], options[:model])
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def result!(success, policy)
|
|
30
|
+
data = { "policy" => policy }
|
|
31
|
+
data["message"] = "Breach" if !success # TODO: how to allow messages here?
|
|
32
|
+
|
|
33
|
+
Result.new(success, data)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Trailblazer::V2_1
|
|
2
|
+
# Operations is simply a thin API to define, inherit and run circuits by passing the options object.
|
|
3
|
+
# It encourages the linear railway style (http://trb.to/gems/workflow/circuit.html#operation) but can
|
|
4
|
+
# easily be extend for more complex workflows.
|
|
5
|
+
class Operation
|
|
6
|
+
# End event: All subclasses of End:::Success are interpreted as "success".
|
|
7
|
+
module Railway
|
|
8
|
+
# @param options Context
|
|
9
|
+
# @param end_event The last emitted signal in a circuit is usually the end event.
|
|
10
|
+
def self.Result(end_event, options, *)
|
|
11
|
+
Result.new(end_event.kind_of?(End::Success), options, end_event)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# The Railway::Result knows about its binary state, the context (data), and the last event in the circuit.
|
|
15
|
+
class Result < Result # Operation::Result
|
|
16
|
+
def initialize(success, data, event)
|
|
17
|
+
super(success, data)
|
|
18
|
+
|
|
19
|
+
@event = event
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
attr_reader :event
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
module End
|
|
26
|
+
class Success < Activity::End; end
|
|
27
|
+
class Failure < Activity::End; end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end # Railway
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Trailblazer::V2_1
|
|
2
|
+
module Operation::Railway
|
|
3
|
+
def self.fail! ; Activity::Left end
|
|
4
|
+
def self.pass! ; Activity::Right end
|
|
5
|
+
def self.fail_fast!; Activity::FastTrack::FailFast end
|
|
6
|
+
def self.pass_fast!; Activity::FastTrack::PassFast end
|
|
7
|
+
|
|
8
|
+
module End
|
|
9
|
+
FailFast = Class.new(Operation::Railway::End::Failure)
|
|
10
|
+
PassFast = Class.new(Operation::Railway::End::Success)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Trailblazer::V2_1
|
|
2
|
+
module Operation::Railway
|
|
3
|
+
# Call the user's steps with a differing API (inspired by Maciej Mensfeld) that
|
|
4
|
+
# only receives keyword args. The `options` keyword is the stateful context object
|
|
5
|
+
#
|
|
6
|
+
# def my_step( params:, ** )
|
|
7
|
+
# def my_step( params:, options:, ** )
|
|
8
|
+
module Macaroni
|
|
9
|
+
def self.call(user_proc)
|
|
10
|
+
Activity::TaskBuilder::Task.new( Trailblazer::V2_1::Option.build( Macaroni::Option, user_proc ), user_proc )
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class Option < Trailblazer::V2_1::Option
|
|
14
|
+
# The Option#call! method prepares the arguments.
|
|
15
|
+
def self.call!(proc, options, *)
|
|
16
|
+
proc.( **options.to_hash.merge( options: options ) )
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
KwSignature = Macaroni
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module Trailblazer::V2_1
|
|
2
|
+
module Operation::Railway
|
|
3
|
+
# The {Normalizer} is called for every DSL call (step/pass/fail etc.) and normalizes/defaults
|
|
4
|
+
# the user options, such as setting `:id`, connecting the task's outputs or wrapping the user's
|
|
5
|
+
# task via {TaskBuilder} in order to translate true/false to `Right` or `Left`.
|
|
6
|
+
#
|
|
7
|
+
# The Normalizer sits in the `@builder`, which receives all DSL calls from the Operation subclass.
|
|
8
|
+
module Normalizer
|
|
9
|
+
Pipeline = Activity::Magnetic::Normalizer::Pipeline.clone
|
|
10
|
+
|
|
11
|
+
Pipeline.module_eval do
|
|
12
|
+
# Handle the :override option which is specific to Operation.
|
|
13
|
+
def self.override(ctx, task:, options:, sequence_options:, **)
|
|
14
|
+
options, locals = Activity::Magnetic::Options.normalize(options, [:override])
|
|
15
|
+
sequence_options = sequence_options.merge( replace: options[:id] ) if locals[:override]
|
|
16
|
+
|
|
17
|
+
ctx[:options], ctx[:sequence_options] = options, sequence_options
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# TODO remove in 2.2
|
|
21
|
+
def self.deprecate_macro_with_two_args(ctx, task:, **)
|
|
22
|
+
return true unless task.is_a?(Array) # TODO remove in 2.2
|
|
23
|
+
|
|
24
|
+
ctx[:options] = Operation::DeprecatedMacro.( *task )
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# TODO remove in 2.2
|
|
28
|
+
def self.deprecate_name(ctx, local_options:, connection_options:, **)
|
|
29
|
+
connection_options, deprecated_options = Activity::Magnetic::Options.normalize(connection_options, [:name])
|
|
30
|
+
local_options, _deprecated_options = Activity::Magnetic::Options.normalize(local_options, [:name])
|
|
31
|
+
|
|
32
|
+
deprecated_options = deprecated_options.merge(_deprecated_options)
|
|
33
|
+
|
|
34
|
+
local_options = local_options.merge( name: deprecated_options[:name] ) if deprecated_options[:name]
|
|
35
|
+
|
|
36
|
+
local_options, locals = Activity::Magnetic::Options.normalize(local_options, [:name])
|
|
37
|
+
if locals[:name]
|
|
38
|
+
warn "[Trailblazer::V2_1] The :name option for #step, #success and #failure has been renamed to :id."
|
|
39
|
+
local_options = local_options.merge(id: locals[:name])
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
ctx[:local_options], ctx[:connection_options] = local_options, connection_options
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.raise_on_missing_id(ctx, local_options:, **)
|
|
46
|
+
raise "No :id given for #{local_options[:task]}" unless local_options[:id]
|
|
47
|
+
true
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# add more normalization tasks to the existing Magnetic::Normalizer::Pipeline
|
|
51
|
+
task Activity::TaskBuilder::Binary( method(:deprecate_macro_with_two_args) ), before: "split_options"
|
|
52
|
+
task Activity::TaskBuilder::Binary( method(:deprecate_name) )
|
|
53
|
+
task Activity::TaskBuilder::Binary( method(:override) )
|
|
54
|
+
task Activity::TaskBuilder::Binary( method(:raise_on_missing_id) )
|
|
55
|
+
end
|
|
56
|
+
end # Normalizer
|
|
57
|
+
end
|
|
58
|
+
end
|