trailblazer 2.1.0.beta4 → 2.1.0.beta5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +194 -502
- data/CHANGES.md +4 -0
- data/CONTRIBUTING.md +170 -0
- data/Gemfile +4 -1
- data/README.md +183 -40
- data/Rakefile +6 -2
- data/lib/trailblazer/version.rb +1 -1
- data/lib/trailblazer.rb +3 -12
- data/test/{operation/dsl → dsl}/contract_test.rb +2 -2
- data/trailblazer.gemspec +3 -3
- metadata +17 -63
- data/lib/trailblazer/operation/contract.rb +0 -82
- data/lib/trailblazer/operation/guard.rb +0 -18
- data/lib/trailblazer/operation/model.rb +0 -65
- data/lib/trailblazer/operation/nested.rb +0 -91
- data/lib/trailblazer/operation/persist.rb +0 -14
- data/lib/trailblazer/operation/policy.rb +0 -44
- data/lib/trailblazer/operation/pundit.rb +0 -38
- data/lib/trailblazer/operation/representer.rb +0 -36
- data/lib/trailblazer/operation/rescue.rb +0 -24
- data/lib/trailblazer/operation/validate.rb +0 -74
- data/lib/trailblazer/operation/wrap.rb +0 -64
- data/test/docs/contract_test.rb +0 -545
- data/test/docs/dry_test.rb +0 -31
- data/test/docs/guard_test.rb +0 -162
- data/test/docs/macro_test.rb +0 -36
- data/test/docs/model_test.rb +0 -75
- data/test/docs/nested_test.rb +0 -300
- data/test/docs/policy_test.rb +0 -2
- data/test/docs/pundit_test.rb +0 -133
- data/test/docs/representer_test.rb +0 -268
- data/test/docs/rescue_test.rb +0 -154
- data/test/docs/wrap_test.rb +0 -219
- data/test/nested_test.rb +0 -293
- data/test/operation/contract_test.rb +0 -290
- data/test/operation/dsl/representer_test.rb +0 -169
- data/test/operation/model_test.rb +0 -54
- data/test/operation/persist_test.rb +0 -51
- data/test/operation/pundit_test.rb +0 -106
- data/test/operation/representer_test.rb +0 -254
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trailblazer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.1.0.
|
4
|
+
version: 2.1.0.beta5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-03-
|
11
|
+
date: 2018-03-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: trailblazer-operation
|
@@ -16,7 +16,7 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.2.
|
19
|
+
version: 0.2.4
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
22
|
version: 0.3.0
|
@@ -26,50 +26,50 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: 0.2.
|
29
|
+
version: 0.2.4
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: 0.3.0
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
|
-
name: trailblazer-
|
34
|
+
name: trailblazer-macro
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
37
|
- - ">="
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: 0.
|
39
|
+
version: 2.1.0.beta2
|
40
40
|
- - "<"
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
version:
|
42
|
+
version: 2.2.0
|
43
43
|
type: :runtime
|
44
44
|
prerelease: false
|
45
45
|
version_requirements: !ruby/object:Gem::Requirement
|
46
46
|
requirements:
|
47
47
|
- - ">="
|
48
48
|
- !ruby/object:Gem::Version
|
49
|
-
version: 0.
|
49
|
+
version: 2.1.0.beta2
|
50
50
|
- - "<"
|
51
51
|
- !ruby/object:Gem::Version
|
52
|
-
version:
|
52
|
+
version: 2.2.0
|
53
53
|
- !ruby/object:Gem::Dependency
|
54
|
-
name:
|
54
|
+
name: trailblazer-macro-contract
|
55
55
|
requirement: !ruby/object:Gem::Requirement
|
56
56
|
requirements:
|
57
57
|
- - ">="
|
58
58
|
- !ruby/object:Gem::Version
|
59
|
-
version: 2.
|
59
|
+
version: 2.1.0.beta2
|
60
60
|
- - "<"
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version:
|
62
|
+
version: 2.2.0
|
63
63
|
type: :runtime
|
64
64
|
prerelease: false
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
66
66
|
requirements:
|
67
67
|
- - ">="
|
68
68
|
- !ruby/object:Gem::Version
|
69
|
-
version: 2.
|
69
|
+
version: 2.1.0.beta2
|
70
70
|
- - "<"
|
71
71
|
- !ruby/object:Gem::Version
|
72
|
-
version:
|
72
|
+
version: 2.2.0
|
73
73
|
- !ruby/object:Gem::Dependency
|
74
74
|
name: declarative
|
75
75
|
requirement: !ruby/object:Gem::Requirement
|
@@ -182,6 +182,7 @@ files:
|
|
182
182
|
- ".travis.yml"
|
183
183
|
- CHANGES.md
|
184
184
|
- COMM-LICENSE
|
185
|
+
- CONTRIBUTING.md
|
185
186
|
- Gemfile
|
186
187
|
- LICENSE.txt
|
187
188
|
- README.md
|
@@ -195,54 +196,25 @@ files:
|
|
195
196
|
- lib/trailblazer/deprecation/context.rb
|
196
197
|
- lib/trailblazer/dsl.rb
|
197
198
|
- lib/trailblazer/operation/auto_inject.rb
|
198
|
-
- lib/trailblazer/operation/contract.rb
|
199
199
|
- lib/trailblazer/operation/deprecations.rb
|
200
|
-
- lib/trailblazer/operation/guard.rb
|
201
200
|
- lib/trailblazer/operation/input_output.rb
|
202
|
-
- lib/trailblazer/operation/model.rb
|
203
201
|
- lib/trailblazer/operation/module.rb
|
204
|
-
- lib/trailblazer/operation/nested.rb
|
205
|
-
- lib/trailblazer/operation/persist.rb
|
206
|
-
- lib/trailblazer/operation/policy.rb
|
207
|
-
- lib/trailblazer/operation/pundit.rb
|
208
|
-
- lib/trailblazer/operation/representer.rb
|
209
|
-
- lib/trailblazer/operation/rescue.rb
|
210
202
|
- lib/trailblazer/operation/test.rb
|
211
|
-
- lib/trailblazer/operation/validate.rb
|
212
|
-
- lib/trailblazer/operation/wrap.rb
|
213
203
|
- lib/trailblazer/task.rb
|
214
204
|
- lib/trailblazer/version.rb
|
215
205
|
- test/benchmark.rb
|
216
206
|
- test/deprecation/call_test.rb
|
217
207
|
- test/deprecation/context_test.rb
|
218
208
|
- test/docs/auto_inject_test.rb
|
219
|
-
- test/docs/contract_test.rb
|
220
|
-
- test/docs/dry_test.rb
|
221
209
|
- test/docs/fast_test.rb
|
222
|
-
- test/docs/guard_test.rb
|
223
210
|
- test/docs/input_output_test.rb
|
224
|
-
- test/docs/macro_test.rb
|
225
|
-
- test/docs/model_test.rb
|
226
|
-
- test/docs/nested_test.rb
|
227
211
|
- test/docs/operation_test.rb
|
228
|
-
- test/docs/policy_test.rb
|
229
|
-
- test/docs/pundit_test.rb
|
230
|
-
- test/docs/representer_test.rb
|
231
|
-
- test/docs/rescue_test.rb
|
232
212
|
- test/docs/trace_test.rb
|
233
|
-
- test/
|
213
|
+
- test/dsl/contract_test.rb
|
234
214
|
- test/gemfiles/Gemfile.ruby-1.9
|
235
215
|
- test/gemfiles/Gemfile.ruby-2.0
|
236
216
|
- test/gemfiles/Gemfile.ruby-2.3
|
237
217
|
- test/module_test.rb
|
238
|
-
- test/nested_test.rb
|
239
|
-
- test/operation/contract_test.rb
|
240
|
-
- test/operation/dsl/contract_test.rb
|
241
|
-
- test/operation/dsl/representer_test.rb
|
242
|
-
- test/operation/model_test.rb
|
243
|
-
- test/operation/persist_test.rb
|
244
|
-
- test/operation/pundit_test.rb
|
245
|
-
- test/operation/representer_test.rb
|
246
218
|
- test/test_helper.rb
|
247
219
|
- test/variables_test.rb
|
248
220
|
- trailblazer.gemspec
|
@@ -276,32 +248,14 @@ test_files:
|
|
276
248
|
- test/deprecation/call_test.rb
|
277
249
|
- test/deprecation/context_test.rb
|
278
250
|
- test/docs/auto_inject_test.rb
|
279
|
-
- test/docs/contract_test.rb
|
280
|
-
- test/docs/dry_test.rb
|
281
251
|
- test/docs/fast_test.rb
|
282
|
-
- test/docs/guard_test.rb
|
283
252
|
- test/docs/input_output_test.rb
|
284
|
-
- test/docs/macro_test.rb
|
285
|
-
- test/docs/model_test.rb
|
286
|
-
- test/docs/nested_test.rb
|
287
253
|
- test/docs/operation_test.rb
|
288
|
-
- test/docs/policy_test.rb
|
289
|
-
- test/docs/pundit_test.rb
|
290
|
-
- test/docs/representer_test.rb
|
291
|
-
- test/docs/rescue_test.rb
|
292
254
|
- test/docs/trace_test.rb
|
293
|
-
- test/
|
255
|
+
- test/dsl/contract_test.rb
|
294
256
|
- test/gemfiles/Gemfile.ruby-1.9
|
295
257
|
- test/gemfiles/Gemfile.ruby-2.0
|
296
258
|
- test/gemfiles/Gemfile.ruby-2.3
|
297
259
|
- test/module_test.rb
|
298
|
-
- test/nested_test.rb
|
299
|
-
- test/operation/contract_test.rb
|
300
|
-
- test/operation/dsl/contract_test.rb
|
301
|
-
- test/operation/dsl/representer_test.rb
|
302
|
-
- test/operation/model_test.rb
|
303
|
-
- test/operation/persist_test.rb
|
304
|
-
- test/operation/pundit_test.rb
|
305
|
-
- test/operation/representer_test.rb
|
306
260
|
- test/test_helper.rb
|
307
261
|
- test/variables_test.rb
|
@@ -1,82 +0,0 @@
|
|
1
|
-
# Best practices for using contract.
|
2
|
-
#
|
3
|
-
# * inject contract instance via constructor to #contract
|
4
|
-
# * allow contract setup and memo via #contract(model, options)
|
5
|
-
# * allow implicit automatic setup via #contract and class.contract_class
|
6
|
-
#
|
7
|
-
# Needs Operation#model.
|
8
|
-
# Needs #[], #[]= skill dependency.
|
9
|
-
module Trailblazer
|
10
|
-
class Operation
|
11
|
-
module Contract
|
12
|
-
def self.Build(name: "default", constant: nil, builder: nil)
|
13
|
-
task = ->((options, flow_options), **circuit_options) do
|
14
|
-
result = Build.(options, circuit_options, name: name, constant: constant, builder: builder)
|
15
|
-
|
16
|
-
return Activity::TaskBuilder::Binary.binary_direction_for( result, Activity::Right, Activity::Left ),
|
17
|
-
[options, flow_options]
|
18
|
-
end
|
19
|
-
|
20
|
-
{ task: task, id: "contract.build" }
|
21
|
-
end
|
22
|
-
|
23
|
-
module Build
|
24
|
-
# Build contract at runtime.
|
25
|
-
def self.call(options, circuit_options, name: "default", constant: nil, builder: nil)
|
26
|
-
# TODO: we could probably clean this up a bit at some point.
|
27
|
-
contract_class = constant || options["contract.#{name}.class"] # DISCUSS: Injection possible here?
|
28
|
-
model = options[:model]
|
29
|
-
name = "contract.#{name}"
|
30
|
-
|
31
|
-
options[name] =
|
32
|
-
if builder
|
33
|
-
call_builder( options, circuit_options, builder: builder, constant: contract_class, name: name )
|
34
|
-
else
|
35
|
-
contract_class.new(model)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def self.call_builder(options, circuit_options, builder:raise, constant:raise, name:raise)
|
40
|
-
# builder_options = Trailblazer::Context( options, constant: constant, name: name ) # options.merge( .. )
|
41
|
-
|
42
|
-
# Trailblazer::Option::KW(builder).(builder_options, circuit_options)
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
# FIXME: almost identical with Option::KW.
|
52
|
-
# FIXME: see Nested::Options::Dynamic, the same shit
|
53
|
-
tmp_options = options.to_hash.merge(
|
54
|
-
constant: constant,
|
55
|
-
name: name
|
56
|
-
)
|
57
|
-
|
58
|
-
Trailblazer::Option(builder).( options, tmp_options, circuit_options )
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
module DSL
|
63
|
-
def self.extended(extender)
|
64
|
-
extender.extend(ClassDependencies)
|
65
|
-
warn "[Trailblazer] Using `contract do...end` is deprecated. Please use a form class and the Builder( constant: <Form> ) option."
|
66
|
-
end
|
67
|
-
# This is the class level DSL method.
|
68
|
-
# Op.contract #=> returns contract class
|
69
|
-
# Op.contract do .. end # defines contract
|
70
|
-
# Op.contract CommentForm # copies (and subclasses) external contract.
|
71
|
-
# Op.contract CommentForm do .. end # copies and extends contract.
|
72
|
-
def contract(name=:default, constant=nil, base: Reform::Form, &block)
|
73
|
-
heritage.record(:contract, name, constant, &block)
|
74
|
-
|
75
|
-
path, form_class = Trailblazer::DSL::Build.new.({ prefix: :contract, class: base, container: self }, name, constant, block)
|
76
|
-
|
77
|
-
self[path] = form_class
|
78
|
-
end
|
79
|
-
end # Contract
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
class Trailblazer::Operation
|
2
|
-
module Policy
|
3
|
-
def self.Guard(proc, name: :default, &block)
|
4
|
-
Policy.step( Guard.build(proc), name: name )
|
5
|
-
end
|
6
|
-
|
7
|
-
module Guard
|
8
|
-
def self.build(callable)
|
9
|
-
option = Trailblazer::Option::KW(callable)
|
10
|
-
|
11
|
-
# this gets wrapped in a Operation::Result object.
|
12
|
-
->( (options, *), circuit_args ) do
|
13
|
-
Result.new( !!option.(options, circuit_args), {} )
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end # Guard
|
17
|
-
end
|
18
|
-
end
|
@@ -1,65 +0,0 @@
|
|
1
|
-
class Trailblazer::Operation
|
2
|
-
def self.Model(model_class, action=nil)
|
3
|
-
# step = Pipetree::Step.new(step, "model.class" => model_class, "model.action" => action)
|
4
|
-
|
5
|
-
task = Trailblazer::Activity::TaskBuilder::Binary.( Model.new )
|
6
|
-
|
7
|
-
extension = Trailblazer::Activity::TaskWrap::Merge.new(
|
8
|
-
Wrap::Inject::Defaults(
|
9
|
-
"model.class" => model_class,
|
10
|
-
"model.action" => action
|
11
|
-
)
|
12
|
-
)
|
13
|
-
|
14
|
-
{ task: task, id: "model.build", extension: [extension] }
|
15
|
-
end
|
16
|
-
|
17
|
-
class Model
|
18
|
-
def call(options, params:, **)
|
19
|
-
builder = Model::Builder.new
|
20
|
-
|
21
|
-
options[:model] = model = builder.(options, params)
|
22
|
-
|
23
|
-
options["result.model"] = result = Result.new(!model.nil?, {})
|
24
|
-
|
25
|
-
result.success?
|
26
|
-
end
|
27
|
-
|
28
|
-
class Builder
|
29
|
-
def call(options, params)
|
30
|
-
deprecate_update!(options)
|
31
|
-
action = options["model.action"] || :new
|
32
|
-
model_class = options["model.class"]
|
33
|
-
|
34
|
-
action = :pass_through unless [:new, :find_by, :find].include?(action)
|
35
|
-
|
36
|
-
send("#{action}!", model_class, params, options["model.action"])
|
37
|
-
end
|
38
|
-
|
39
|
-
def new!(model_class, params, *)
|
40
|
-
model_class.new
|
41
|
-
end
|
42
|
-
|
43
|
-
def find!(model_class, params, *)
|
44
|
-
model_class.find(params[:id])
|
45
|
-
end
|
46
|
-
|
47
|
-
# Doesn't throw an exception and will return false to divert to Left.
|
48
|
-
def find_by!(model_class, params, *)
|
49
|
-
model_class.find_by(id: params[:id])
|
50
|
-
end
|
51
|
-
|
52
|
-
# Call any method on the model class and pass :id.
|
53
|
-
def pass_through!(model_class, params, action)
|
54
|
-
model_class.send(action, params[:id])
|
55
|
-
end
|
56
|
-
|
57
|
-
private
|
58
|
-
def deprecate_update!(options) # TODO: remove in 2.1.
|
59
|
-
return unless options["model.action"] == :update
|
60
|
-
options["model.action"] = :find
|
61
|
-
warn "[Trailblazer] Model( .., :update ) is deprecated, please use :find or :find_by."
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
@@ -1,91 +0,0 @@
|
|
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
|
3
|
-
class Operation
|
4
|
-
# {Nested} macro.
|
5
|
-
def self.Nested(callable, input:nil, output:nil, id: "Nested(#{callable})")
|
6
|
-
task_wrap_extensions = Module.new do
|
7
|
-
extend Activity::Path::Plan()
|
8
|
-
end
|
9
|
-
|
10
|
-
task, operation, is_dynamic = Nested.build(callable)
|
11
|
-
|
12
|
-
if input || output # TODO: move this to the generic step DSL
|
13
|
-
task_wrap_extensions = InputOutput.plan( input, output )
|
14
|
-
end
|
15
|
-
|
16
|
-
if is_dynamic
|
17
|
-
# task_wrap_extensions += Activity::Magnetic::Builder::Path.plan do
|
18
|
-
task_wrap_extensions.task task.method(:compute_nested_activity), id: ".compute_nested_activity", after: "Start.default", group: :start
|
19
|
-
task_wrap_extensions.task task.method(:compute_return_signal), id: ".compute_return_signal", after: "task_wrap.call_task"
|
20
|
-
# end
|
21
|
-
end
|
22
|
-
|
23
|
-
{
|
24
|
-
task: task,
|
25
|
-
id: id,
|
26
|
-
extension: [ Trailblazer::Activity::TaskWrap::Merge.new(task_wrap_extensions) ],
|
27
|
-
outputs: operation.outputs
|
28
|
-
}
|
29
|
-
end
|
30
|
-
|
31
|
-
# @private
|
32
|
-
module Nested
|
33
|
-
def self.build(nested_operation) # DISCUSS: use builders here?
|
34
|
-
return dynamic = Dynamic.new(nested_operation), dynamic, true unless nestable_object?(nested_operation)
|
35
|
-
|
36
|
-
# The returned {Nested} instance is a valid circuit element and will be `call`ed in the circuit.
|
37
|
-
# It simply returns the nested activity's `signal,options,flow_options` return set.
|
38
|
-
# The actual wiring - where to go with that - is done by the step DSL.
|
39
|
-
return Trailblazer::Operation::Callable(nested_operation, call: :__call__), nested_operation, false
|
40
|
-
end
|
41
|
-
|
42
|
-
def self.nestable_object?(object)
|
43
|
-
object.is_a?( Trailblazer::Activity::Interface )
|
44
|
-
end
|
45
|
-
|
46
|
-
def self.operation_class
|
47
|
-
Operation
|
48
|
-
end
|
49
|
-
|
50
|
-
private
|
51
|
-
|
52
|
-
# For dynamic `Nested`s that do not expose an {Activity} interface.
|
53
|
-
# Since we do not know its outputs, we have to map them to :success and :failure, only.
|
54
|
-
#
|
55
|
-
# This is what {Nested} in 2.0 used to do, where the outcome could only be true/false (or success/failure).
|
56
|
-
class Dynamic
|
57
|
-
def initialize(nested_activity)
|
58
|
-
@nested_activity = Trailblazer::Option::KW(nested_activity)
|
59
|
-
@outputs = {
|
60
|
-
:success => Activity::Output( Railway::End::Success.new(semantic: :success), :success ),
|
61
|
-
:failure => Activity::Output( Railway::End::Failure.new(semantic: :failure), :failure ),
|
62
|
-
}
|
63
|
-
end
|
64
|
-
|
65
|
-
attr_reader :outputs
|
66
|
-
|
67
|
-
# TaskWrap step.
|
68
|
-
def compute_nested_activity( (wrap_ctx, original_args), **circuit_options )
|
69
|
-
(ctx, _), original_circuit_options = original_args
|
70
|
-
|
71
|
-
activity = @nested_activity.( ctx, original_circuit_options ) # evaluate the option to get the actual "object" to call.
|
72
|
-
|
73
|
-
# overwrite :task so task_wrap.call_task will call this activity. This is a trick so we don't have to repeat
|
74
|
-
# logic from #call_task here.
|
75
|
-
wrap_ctx[:task] = Trailblazer::Operation::Callable( activity, call: :__call__ )
|
76
|
-
|
77
|
-
return Activity::Right, [ wrap_ctx, original_args ]
|
78
|
-
end
|
79
|
-
|
80
|
-
def compute_return_signal( (wrap_ctx, original_args), **circuit_options )
|
81
|
-
# Translate the genuine nested signal to the generic Dynamic end (success/failure, only).
|
82
|
-
# Note that here we lose information about what specific event was emitted.
|
83
|
-
wrap_ctx[:return_signal] = wrap_ctx[:return_signal].kind_of?(Railway::End::Success) ?
|
84
|
-
@outputs[:success].signal : @outputs[:failure].signal
|
85
|
-
|
86
|
-
return Activity::Right, [ wrap_ctx, original_args ]
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end # Operation
|
91
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
module Trailblazer
|
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
|
@@ -1,44 +0,0 @@
|
|
1
|
-
class Trailblazer::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::Activity::Right : Trailblazer::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::Activity::TaskWrap::Merge.new(
|
36
|
-
Trailblazer::Operation::Wrap::Inject::Defaults(
|
37
|
-
path => condition
|
38
|
-
)
|
39
|
-
)
|
40
|
-
|
41
|
-
{ task: task, id: path, extension: [extension] }
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
@@ -1,38 +0,0 @@
|
|
1
|
-
class Trailblazer::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
|
@@ -1,36 +0,0 @@
|
|
1
|
-
class Trailblazer::Operation
|
2
|
-
module Representer
|
3
|
-
def self.infer(contract_class, format:Representable::JSON)
|
4
|
-
Disposable::Rescheme.from(contract_class,
|
5
|
-
include: [format],
|
6
|
-
options_from: :deserializer, # use :instance etc. in deserializer.
|
7
|
-
superclass: Representable::Decorator,
|
8
|
-
definitions_from: lambda { |inline| inline.definitions },
|
9
|
-
exclude_options: [:default, :populator], # TODO: test with populator: in an operation.
|
10
|
-
exclude_properties: [:persisted?]
|
11
|
-
)
|
12
|
-
end
|
13
|
-
|
14
|
-
module DSL
|
15
|
-
def self.extended(extender)
|
16
|
-
extender.extend(ClassDependencies)
|
17
|
-
warn "[Trailblazer] Using `representer do...end` is deprecated. Please use a dedicated representer class."
|
18
|
-
end
|
19
|
-
|
20
|
-
def representer(name=:default, constant=nil, &block)
|
21
|
-
heritage.record(:representer, name, constant, &block)
|
22
|
-
|
23
|
-
# FIXME: make this nicer. we want to extend same-named callback groups.
|
24
|
-
# TODO: allow the same with contract, or better, test it!
|
25
|
-
path, representer_class = Trailblazer::DSL::Build.new.({ prefix: :representer, class: representer_base_class, container: self }, name, constant, block)
|
26
|
-
|
27
|
-
self[path] = representer_class
|
28
|
-
end
|
29
|
-
|
30
|
-
# TODO: make engine configurable?
|
31
|
-
def representer_base_class
|
32
|
-
Class.new(Representable::Decorator) { include Representable::JSON; self }
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
class Trailblazer::Operation
|
2
|
-
NoopHandler = lambda { |*| }
|
3
|
-
|
4
|
-
def self.Rescue(*exceptions, handler: NoopHandler, &block)
|
5
|
-
exceptions = [StandardError] unless exceptions.any?
|
6
|
-
handler = Trailblazer::Option(handler)
|
7
|
-
|
8
|
-
# This block is evaluated by {Wrap} which currently expects a binary return type.
|
9
|
-
rescue_block = ->(options, flow_options, **circuit_options, &nested_activity) {
|
10
|
-
begin
|
11
|
-
nested_activity.call
|
12
|
-
rescue *exceptions => exception
|
13
|
-
# DISCUSS: should we deprecate this signature and rather apply the Task API here?
|
14
|
-
handler.call(exception, options, **circuit_options) # FIXME: when there's an error here, it shows the wrong exception!
|
15
|
-
false
|
16
|
-
end
|
17
|
-
}
|
18
|
-
|
19
|
-
Wrap(rescue_block, id: "Rescue(#{rand(100)})", &block)
|
20
|
-
# FIXME: name
|
21
|
-
# [ step, name: "Rescue:#{block.source_location.last}" ]
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
@@ -1,74 +0,0 @@
|
|
1
|
-
module Trailblazer
|
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) # 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 ).freeze
|
13
|
-
|
14
|
-
# Build a simple Railway {Activity} for the internal flow.
|
15
|
-
activity = Module.new do
|
16
|
-
extend Activity::Railway()
|
17
|
-
|
18
|
-
step extract, id: "#{params_path}_extract" unless skip_extract || representer
|
19
|
-
step validate, id: "contract.#{name}.call"
|
20
|
-
end
|
21
|
-
|
22
|
-
# activity, _ = activity.decompose
|
23
|
-
|
24
|
-
# DISCUSS: use Nested here?
|
25
|
-
{ task: activity, id: "contract.#{name}.validate", outputs: activity.outputs }
|
26
|
-
end
|
27
|
-
|
28
|
-
class Validate
|
29
|
-
# Task: extract the contract's input from params by reading `:key`.
|
30
|
-
class Extract
|
31
|
-
def initialize(key:nil, params_path:nil)
|
32
|
-
@key, @params_path = key, params_path
|
33
|
-
end
|
34
|
-
|
35
|
-
def call( ctx, params:, ** )
|
36
|
-
ctx[@params_path] = @key ? params[@key] : params
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def initialize(name:"default", representer:false, params_path:nil)
|
41
|
-
@name, @representer, @params_path = name, representer, params_path
|
42
|
-
end
|
43
|
-
|
44
|
-
# Task: Validates contract `:name`.
|
45
|
-
def call( ctx, ** )
|
46
|
-
validate!(
|
47
|
-
ctx,
|
48
|
-
representer: ctx["representer.#{@name}.class"] ||= @representer, # FIXME: maybe @representer should use DI.
|
49
|
-
params_path: @params_path
|
50
|
-
)
|
51
|
-
end
|
52
|
-
|
53
|
-
def validate!(options, representer:false, from: :document, params_path:nil)
|
54
|
-
path = "contract.#{@name}"
|
55
|
-
contract = options[path]
|
56
|
-
|
57
|
-
# this is for 1.1-style compatibility and should be removed once we have Deserializer in place:
|
58
|
-
options["result.#{path}"] = result =
|
59
|
-
if representer
|
60
|
-
# use :document as the body and let the representer deserialize to the contract.
|
61
|
-
# this will be simplified once we have Deserializer.
|
62
|
-
# translates to contract.("{document: bla}") { MyRepresenter.new(contract).from_json .. }
|
63
|
-
contract.(options[from]) { |document| representer.new(contract).parse(document) }
|
64
|
-
else
|
65
|
-
# let Reform handle the deserialization.
|
66
|
-
contract.(options[params_path])
|
67
|
-
end
|
68
|
-
|
69
|
-
result.success?
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end # Operation
|
74
|
-
end
|