trailblazer-macro-contract 2.1.1 → 2.1.3
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 +4 -4
- data/.github/workflows/ci.yml +17 -0
- data/.rubocop-https---raw-githubusercontent-com-trailblazer-meta-master-rubocop-yml +15 -6
- data/CHANGES.md +11 -0
- data/Gemfile +2 -1
- data/Rakefile +1 -1
- data/lib/trailblazer/macro/contract/build.rb +49 -28
- data/lib/trailblazer/macro/contract/validate.rb +23 -16
- data/lib/trailblazer/macro/contract/version.rb +1 -1
- data/lib/trailblazer/macro/contract.rb +1 -3
- data/test/docs/contract_test.rb +129 -22
- data/trailblazer-macro-contract.gemspec +2 -3
- metadata +9 -23
- data/.travis.yml +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44b6ac6ab9a1efc431188b24d22ce9882a9ca35ea3a726d14267be789bd66973
|
4
|
+
data.tar.gz: 77ad5f9fd06aa6b51117b2fa5d9ad037283e4107402b66cbc852a83423387a25
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 50d6644a6a4978bfe8ce4f49b973b36b38309221bcab324203cfd3fde5d542e348d52b03311a5b1d9e3ed959e83194921f51c4e0f66fa8aa33728235bcfdf9d9
|
7
|
+
data.tar.gz: a96051e482288a86001857773644f74c3a2749c975207016fe45efe09b5ec3f2b96edd491332f9baa9692c42eebf03bf265e09f15252dc43077c50ab664232de
|
@@ -0,0 +1,17 @@
|
|
1
|
+
name: CI
|
2
|
+
on: [push, pull_request]
|
3
|
+
jobs:
|
4
|
+
test:
|
5
|
+
strategy:
|
6
|
+
fail-fast: false
|
7
|
+
matrix:
|
8
|
+
# Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0'
|
9
|
+
ruby: [2.6, 2.7, '3.0', jruby]
|
10
|
+
runs-on: ubuntu-latest
|
11
|
+
steps:
|
12
|
+
- uses: actions/checkout@v2
|
13
|
+
- uses: ruby/setup-ruby@v1
|
14
|
+
with:
|
15
|
+
ruby-version: ${{ matrix.ruby }}
|
16
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
17
|
+
- run: bundle exec rake
|
@@ -26,6 +26,8 @@ Style/AndOr:
|
|
26
26
|
EnforcedStyle: conditionals
|
27
27
|
Style/AutoResourceCleanup:
|
28
28
|
Enabled: true
|
29
|
+
Style/CollectionMethods:
|
30
|
+
Enabled: true
|
29
31
|
Style/Documentation:
|
30
32
|
Enabled: false
|
31
33
|
Style/EmptyLiteral:
|
@@ -47,6 +49,13 @@ Style/NumericLiterals:
|
|
47
49
|
Enabled: false
|
48
50
|
Style/OptionHash:
|
49
51
|
Enabled: true
|
52
|
+
Style/PercentLiteralDelimiters:
|
53
|
+
PreferredDelimiters:
|
54
|
+
"%w": "[]"
|
55
|
+
"%W": "[]"
|
56
|
+
"%i": "[]"
|
57
|
+
"%I": "[]"
|
58
|
+
"%r": "()"
|
50
59
|
Style/ReturnNil:
|
51
60
|
Enabled: true
|
52
61
|
Style/SafeNavigation:
|
@@ -85,24 +94,27 @@ Lint/UnreachableCode:
|
|
85
94
|
Enabled: false
|
86
95
|
Lint/Void:
|
87
96
|
Enabled: false
|
97
|
+
Layout/AlignHash:
|
98
|
+
EnforcedLastArgumentHashStyle: ignore_implicit
|
88
99
|
Metrics/AbcSize:
|
89
100
|
Max: 25
|
90
101
|
Style/LambdaCall:
|
91
102
|
Enabled: false
|
92
103
|
Style/Semicolon:
|
93
104
|
Enabled: false
|
94
|
-
Naming/
|
105
|
+
Naming/UncommunicativeMethodParamName:
|
95
106
|
Enabled: false
|
96
107
|
Style/ClassAndModuleChildren:
|
97
108
|
Enabled: false
|
98
109
|
Layout/LeadingCommentSpace:
|
99
110
|
Exclude:
|
100
111
|
- 'test/docs/**/*'
|
101
|
-
Layout/
|
112
|
+
Layout/AlignHash:
|
102
113
|
EnforcedHashRocketStyle: table
|
103
|
-
EnforcedColonStyle: table
|
104
114
|
Style/FrozenStringLiteralComment:
|
105
115
|
Enabled: false
|
116
|
+
Layout/AlignHash:
|
117
|
+
EnforcedColonStyle: table
|
106
118
|
SingleLineMethods:
|
107
119
|
Enabled: false
|
108
120
|
Style/Lambda:
|
@@ -120,8 +132,5 @@ Style/PercentLiteralDelimiters:
|
|
120
132
|
"%r": '{}'
|
121
133
|
"%w": '[]'
|
122
134
|
"%": '{}'
|
123
|
-
"%W": '[]'
|
124
|
-
"%i": '[]'
|
125
|
-
"%I": '[]'
|
126
135
|
Style/HashSyntax:
|
127
136
|
Enabled: false
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
# 2.1.3
|
2
|
+
|
3
|
+
* Use `trailblazer-activity-dsl-linear` >= 1.0.0.
|
4
|
+
* Use Inject() API instead of `:inject`, `:input` etc in macros.
|
5
|
+
|
6
|
+
# 2.1.2
|
7
|
+
|
8
|
+
* Refactor `Contract::Build` to use TRB mechanics:
|
9
|
+
* `:input` and `:inject` to allow injection of the contract class.
|
10
|
+
* an `Option()` to wrap the builder code.
|
11
|
+
|
1
12
|
# 2.1.1
|
2
13
|
|
3
14
|
* Support for Ruby 3.0.
|
data/Gemfile
CHANGED
@@ -9,7 +9,8 @@ gem "dry-matcher"
|
|
9
9
|
# gem "trailblazer-macro", path: "../trailblazer-macro"
|
10
10
|
# gem "trailblazer-activity", path: "../trailblazer-activity"
|
11
11
|
# gem "trailblazer-activity-dsl-linear", path: "../trailblazer-activity-dsl-linear"
|
12
|
-
# gem "
|
12
|
+
# gem "trailblazer-errors", path: "../trailblazer-errors"
|
13
|
+
# gem "trailblazer-developer", path: "../trailblazer-developer"
|
13
14
|
|
14
15
|
gem "minitest-line"
|
15
16
|
|
data/Rakefile
CHANGED
@@ -2,41 +2,62 @@ require "reform"
|
|
2
2
|
|
3
3
|
module Trailblazer
|
4
4
|
module Macro
|
5
|
+
# This Circuit-task calls the {task} Option, then allows
|
6
|
+
# to run an arbitary block to process the option's result.
|
7
|
+
# @private
|
8
|
+
class CircuitTaskWithResultProcessing < Activity::TaskBuilder::Task # DISCUSS: extract to public?
|
9
|
+
def initialize(task, user_proc, block)
|
10
|
+
@block = block
|
11
|
+
super(task, user_proc)
|
12
|
+
end
|
13
|
+
|
14
|
+
def call_option(task_with_option_interface, (ctx, flow_options), **circuit_options)
|
15
|
+
result = super
|
16
|
+
|
17
|
+
@block.call(result, ctx)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
5
21
|
module Contract
|
6
22
|
def self.Build(name: "default", constant: nil, builder: nil)
|
7
|
-
|
8
|
-
result = Build.(options, circuit_options, name: name, constant: constant, builder: builder)
|
23
|
+
contract_path = :"contract.#{name}"
|
9
24
|
|
10
|
-
|
11
|
-
|
12
|
-
|
25
|
+
injections = {
|
26
|
+
Activity::Railway.Inject() => {
|
27
|
+
"#{contract_path}.class": ->(*) { constant }, # default to {constant} if not injected.
|
28
|
+
}
|
29
|
+
}
|
13
30
|
|
14
|
-
|
15
|
-
|
31
|
+
# DISCUSS: can we force-default this via Inject()?
|
32
|
+
input = {
|
33
|
+
Activity::Railway.In() => ->(ctx, **) do
|
34
|
+
ctx.to_hash.merge(
|
35
|
+
constant: constant,
|
36
|
+
name: contract_path
|
37
|
+
)
|
38
|
+
end
|
39
|
+
}
|
16
40
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
# TODO: we could probably clean this up a bit at some point.
|
21
|
-
contract_class = constant || options[:"contract.#{name}.class"] # DISCUSS: Injection possible here?
|
22
|
-
model = options[:model]
|
23
|
-
name = :"contract.#{name}"
|
24
|
-
|
25
|
-
options[name] = if builder
|
26
|
-
call_builder(options, circuit_options, builder: builder, constant: contract_class, name: name)
|
27
|
-
else
|
28
|
-
contract_class.new(model)
|
29
|
-
end
|
30
|
-
end
|
41
|
+
output = {
|
42
|
+
Activity::Railway.Out() => [contract_path]
|
43
|
+
}
|
31
44
|
|
32
|
-
|
33
|
-
tmp_options = ctx.to_hash.merge(
|
34
|
-
constant: constant,
|
35
|
-
name: name
|
36
|
-
)
|
45
|
+
default_contract_builder = ->(ctx, model: nil, **) { ctx[:"#{contract_path}.class"].new(model) }
|
37
46
|
|
38
|
-
|
39
|
-
|
47
|
+
# proc is called via {Option()}.
|
48
|
+
task_option_proc = builder ? builder : default_contract_builder
|
49
|
+
|
50
|
+
# after the builder proc is run, assign its result to {:"contract.default"}.
|
51
|
+
ctx_assign_block = ->(result, ctx) { ctx[contract_path] = result }
|
52
|
+
|
53
|
+
task = CircuitTaskWithResultProcessing.new(Trailblazer::Option(task_option_proc), task_option_proc, ctx_assign_block)
|
54
|
+
|
55
|
+
{
|
56
|
+
task: task, id: "contract.build",
|
57
|
+
}.
|
58
|
+
merge(injections).
|
59
|
+
merge(input).
|
60
|
+
merge(output)
|
40
61
|
end
|
41
62
|
|
42
63
|
module DSL
|
@@ -6,15 +6,22 @@ module Trailblazer
|
|
6
6
|
# Deviate to left track if optional key is not found in params.
|
7
7
|
# Deviate to left if validation result falsey.
|
8
8
|
def self.Validate(skip_extract: false, name: "default", representer: false, key: nil, constant: nil, invalid_data_terminus: false) # DISCUSS: should we introduce something like Validate::Deserializer?
|
9
|
-
|
9
|
+
contract_path = :"contract.#{name}" # the contract instance
|
10
|
+
params_path = :"contract.#{name}.params" # extract_params! save extracted params here.
|
11
|
+
key_path = :"contract.#{name}.extract_key"
|
10
12
|
|
11
|
-
extract = Validate::Extract.new(
|
12
|
-
validate = Validate.new(name: name, representer: representer, params_path: params_path,
|
13
|
+
extract = Validate::Extract.new(key_path: key_path, params_path: params_path)
|
14
|
+
validate = Validate.new(name: name, representer: representer, params_path: params_path, contract_path: contract_path)
|
15
|
+
|
16
|
+
# These are defaulting dependency injection, more here
|
17
|
+
# https://trailblazer.to/2.1/docs/activity.html#activity-dependency-injection-inject-defaulting
|
18
|
+
extract_injections = {key_path => ->(*) { key }} # default to {key} if not injected.
|
19
|
+
validate_injections = {contract_path => ->(*) { constant }} # default the contract instance to {constant}, if not injected (or passed down from {Build()})
|
13
20
|
|
14
21
|
# Build a simple Railway {Activity} for the internal flow.
|
15
22
|
activity = Class.new(Activity::Railway(name: "Contract::Validate")) do
|
16
|
-
step extract, id: "#{params_path}_extract", Output(:failure) => End(:extract_failure) unless skip_extract# || representer
|
17
|
-
step validate, id: "contract.#{name}.call"
|
23
|
+
step extract, id: "#{params_path}_extract", Output(:failure) => End(:extract_failure), Activity::Railway.Inject() => extract_injections unless skip_extract# || representer
|
24
|
+
step validate, id: "contract.#{name}.call", Activity::Railway.Inject() => validate_injections
|
18
25
|
end
|
19
26
|
|
20
27
|
options = activity.Subprocess(activity)
|
@@ -32,17 +39,18 @@ module Trailblazer
|
|
32
39
|
class Validate
|
33
40
|
# Task: extract the contract's input from params by reading `:key`.
|
34
41
|
class Extract
|
35
|
-
def initialize(
|
36
|
-
@
|
42
|
+
def initialize(key_path: nil, params_path: nil)
|
43
|
+
@key_path, @params_path = key_path, params_path
|
37
44
|
end
|
38
45
|
|
39
46
|
def call(ctx, params: {}, **)
|
40
|
-
ctx[@
|
47
|
+
key = ctx[@key_path] # e.g. {:song}.
|
48
|
+
ctx[@params_path] = key ? params[key] : params
|
41
49
|
end
|
42
50
|
end
|
43
51
|
|
44
|
-
def initialize(name: "default", representer: false, params_path: nil,
|
45
|
-
@name, @representer, @params_path, @
|
52
|
+
def initialize(name: "default", representer: false, params_path: nil, contract_path: )
|
53
|
+
@name, @representer, @params_path, @contract_path = name, representer, params_path, contract_path
|
46
54
|
end
|
47
55
|
|
48
56
|
# Task: Validates contract `:name`.
|
@@ -54,20 +62,19 @@ module Trailblazer
|
|
54
62
|
)
|
55
63
|
end
|
56
64
|
|
57
|
-
def validate!(
|
58
|
-
|
59
|
-
contract = @constant || options[path]
|
65
|
+
def validate!(ctx, representer: false, from: :document, params_path: nil)
|
66
|
+
contract = ctx[@contract_path] # grab contract instance from "contract.default" (usually set in {Contract::Build()})
|
60
67
|
|
61
68
|
# this is for 1.1-style compatibility and should be removed once we have Deserializer in place:
|
62
|
-
|
69
|
+
ctx[:"result.#{@contract_path}"] = result =
|
63
70
|
if representer
|
64
71
|
# use :document as the body and let the representer deserialize to the contract.
|
65
72
|
# this will be simplified once we have Deserializer.
|
66
73
|
# translates to contract.("{document: bla}") { MyRepresenter.new(contract).from_json .. }
|
67
|
-
contract.(
|
74
|
+
contract.(ctx[from]) { |document| representer.new(contract).parse(document) }
|
68
75
|
else
|
69
76
|
# let Reform handle the deserialization.
|
70
|
-
contract.(
|
77
|
+
contract.(ctx[params_path])
|
71
78
|
end
|
72
79
|
|
73
80
|
result.success?
|
@@ -13,7 +13,5 @@ module Trailblazer
|
|
13
13
|
|
14
14
|
# All macros sit in the {Trailblazer::Macro::Contract} namespace, where we forward calls from
|
15
15
|
# operations and activities to.
|
16
|
-
|
17
|
-
Contract = Macro::Contract
|
18
|
-
end
|
16
|
+
Activity::DSL::Linear::Helper::Constants::Contract = Macro::Contract
|
19
17
|
end
|
data/test/docs/contract_test.rb
CHANGED
@@ -67,7 +67,11 @@ class DocsContractOverviewTest < Minitest::Spec
|
|
67
67
|
| `-- End.failure
|
68
68
|
`-- End.failure}
|
69
69
|
end
|
70
|
+
|
71
|
+
# internal variables from {:builder} are excluded in public ctx.
|
72
|
+
it { Create.(params: {}).keys.inspect.must_equal %{[:params, :model, :\"result.model\", :\"contract.default\", :\"contract.default.params\", :\"representer.default.class\", :\"result.contract.default\"]} }
|
70
73
|
end
|
74
|
+
|
71
75
|
#---
|
72
76
|
# contract MyContract
|
73
77
|
class DocsContractExplicitTest < Minitest::Spec
|
@@ -257,6 +261,42 @@ class DocsContractKeyTest < Minitest::Spec
|
|
257
261
|
end
|
258
262
|
end
|
259
263
|
|
264
|
+
#---
|
265
|
+
#- Validate() with injected {:"contract.default.extract_key"}
|
266
|
+
class DocsContractInjectedKeyTest < Minitest::Spec
|
267
|
+
Song = Class.new(ContractConstantTest::Song)
|
268
|
+
|
269
|
+
module Song::Contract
|
270
|
+
Create = ContractConstantTest::Song::Contract::Create
|
271
|
+
end
|
272
|
+
|
273
|
+
#:inject-key-op
|
274
|
+
class Song::Create < Trailblazer::Operation
|
275
|
+
#~meths
|
276
|
+
step Model(Song, :new)
|
277
|
+
step Contract::Build(constant: Song::Contract::Create)
|
278
|
+
#~meths end
|
279
|
+
step Contract::Validate() # we don't define a key here! E.g. {key: "song"}
|
280
|
+
step Contract::Persist()
|
281
|
+
end
|
282
|
+
#:inject-key-op end
|
283
|
+
|
284
|
+
# empty {:params}, validation fails
|
285
|
+
it { Song::Create.(params: {}).inspect(:model, "result.contract.default.extract").must_equal %{<Result:false [#<struct DocsContractInjectedKeyTest::Song title=nil, length=nil>, nil] >} }
|
286
|
+
# no {:key} injected/defined, we don't find the data in {params}.
|
287
|
+
it { Song::Create.(params: {"song" => { title: "SVG", length: 13 }}).inspect(:model, "result.contract.default.extract").must_equal %{<Result:false [#<struct DocsContractInjectedKeyTest::Song title=nil, length=nil>, nil] >} }
|
288
|
+
# {:key} defined and everything works smoothly
|
289
|
+
it {
|
290
|
+
params = {"song" => { title: "SVG", length: 13 }}
|
291
|
+
#:inject-key-call
|
292
|
+
res = Song::Create.(
|
293
|
+
params: params,
|
294
|
+
"contract.default.extract_key": "song"
|
295
|
+
)
|
296
|
+
#:inject-key-call end
|
297
|
+
res.inspect(:model).must_equal %{<Result:true [#<struct DocsContractInjectedKeyTest::Song title=\"SVG\", length=13>] >} }
|
298
|
+
end
|
299
|
+
|
260
300
|
#---
|
261
301
|
#- Validate( key: :song ), Output(:extract_failure) => End(:my_new_end)
|
262
302
|
class DocsContractKeyWithOutputTest < Minitest::Spec
|
@@ -297,6 +337,37 @@ class DocsContractKeyWithOutputTest < Minitest::Spec
|
|
297
337
|
end
|
298
338
|
end
|
299
339
|
|
340
|
+
#---
|
341
|
+
#- Validate() with injected {:"contract.default"} and no `Build()`.
|
342
|
+
class DocsContractInjectedContractTest < Minitest::Spec
|
343
|
+
Song = Class.new(ContractConstantTest::Song)
|
344
|
+
|
345
|
+
module Song::Contract
|
346
|
+
Create = ContractConstantTest::Song::Contract::Create
|
347
|
+
end
|
348
|
+
|
349
|
+
#:inject-contract-op
|
350
|
+
class Song::Create < Trailblazer::Operation
|
351
|
+
# we omit the {Model()} call as the run-time contract contains the model.
|
352
|
+
# we don't have a {Contract::Build()} step here.
|
353
|
+
step Contract::Validate(key: "song") # you could use an injection here, too!
|
354
|
+
step Contract::Persist()
|
355
|
+
end
|
356
|
+
#:inject-contract-op end
|
357
|
+
|
358
|
+
it {
|
359
|
+
params = {"song" => { title: "SVG", length: 13 }}
|
360
|
+
#:inject-contract-call
|
361
|
+
res = Song::Create.(
|
362
|
+
params: params,
|
363
|
+
"contract.default": Song::Contract::Create.new(Song.new) # we build the contract ourselves!
|
364
|
+
)
|
365
|
+
#:inject-contract-call end
|
366
|
+
res.inspect(:model).must_equal %{<Result:true [nil] >}
|
367
|
+
res[:"contract.default"].model.inspect.must_equal %{#<struct DocsContractInjectedContractTest::Song title=\"SVG\", length=13>}
|
368
|
+
}
|
369
|
+
end
|
370
|
+
|
300
371
|
#---
|
301
372
|
#- Validate( name: "default", invalid_data_terminus: true )
|
302
373
|
class DocsContractInvalidEndTest < Minitest::Spec
|
@@ -377,24 +448,24 @@ class ContractInjectConstantTest < Minitest::Spec
|
|
377
448
|
end
|
378
449
|
#:di-constant-contract end
|
379
450
|
#:di-constant
|
380
|
-
class Create < Trailblazer::Operation
|
381
|
-
step Model(
|
382
|
-
step Contract::Build()
|
451
|
+
class Song::Create < Trailblazer::Operation
|
452
|
+
step Model(Song, :new)
|
453
|
+
step Contract::Build() # no constant provided here!
|
383
454
|
step Contract::Validate()
|
384
|
-
step Contract::Persist(
|
455
|
+
step Contract::Persist(method: :sync)
|
385
456
|
end
|
386
457
|
#:di-constant end
|
387
458
|
|
388
459
|
it do
|
389
460
|
#:di-contract-call
|
390
|
-
Create.(
|
391
|
-
params:
|
392
|
-
|
461
|
+
Song::Create.(
|
462
|
+
params: { title: "Anthony's Song" },
|
463
|
+
"contract.default.class": MyContract # dependency injection!
|
393
464
|
)
|
394
465
|
#:di-contract-call end
|
395
466
|
end
|
396
|
-
it { Create.(params: { title: "A" }, :"contract.default.class" => MyContract).inspect(:model).must_equal %{<Result:false [#<struct ContractInjectConstantTest::Song id=nil, title=nil>] >} }
|
397
|
-
it { Create.(params: { title: "Anthony's Song" }, :"contract.default.class" => MyContract).inspect(:model).must_equal %{<Result:true [#<struct ContractInjectConstantTest::Song id=nil, title="Anthony's Song">] >} }
|
467
|
+
it { Song::Create.(params: { title: "A" }, :"contract.default.class" => MyContract).inspect(:model).must_equal %{<Result:false [#<struct ContractInjectConstantTest::Song id=nil, title=nil>] >} }
|
468
|
+
it { Song::Create.(params: { title: "Anthony's Song" }, :"contract.default.class" => MyContract).inspect(:model).must_equal %{<Result:true [#<struct ContractInjectConstantTest::Song id=nil, title="Anthony's Song">] >} }
|
398
469
|
end
|
399
470
|
|
400
471
|
class DryValidationContractTest < Minitest::Spec
|
@@ -446,30 +517,47 @@ class DryValidationContractTest < Minitest::Spec
|
|
446
517
|
it { Create.(params: { id: 1, title: "Y" }).inspect(:model).must_equal %{<Result:false [#<struct DryValidationContractTest::Song id=nil, title=nil>] >} }
|
447
518
|
it { Create.(params: { id: 1, title: "Yo" }).inspect(:model).must_equal %{<Result:true [#<struct DryValidationContractTest::Song id=1, title="Yo">] >} }
|
448
519
|
|
449
|
-
|
520
|
+
##---
|
450
521
|
# Contract::Validate(constant: DrySchema)
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
522
|
+
|
523
|
+
#:dry-schema-contract
|
524
|
+
module Song::Operation
|
525
|
+
class Archive < Trailblazer::Operation
|
526
|
+
Schema = Dry::Validation.Contract do
|
527
|
+
params do
|
528
|
+
required(:id).filled
|
529
|
+
end
|
455
530
|
end
|
456
|
-
end
|
457
531
|
|
458
|
-
|
459
|
-
|
532
|
+
# step Model(Song, :new) # You don't need {ctx[:model]}.
|
533
|
+
step Contract::Validate(constant: Schema, key: :song) # Your validation.
|
534
|
+
#~methods
|
535
|
+
# step Contract::Persist() # this is not possible!
|
536
|
+
#~methods end
|
537
|
+
end
|
460
538
|
end
|
539
|
+
#:dry-schema-contract end
|
461
540
|
|
462
541
|
# success
|
463
|
-
it {
|
542
|
+
it { _(Song::Operation::Archive.(params: {song: {id: "SVG"}}).success?).must_equal true }
|
464
543
|
# failure
|
465
|
-
it {
|
544
|
+
it { _(Song::Operation::Archive.(params: {song: {id: nil}}).success?).must_equal false }
|
545
|
+
# shows error messages
|
466
546
|
it "shows error messages" do
|
467
|
-
|
547
|
+
#:dry-contract-call
|
548
|
+
result = Song::Operation::Archive.(params: {song: {id: nil}})
|
549
|
+
#:dry-contract-call end
|
468
550
|
|
469
|
-
result[:"result.contract.default"].errors.inspect.must_equal %{#<Dry::Validation::MessageSet messages=[#<Dry::Schema::Message text=\"must be filled\" path=[:
|
551
|
+
_(result[:"result.contract.default"].errors.inspect).must_equal %{#<Dry::Validation::MessageSet messages=[#<Dry::Schema::Message text=\"must be filled\" path=[:id] predicate=:filled? input=nil>] options={:source=>[#<Dry::Schema::Message text=\"must be filled\" path=[:id] predicate=:filled? input=nil>], :hints=>false}>}
|
552
|
+
|
553
|
+
# raise result[:"result.contract.default"].errors.messages[0].to_s.inspect
|
554
|
+
assert_equal result[:"result.contract.default"].errors[:id].inspect, %{["must be filled"]}
|
555
|
+
#:dry-contract-result
|
556
|
+
result[:"result.contract.default"].errors[:id] #=> ["must be filled"]
|
557
|
+
#:dry-contract-result end
|
470
558
|
end
|
471
559
|
# key not found
|
472
|
-
it {
|
560
|
+
it { _(Song::Operation::Archive.(params: {}).success?).must_equal false }
|
473
561
|
end
|
474
562
|
|
475
563
|
class DocContractBuilderTest < Minitest::Spec
|
@@ -505,6 +593,8 @@ class DocContractBuilderTest < Minitest::Spec
|
|
505
593
|
|
506
594
|
it { Create.(params: {}).inspect(:model).must_equal %{<Result:false [#<struct DocContractBuilderTest::Song id=nil, title=nil>] >} }
|
507
595
|
it { Create.(params: { title: "title"}, current_user: Module).inspect(:model).must_equal %{<Result:true [#<struct DocContractBuilderTest::Song id=nil, title="title">] >} }
|
596
|
+
# internal variables from {:builder} are excluded in public ctx.
|
597
|
+
it { Create.(params: {}).keys.inspect.must_equal %{[:params, :model, :\"result.model\", :\"contract.default\", :\"contract.default.params\", :\"representer.default.class\", :\"result.contract.default\"]} }
|
508
598
|
end
|
509
599
|
|
510
600
|
class DocContractTest < Minitest::Spec
|
@@ -547,3 +637,20 @@ class DocContractTest < Minitest::Spec
|
|
547
637
|
|
548
638
|
it { Break.(params: { id:1, title: "Fame" }).inspect(:model).must_equal %{<Result:true [#<struct DocContractTest::Song id=1, title=nil>] >} }
|
549
639
|
end
|
640
|
+
|
641
|
+
class ModelMissingTest < Minitest::Spec
|
642
|
+
class Create < Trailblazer::Operation
|
643
|
+
class MyContract < Reform::Form
|
644
|
+
property :duration, virtual: true
|
645
|
+
end
|
646
|
+
|
647
|
+
step Contract::Build(constant: MyContract)
|
648
|
+
step Contract::Validate()
|
649
|
+
end
|
650
|
+
|
651
|
+
it do
|
652
|
+
result = Create.(params: {duration: 18})
|
653
|
+
assert_equal true, result.success?
|
654
|
+
assert_equal 18, result[:"contract.default"].duration
|
655
|
+
end
|
656
|
+
end
|
@@ -23,15 +23,14 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_development_dependency "bundler"
|
24
24
|
spec.add_development_dependency "dry-validation"
|
25
25
|
spec.add_development_dependency "reform-rails", "~> 0.2.0.rc2"
|
26
|
-
spec.add_development_dependency "trailblazer-
|
27
|
-
spec.add_development_dependency "trailblazer-macro", ">= 2.1.5"
|
26
|
+
spec.add_development_dependency "trailblazer-macro", ">= 2.1.9"
|
28
27
|
spec.add_development_dependency "trailblazer-developer"
|
29
28
|
spec.add_development_dependency "activemodel", "~> 6.0.0" # FIXME: we still don't support the Rails 6.1 errors object.
|
30
29
|
|
31
30
|
spec.add_development_dependency "minitest"
|
32
31
|
spec.add_development_dependency "rake"
|
33
32
|
|
34
|
-
spec.add_dependency "trailblazer-activity-dsl-linear", ">= 0.
|
33
|
+
spec.add_dependency "trailblazer-activity-dsl-linear", ">= 1.0.0", "< 1.1.0"
|
35
34
|
|
36
35
|
spec.required_ruby_version = ">= 2.0.0"
|
37
36
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trailblazer-macro-contract
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.1.
|
4
|
+
version: 2.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-08-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: reform
|
@@ -72,34 +72,20 @@ dependencies:
|
|
72
72
|
- - "~>"
|
73
73
|
- !ruby/object:Gem::Version
|
74
74
|
version: 0.2.0.rc2
|
75
|
-
- !ruby/object:Gem::Dependency
|
76
|
-
name: trailblazer-operation
|
77
|
-
requirement: !ruby/object:Gem::Requirement
|
78
|
-
requirements:
|
79
|
-
- - ">="
|
80
|
-
- !ruby/object:Gem::Version
|
81
|
-
version: 0.6.2
|
82
|
-
type: :development
|
83
|
-
prerelease: false
|
84
|
-
version_requirements: !ruby/object:Gem::Requirement
|
85
|
-
requirements:
|
86
|
-
- - ">="
|
87
|
-
- !ruby/object:Gem::Version
|
88
|
-
version: 0.6.2
|
89
75
|
- !ruby/object:Gem::Dependency
|
90
76
|
name: trailblazer-macro
|
91
77
|
requirement: !ruby/object:Gem::Requirement
|
92
78
|
requirements:
|
93
79
|
- - ">="
|
94
80
|
- !ruby/object:Gem::Version
|
95
|
-
version: 2.1.
|
81
|
+
version: 2.1.9
|
96
82
|
type: :development
|
97
83
|
prerelease: false
|
98
84
|
version_requirements: !ruby/object:Gem::Requirement
|
99
85
|
requirements:
|
100
86
|
- - ">="
|
101
87
|
- !ruby/object:Gem::Version
|
102
|
-
version: 2.1.
|
88
|
+
version: 2.1.9
|
103
89
|
- !ruby/object:Gem::Dependency
|
104
90
|
name: trailblazer-developer
|
105
91
|
requirement: !ruby/object:Gem::Requirement
|
@@ -162,20 +148,20 @@ dependencies:
|
|
162
148
|
requirements:
|
163
149
|
- - ">="
|
164
150
|
- !ruby/object:Gem::Version
|
165
|
-
version: 0.
|
151
|
+
version: 1.0.0
|
166
152
|
- - "<"
|
167
153
|
- !ruby/object:Gem::Version
|
168
|
-
version:
|
154
|
+
version: 1.1.0
|
169
155
|
type: :runtime
|
170
156
|
prerelease: false
|
171
157
|
version_requirements: !ruby/object:Gem::Requirement
|
172
158
|
requirements:
|
173
159
|
- - ">="
|
174
160
|
- !ruby/object:Gem::Version
|
175
|
-
version: 0.
|
161
|
+
version: 1.0.0
|
176
162
|
- - "<"
|
177
163
|
- !ruby/object:Gem::Version
|
178
|
-
version:
|
164
|
+
version: 1.1.0
|
179
165
|
description: Operation macros for form objects
|
180
166
|
email:
|
181
167
|
- apotonick@gmail.com
|
@@ -183,11 +169,11 @@ executables: []
|
|
183
169
|
extensions: []
|
184
170
|
extra_rdoc_files: []
|
185
171
|
files:
|
172
|
+
- ".github/workflows/ci.yml"
|
186
173
|
- ".gitignore"
|
187
174
|
- ".rubocop-https---raw-githubusercontent-com-trailblazer-meta-master-rubocop-yml"
|
188
175
|
- ".rubocop.yml"
|
189
176
|
- ".rubocop_todo.yml"
|
190
|
-
- ".travis.yml"
|
191
177
|
- CHANGES.md
|
192
178
|
- COMM-LICENSE
|
193
179
|
- Gemfile
|