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,32 @@
|
|
|
1
|
+
# DISCUSS: move to trailblazer-activity-test ?
|
|
2
|
+
|
|
3
|
+
# Helpers to quickly create steps and tasks.
|
|
4
|
+
module Trailblazer::V2_1::Activity::Testing
|
|
5
|
+
# Creates a module with one step method for each name.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# extend T.def_steps(:create, :save)
|
|
9
|
+
def self.def_steps(*names)
|
|
10
|
+
Module.new do
|
|
11
|
+
names.each do |name|
|
|
12
|
+
define_method(name) do | ctx, ** |
|
|
13
|
+
ctx[:seq] << name
|
|
14
|
+
ctx.key?(name) ? ctx[name] : true
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Creates a method instance with a task interface.
|
|
21
|
+
#
|
|
22
|
+
# @example
|
|
23
|
+
# task task: T.def_task(:create)
|
|
24
|
+
def self.def_task(name)
|
|
25
|
+
Module.new do
|
|
26
|
+
define_singleton_method(name) do | (ctx, flow_options), ** |
|
|
27
|
+
ctx[:seq] << name
|
|
28
|
+
return Trailblazer::V2_1::Activity::Right, [ctx, flow_options]
|
|
29
|
+
end
|
|
30
|
+
end.method(name)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
module Trailblazer::V2_1
|
|
2
|
+
class Activity < Module # Trace#call will call the activities and trace what steps are called, options passed,
|
|
3
|
+
# and the order and nesting.
|
|
4
|
+
#
|
|
5
|
+
# stack, _ = Trailblazer::V2_1::Activity::Trace.(activity, activity[:Start], { id: 1 })
|
|
6
|
+
# puts Trailblazer::V2_1::Activity::Present.tree(stack) # renders the trail.
|
|
7
|
+
#
|
|
8
|
+
# Hooks into the taskWrap.
|
|
9
|
+
module Trace
|
|
10
|
+
class << self
|
|
11
|
+
# {:argumenter} API
|
|
12
|
+
# FIXME: needs Introspect.arguments_for_call
|
|
13
|
+
# FIXME: needs TaskWrap.arguments_for_call
|
|
14
|
+
def arguments_for_call(activity, (options, flow_options), **circuit_options)
|
|
15
|
+
tracing_flow_options = {
|
|
16
|
+
stack: Trace::Stack.new,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
tracing_circuit_options = {
|
|
20
|
+
wrap_runtime: ::Hash.new(Trace.wirings), # FIXME: this still overrides existing :wrap_runtime.
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return activity, [ options, tracing_flow_options.merge(flow_options) ], circuit_options.merge(tracing_circuit_options)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def call(activity, (options, flow_options), circuit_options={})
|
|
27
|
+
activity, (options, flow_options), circuit_options = Trace.arguments_for_call( activity, [options, flow_options], circuit_options ) # only run once for the entire circuit!
|
|
28
|
+
|
|
29
|
+
last_signal, (options, flow_options) =
|
|
30
|
+
Activity::TaskWrap.invoke(activity, [options, flow_options], circuit_options)
|
|
31
|
+
|
|
32
|
+
return flow_options[:stack], last_signal, [options, flow_options]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
alias_method :invoke, :call
|
|
36
|
+
|
|
37
|
+
# Insertions for the trace tasks that capture the arguments just before calling the task,
|
|
38
|
+
# and before the TaskWrap is finished.
|
|
39
|
+
#
|
|
40
|
+
# Note that the TaskWrap steps are implemented in Activity::TaskWrap::Trace.
|
|
41
|
+
#
|
|
42
|
+
# @private
|
|
43
|
+
def wirings
|
|
44
|
+
Module.new do
|
|
45
|
+
extend Activity::Path::Plan()
|
|
46
|
+
|
|
47
|
+
task TaskWrap::Trace.method(:capture_args), id: "task_wrap.capture_args", before: "task_wrap.call_task"
|
|
48
|
+
task TaskWrap::Trace.method(:capture_return), id: "task_wrap.capture_return", before: "End.success", group: :end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
Entity = Struct.new(:task, :activity, :data)
|
|
54
|
+
class Entity::Input < Entity
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class Entity::Output < Entity
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class Task < Array
|
|
61
|
+
def inspect
|
|
62
|
+
%{<Task>#{super}}
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Mutable/stateful per design. We want a (global) stack!
|
|
67
|
+
class Stack
|
|
68
|
+
def initialize
|
|
69
|
+
@nested = []
|
|
70
|
+
@stack = [ @nested ]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def indent!
|
|
74
|
+
current << indented = Task.new
|
|
75
|
+
@stack << indented
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def unindent!
|
|
79
|
+
@stack.pop
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def <<(args)
|
|
83
|
+
current << args
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def to_a
|
|
87
|
+
@nested
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
def current
|
|
92
|
+
@stack.last
|
|
93
|
+
end
|
|
94
|
+
end # Stack
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module Trailblazer::V2_1
|
|
2
|
+
# Running a Circuit instance will run all tasks sequentially depending on the former's result.
|
|
3
|
+
# Each task is called and retrieves the former task's return values.
|
|
4
|
+
#
|
|
5
|
+
# Note: Please use #Activity as a public circuit builder.
|
|
6
|
+
#
|
|
7
|
+
# @param map [Hash] Defines the wiring.
|
|
8
|
+
# @param stop_events [Array] Tasks that stop execution of the circuit.
|
|
9
|
+
#
|
|
10
|
+
# result = circuit.(start_at, *args)
|
|
11
|
+
#
|
|
12
|
+
# @see Activity
|
|
13
|
+
# @api semi-private
|
|
14
|
+
#
|
|
15
|
+
# This is the "pipeline operator"'s implementation.
|
|
16
|
+
class Circuit
|
|
17
|
+
def initialize(map, stop_events, start_task:, name: nil)
|
|
18
|
+
@map = map
|
|
19
|
+
@stop_events = stop_events
|
|
20
|
+
@name = name
|
|
21
|
+
@start_task = start_task
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @param args [Array] all arguments to be passed to the task's `call`
|
|
25
|
+
# @param task [callable] task to call
|
|
26
|
+
Run = ->(task, args, **circuit_options) { task.(args, **circuit_options) }
|
|
27
|
+
|
|
28
|
+
# Runs the circuit until we hit a stop event.
|
|
29
|
+
#
|
|
30
|
+
# This method throws exceptions when the returned value of a task doesn't match
|
|
31
|
+
# any wiring.
|
|
32
|
+
#
|
|
33
|
+
# @param task An event or task of this circuit from where to start
|
|
34
|
+
# @param options anything you want to pass to the first task
|
|
35
|
+
# @param flow_options Library-specific flow control data
|
|
36
|
+
# @return [last_signal, options, flow_options, *args]
|
|
37
|
+
#
|
|
38
|
+
# NOTE: returned circuit_options are discarded when calling the runner.
|
|
39
|
+
def call(args, start_task: @start_task, runner: Run, **circuit_options)
|
|
40
|
+
circuit_options = circuit_options.merge( runner: runner ).freeze # TODO: set the :runner option via arguments_for_call to save the merge?
|
|
41
|
+
task = start_task
|
|
42
|
+
|
|
43
|
+
loop do
|
|
44
|
+
last_signal, args, _discarded_circuit_options = runner.(
|
|
45
|
+
task,
|
|
46
|
+
args,
|
|
47
|
+
circuit_options
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Stop execution of the circuit when we hit a stop event (< End). This could be an task's End or Suspend.
|
|
51
|
+
return [ last_signal, args ] if @stop_events.include?(task) # DISCUSS: return circuit_options here?
|
|
52
|
+
|
|
53
|
+
task = next_for(task, last_signal) or raise IllegalSignalError.new("<#{@name}>[#{task}][ #{last_signal.inspect} ]")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Returns the circuit's components.
|
|
58
|
+
def to_h
|
|
59
|
+
{ map: @map, end_events: @stop_events, start_task: @start_task }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
def next_for(last_task, signal)
|
|
64
|
+
outputs = @map[last_task]
|
|
65
|
+
outputs[signal]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class IllegalSignalError < RuntimeError
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @private
|
|
2
|
+
class Trailblazer::V2_1::Context::ContainerChain # used to be called Resolver.
|
|
3
|
+
# Keeps a list of containers. When looking up a key/value, containers are traversed in
|
|
4
|
+
# the order they were added until key is found.
|
|
5
|
+
#
|
|
6
|
+
# Required Container interface: `#key?`, `#[]`.
|
|
7
|
+
#
|
|
8
|
+
# @note ContainerChain is an immutable data structure, it does not support writing.
|
|
9
|
+
# @param containers Array of <Container> objects (splatted)
|
|
10
|
+
def initialize(containers, to_hash: nil)
|
|
11
|
+
@containers = containers
|
|
12
|
+
@to_hash = to_hash
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @param name Symbol or String to lookup a value stored in one of the containers.
|
|
16
|
+
def [](name)
|
|
17
|
+
self.class.find(@containers, name)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @private
|
|
21
|
+
def key?(name)
|
|
22
|
+
@containers.find { |container| container.key?(name) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.find(containers, name)
|
|
26
|
+
containers.find { |container| container.key?(name) && (return container[name]) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def keys
|
|
30
|
+
@containers.collect(&:keys).flatten
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @private
|
|
34
|
+
def to_hash
|
|
35
|
+
return @to_hash.(@containers) if @to_hash # FIXME: introduce pattern matching so we can have different "transformers" for each container type.
|
|
36
|
+
@containers.each_with_object({}) { |container, hash| hash.merge!(container.to_hash) }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# alternative implementation:
|
|
41
|
+
# containers.reverse.each do |container| @mutable_options.merge!(container) end
|
|
42
|
+
#
|
|
43
|
+
# benchmark, merging in #initialize vs. this resolver.
|
|
44
|
+
# merge 39.678k (± 9.1%) i/s - 198.700k in 5.056653s
|
|
45
|
+
# resolver 68.928k (± 6.4%) i/s - 342.836k in 5.001610s
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# TODO: mark/make all but mutable_options as frozen.
|
|
2
|
+
# The idea of Skill is to have a generic, ordered read/write interface that
|
|
3
|
+
# collects mutable runtime-computed data while providing access to compile-time
|
|
4
|
+
# information.
|
|
5
|
+
# The runtime-data takes precedence over the class data.
|
|
6
|
+
#
|
|
7
|
+
# notes
|
|
8
|
+
# a context is a ContainerChain with two elements (when reading)
|
|
9
|
+
module Trailblazer::V2_1
|
|
10
|
+
# Holds local options (aka `mutable_options`) and "original" options from the "outer"
|
|
11
|
+
# activity (aka wrapped_options).
|
|
12
|
+
|
|
13
|
+
# only public creator: Build
|
|
14
|
+
class Context # :data object:
|
|
15
|
+
def initialize(wrapped_options, mutable_options)
|
|
16
|
+
@wrapped_options, @mutable_options = wrapped_options, mutable_options
|
|
17
|
+
# TODO: wrapped_options should be optimized for lookups here since it could also be a Context instance, but should be a ContainerChain.
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def [](name)
|
|
21
|
+
# ContainerChain.find( [@mutable_options, @wrapped_options], name )
|
|
22
|
+
|
|
23
|
+
# in 99.9% or cases @mutable_options will be a Hash, and these are already optimized for lookups.
|
|
24
|
+
# it's up to the ContainerChain to optimize itself.
|
|
25
|
+
return @mutable_options[name] if @mutable_options.key?(name)
|
|
26
|
+
@wrapped_options[name]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# TODO: use ContainerChain.find here for a generic optimization
|
|
30
|
+
#
|
|
31
|
+
# the version here is about 4x faster for now.
|
|
32
|
+
def key?(name)
|
|
33
|
+
# ContainerChain.find( [@mutable_options, @wrapped_options], name )
|
|
34
|
+
@mutable_options.key?(name) || @wrapped_options.key?(name)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def []=(name, value)
|
|
38
|
+
@mutable_options[name] = value
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @private
|
|
42
|
+
#
|
|
43
|
+
# This method might be removed.
|
|
44
|
+
def merge(hash)
|
|
45
|
+
original, mutable_options = decompose
|
|
46
|
+
|
|
47
|
+
ctx = Trailblazer::V2_1::Context( original, mutable_options.merge(hash) )
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Return the Context's two components. Used when computing the new output for
|
|
51
|
+
# the next activity.
|
|
52
|
+
def decompose
|
|
53
|
+
[ @wrapped_options, @mutable_options ]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def keys
|
|
57
|
+
@mutable_options.keys + @wrapped_options.keys # FIXME.
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# TODO: maybe we shouldn't allow to_hash from context?
|
|
63
|
+
# TODO: massive performance bottleneck. also, we could already "know" here what keys the
|
|
64
|
+
# transformation wants.
|
|
65
|
+
# FIXME: ToKeywordArguments()
|
|
66
|
+
def to_hash
|
|
67
|
+
{}.tap do |hash|
|
|
68
|
+
# the "key" here is to call to_hash on all containers.
|
|
69
|
+
[ @wrapped_options.to_hash, @mutable_options.to_hash ].each do |options|
|
|
70
|
+
options.each { |k, v| hash[k.to_sym] = v }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.Context(wrapped_options, mutable_options={})
|
|
77
|
+
Context.new(wrapped_options, mutable_options)
|
|
78
|
+
end
|
|
79
|
+
end # Trailblazer::V2_1
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Trailblazer::V2_1
|
|
2
|
+
module Deprecation
|
|
3
|
+
module Operation
|
|
4
|
+
# This is super hacky. Fix you call calls everywhere (shouldn't take too long) and never load this file, again.
|
|
5
|
+
module Call
|
|
6
|
+
def self.options_for_call(params, *containers)
|
|
7
|
+
if containers == []
|
|
8
|
+
if params.is_a?(Hash) # this means we assume everything is cool. Create.( {...} )
|
|
9
|
+
|
|
10
|
+
else # this means someone did Create.( #<WeirdParamsObject> )
|
|
11
|
+
deprecate_positional_params(params, *containers)
|
|
12
|
+
return { params: params }, *containers
|
|
13
|
+
end
|
|
14
|
+
else # Create.( params, "current_user" => ... )
|
|
15
|
+
options, containers = containers[0], (containers[1..-1] || [])
|
|
16
|
+
if options.is_a?(Hash) # old API
|
|
17
|
+
warn "[Trailblazer::V2_1] Please don't pass the `params` object as a positional argument into `Operation.()`, use the `:params` key and one hash for all: `Operation.( params: my_params, current_user: ... )` ."
|
|
18
|
+
return options.merge( params: params ), *containers
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
return params, *containers
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.deprecate_positional_params(params, *containers)
|
|
26
|
+
warn "[Trailblazer::V2_1] Please don't pass the `params` object as a positional argument into `Operation.()`, use the `:params` key: `Operation.( params: my_params )` ."
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
Trailblazer::V2_1::Operation.module_eval do
|
|
34
|
+
# this sucks:
|
|
35
|
+
def self.call(options={}, *containers)
|
|
36
|
+
options, *containers = Trailblazer::V2_1::Deprecation::Operation::Call.options_for_call(options, *containers)
|
|
37
|
+
|
|
38
|
+
ctx = Trailblazer::V2_1::Operation::PublicCall.options_for_public_call(options, *containers)
|
|
39
|
+
|
|
40
|
+
# call the activity.
|
|
41
|
+
last_signal, (options, flow_options) = __call__( [ctx, {}] ) # Railway::call # DISCUSS: this could be ::call_with_context.
|
|
42
|
+
|
|
43
|
+
# Result is successful if the activity ended with an End event derived from Railway::End::Success.
|
|
44
|
+
Trailblazer::V2_1::Operation::Railway::Result(last_signal, options, flow_options)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Trailblazer::V2_1
|
|
2
|
+
module Deprecation
|
|
3
|
+
class ContextWithIndifferentAccess < Trailblazer::V2_1::Context
|
|
4
|
+
def [](key)
|
|
5
|
+
return super unless Trailblazer::V2_1::Operation::PublicCall.deprecatable?(key)
|
|
6
|
+
key, _ = Trailblazer::V2_1::Operation::PublicCall.deprecate_string(key, nil)
|
|
7
|
+
super(key)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def []=(key, value)
|
|
11
|
+
return super unless Trailblazer::V2_1::Operation::PublicCall.deprecatable?(key)
|
|
12
|
+
key, _ = Trailblazer::V2_1::Operation::PublicCall.deprecate_string(key, nil)
|
|
13
|
+
super(key, value)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
Trailblazer::V2_1::Operation::PublicCall.module_eval do
|
|
20
|
+
def self.options_for_public_call(options={}, *containers)
|
|
21
|
+
hash_transformer = ->(containers) { containers[0].to_hash } # FIXME: don't transform any containers into kw args.
|
|
22
|
+
|
|
23
|
+
options = deprecate_strings(options)
|
|
24
|
+
|
|
25
|
+
immutable_options = Trailblazer::V2_1::Context::ContainerChain.new( [options, *containers], to_hash: hash_transformer ) # Runtime options, immutable.
|
|
26
|
+
|
|
27
|
+
Trailblazer::V2_1::Deprecation::ContextWithIndifferentAccess.new(immutable_options, {})
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.deprecatable?(key)
|
|
31
|
+
key.is_a?(String) && key.split(".").size == 1
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.deprecate_strings(options)
|
|
35
|
+
ary = options.collect { |k,v| deprecatable?(k) ? deprecate_string(k, v) : [k,v] }
|
|
36
|
+
Hash[ary]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.deprecate_string(key, value)
|
|
40
|
+
warn "[Trailblazer::V2_1] Using a string key for non-namespaced keys is deprecated. Please use `:#{key}` instead of `#{key.inspect}`."
|
|
41
|
+
[ key.to_sym, value ]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module Trailblazer::V2_1
|
|
2
|
+
module DSL
|
|
3
|
+
# Boring DSL code that allows to set a skill class, or define it ad-hoc using a block.
|
|
4
|
+
# passing a constant always wipes out the existing class.
|
|
5
|
+
#
|
|
6
|
+
# Used in Contract, Representer, Callback, ..
|
|
7
|
+
class Build
|
|
8
|
+
# options[:prefix]
|
|
9
|
+
# options[:class]
|
|
10
|
+
# options[:container]
|
|
11
|
+
|
|
12
|
+
# Currently, adds .class only to classes. this could break builder instances?
|
|
13
|
+
|
|
14
|
+
def call(options, name=nil, constant=nil, dsl_block, &block)
|
|
15
|
+
# contract MyForm
|
|
16
|
+
if name.is_a?(Class)
|
|
17
|
+
constant = name
|
|
18
|
+
name = :default
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
is_instance = !(constant.kind_of?(Class) || dsl_block) # i don't like this magic too much, but since it's the only DSL method in TRB, it should be ok. # DISCUSS: options[:is_instance]
|
|
22
|
+
|
|
23
|
+
path = path_name(options[:prefix], name, is_instance ? nil : "class") # "contract.default.class"
|
|
24
|
+
|
|
25
|
+
if is_instance
|
|
26
|
+
skill = constant
|
|
27
|
+
else
|
|
28
|
+
extended = options[:container][path] # Operation["contract.default.class"]
|
|
29
|
+
extended = yield extended if extended && block_given?
|
|
30
|
+
|
|
31
|
+
# only extend an existing skill class when NO constant was passed.
|
|
32
|
+
constant = (extended || options[:class]) if constant.nil?# && block_given?
|
|
33
|
+
|
|
34
|
+
skill = Class.new(constant)
|
|
35
|
+
skill.class_eval(&dsl_block) if dsl_block
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
[path, skill]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
def path_name(prefix, name, suffix)
|
|
43
|
+
[prefix, name, suffix].compact.join(".") # "contract.class" for default, otherwise "contract.params.class" etc.
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|