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