trailblazer 2.0.7 → 2.1.0
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 +5 -5
- data/.gitignore +1 -0
- data/.rubocop-https---raw-githubusercontent-com-trailblazer-meta-master-rubocop-yml +101 -0
- data/.rubocop.yml +20 -0
- data/.rubocop_todo.yml +556 -0
- data/.travis.yml +6 -10
- data/CHANGES.md +83 -1
- data/COMM-LICENSE +46 -75
- data/CONTRIBUTING.md +179 -0
- data/Gemfile +0 -27
- data/{LICENSE.txt → LICENSE} +4 -4
- data/README.md +39 -138
- data/Rakefile +2 -19
- data/lib/trailblazer.rb +3 -17
- data/lib/trailblazer/version.rb +3 -1
- data/test/test_helper.rb +12 -3
- data/trailblazer.gemspec +10 -14
- metadata +22 -147
- data/doc/Trb-The-Stack.png +0 -0
- data/doc/operation-2017.png +0 -0
- data/doc/trb.jpg +0 -0
- data/lib/trailblazer/dsl.rb +0 -47
- data/lib/trailblazer/operation/auto_inject.rb +0 -47
- data/lib/trailblazer/operation/callback.rb +0 -35
- data/lib/trailblazer/operation/contract.rb +0 -46
- data/lib/trailblazer/operation/guard.rb +0 -18
- data/lib/trailblazer/operation/model.rb +0 -60
- data/lib/trailblazer/operation/module.rb +0 -29
- data/lib/trailblazer/operation/nested.rb +0 -113
- data/lib/trailblazer/operation/persist.rb +0 -10
- data/lib/trailblazer/operation/policy.rb +0 -35
- data/lib/trailblazer/operation/procedural/contract.rb +0 -15
- data/lib/trailblazer/operation/procedural/validate.rb +0 -22
- data/lib/trailblazer/operation/pundit.rb +0 -38
- data/lib/trailblazer/operation/representer.rb +0 -31
- data/lib/trailblazer/operation/rescue.rb +0 -21
- data/lib/trailblazer/operation/test.rb +0 -17
- data/lib/trailblazer/operation/validate.rb +0 -68
- data/lib/trailblazer/operation/wrap.rb +0 -25
- data/test/docs/auto_inject_test.rb +0 -30
- data/test/docs/contract_test.rb +0 -525
- data/test/docs/dry_test.rb +0 -31
- data/test/docs/fast_test.rb +0 -164
- data/test/docs/guard_test.rb +0 -169
- data/test/docs/macro_test.rb +0 -36
- data/test/docs/model_test.rb +0 -75
- data/test/docs/nested_test.rb +0 -334
- data/test/docs/operation_test.rb +0 -408
- 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 -183
- data/test/gemfiles/Gemfile.ruby-1.9 +0 -3
- data/test/gemfiles/Gemfile.ruby-2.0 +0 -12
- data/test/gemfiles/Gemfile.ruby-2.3 +0 -12
- data/test/module_test.rb +0 -100
- data/test/operation/callback_test.rb +0 -70
- data/test/operation/contract_test.rb +0 -420
- data/test/operation/dsl/callback_test.rb +0 -106
- data/test/operation/dsl/contract_test.rb +0 -294
- data/test/operation/dsl/representer_test.rb +0 -169
- data/test/operation/model_test.rb +0 -60
- data/test/operation/params_test.rb +0 -36
- data/test/operation/persist_test.rb +0 -44
- data/test/operation/pipedream_test.rb +0 -59
- data/test/operation/pipetree_test.rb +0 -104
- data/test/operation/present_test.rb +0 -24
- data/test/operation/pundit_test.rb +0 -104
- data/test/operation/representer_test.rb +0 -254
- data/test/operation/resolver_test.rb +0 -47
- data/test/operation_test.rb +0 -143
@@ -1,15 +0,0 @@
|
|
1
|
-
module Trailblazer::Operation::Procedural
|
2
|
-
# THIS IS UNTESTED, PRIVATE API AND WILL BE REMOVED SOON.
|
3
|
-
module Contract
|
4
|
-
# Instantiate the contract, either by using the user's contract passed into #validate
|
5
|
-
# or infer the Operation contract.
|
6
|
-
def contract_for(model:self["model"], options:{}, contract_class:self["contract.default.class"])
|
7
|
-
contract!(model: model, options: options, contract_class: contract_class)
|
8
|
-
end
|
9
|
-
|
10
|
-
# Override to construct your own contract.
|
11
|
-
def contract!(model:nil, options:{}, contract_class:nil)
|
12
|
-
contract_class.new(model, options)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
module Trailblazer::Operation::Procedural
|
2
|
-
module Validate
|
3
|
-
def validate(params, contract:self["contract.default"], path:"contract.default") # :params
|
4
|
-
# DISCUSS: should we only have path here and then look up contract ourselves?
|
5
|
-
result = validate_contract(contract, params) # run validation. # FIXME: must be overridable.
|
6
|
-
|
7
|
-
self["result.#{path}"] = result
|
8
|
-
|
9
|
-
if valid = result.success? # FIXME: to_bool or success?
|
10
|
-
yield result if block_given?
|
11
|
-
else
|
12
|
-
# self["errors.#{path}"] = result.errors # TODO: remove me
|
13
|
-
end
|
14
|
-
|
15
|
-
valid
|
16
|
-
end
|
17
|
-
|
18
|
-
def validate_contract(contract, params)
|
19
|
-
contract.(params)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
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(input, 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,31 +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 representer(name=:default, constant=nil, &block)
|
16
|
-
heritage.record(:representer, name, constant, &block)
|
17
|
-
|
18
|
-
# FIXME: make this nicer. we want to extend same-named callback groups.
|
19
|
-
# TODO: allow the same with contract, or better, test it!
|
20
|
-
path, representer_class = Trailblazer::DSL::Build.new.({ prefix: :representer, class: representer_base_class, container: self }, name, constant, block)
|
21
|
-
|
22
|
-
self[path] = representer_class
|
23
|
-
end
|
24
|
-
|
25
|
-
# TODO: make engine configurable?
|
26
|
-
def representer_base_class
|
27
|
-
Class.new(Representable::Decorator) { include Representable::JSON; self }
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
class Trailblazer::Operation
|
2
|
-
def self.Rescue(*exceptions, handler: lambda { |*| }, &block)
|
3
|
-
exceptions = [StandardError] unless exceptions.any?
|
4
|
-
handler = Option.(handler)
|
5
|
-
|
6
|
-
rescue_block = ->(options, operation, *, &nested_pipe) {
|
7
|
-
begin
|
8
|
-
res = nested_pipe.call
|
9
|
-
res.first == ::Pipetree::Railway::Right # FIXME.
|
10
|
-
rescue *exceptions => exception
|
11
|
-
handler.call(operation, exception, options)
|
12
|
-
false
|
13
|
-
end
|
14
|
-
}
|
15
|
-
|
16
|
-
step, _ = Wrap(rescue_block, &block)
|
17
|
-
|
18
|
-
[ step, name: "Rescue:#{block.source_location.last}" ]
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
@@ -1,17 +0,0 @@
|
|
1
|
-
module Trailblazer
|
2
|
-
module Test
|
3
|
-
module Run
|
4
|
-
# DISCUSS: use Endpoint here?
|
5
|
-
# DISCUSS: use Controller code here?
|
6
|
-
module_function
|
7
|
-
def run(operation_class, *args)
|
8
|
-
result = operation_class.(*args)
|
9
|
-
|
10
|
-
raise "[Trailblazer] #{operation_class} wasn't run successfully. #{result.inspect}" if result.failure?
|
11
|
-
|
12
|
-
result
|
13
|
-
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
@@ -1,68 +0,0 @@
|
|
1
|
-
class Trailblazer::Operation
|
2
|
-
module Contract
|
3
|
-
Railway = Pipetree::Railway
|
4
|
-
|
5
|
-
# result.contract = {..}
|
6
|
-
# result.contract.errors = {..}
|
7
|
-
# Deviate to left track if optional key is not found in params.
|
8
|
-
# Deviate to left if validation result falsey.
|
9
|
-
def self.Validate(skip_extract:false, name: "default", representer:false, key: nil) # DISCUSS: should we introduce something like Validate::Deserializer?
|
10
|
-
params_path = "contract.#{name}.params" # extract_params! save extracted params here.
|
11
|
-
|
12
|
-
return Validate::Call(name: name, representer: representer, params_path: params_path) if skip_extract || representer
|
13
|
-
|
14
|
-
extract_step, extract_options = Validate::Extract(key: key, params_path: params_path)
|
15
|
-
validate_step, validate_options = Validate::Call(name: name, representer: representer, params_path: params_path)
|
16
|
-
|
17
|
-
pipe = Railway.new # TODO: make nested pipes simpler.
|
18
|
-
.add(Railway::Right, Railway.&(extract_step), extract_options)
|
19
|
-
.add(Railway::Right, Railway.&(validate_step), validate_options)
|
20
|
-
|
21
|
-
step = ->(input, options) { pipe.(input, options).first <= Railway::Right }
|
22
|
-
|
23
|
-
[step, name: "contract.#{name}.validate"]
|
24
|
-
end
|
25
|
-
|
26
|
-
module Validate
|
27
|
-
# Macro: extract the contract's input from params by reading `:key`.
|
28
|
-
def self.Extract(key:nil, params_path:nil)
|
29
|
-
# TODO: introduce nested pipes and pass composed input instead.
|
30
|
-
step = ->(input, options) do
|
31
|
-
options[params_path] = key ? options["params"][key] : options["params"]
|
32
|
-
end
|
33
|
-
|
34
|
-
[ step, name: params_path ]
|
35
|
-
end
|
36
|
-
|
37
|
-
# Macro: Validates contract `:name`.
|
38
|
-
def self.Call(name:"default", representer:false, params_path:nil)
|
39
|
-
step = ->(input, options) {
|
40
|
-
validate!(options, name: name, representer: options["representer.#{name}.class"], params_path: params_path)
|
41
|
-
}
|
42
|
-
|
43
|
-
step = Pipetree::Step.new( step, "representer.#{name}.class" => representer )
|
44
|
-
|
45
|
-
[ step, name: "contract.#{name}.call" ]
|
46
|
-
end
|
47
|
-
|
48
|
-
def self.validate!(options, name:nil, representer:false, from: "document", params_path:nil)
|
49
|
-
path = "contract.#{name}"
|
50
|
-
contract = options[path]
|
51
|
-
|
52
|
-
# this is for 1.1-style compatibility and should be removed once we have Deserializer in place:
|
53
|
-
options["result.#{path}"] = result =
|
54
|
-
if representer
|
55
|
-
# use "document" as the body and let the representer deserialize to the contract.
|
56
|
-
# this will be simplified once we have Deserializer.
|
57
|
-
# translates to contract.("{document: bla}") { MyRepresenter.new(contract).from_json .. }
|
58
|
-
contract.(options[from]) { |document| representer.new(contract).parse(document) }
|
59
|
-
else
|
60
|
-
# let Reform handle the deserialization.
|
61
|
-
contract.(options[params_path])
|
62
|
-
end
|
63
|
-
|
64
|
-
result.success?
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
class Trailblazer::Operation
|
2
|
-
Base = self # TODO: we won't need this with 2.1.
|
3
|
-
|
4
|
-
def self.Wrap(wrap, &block)
|
5
|
-
operation = Class.new(Base)
|
6
|
-
# DISCUSS: don't instance_exec when |pipe| given?
|
7
|
-
operation.instance_exec(&block) # evaluate the nested pipe.
|
8
|
-
|
9
|
-
pipe = operation["pipetree"]
|
10
|
-
pipe.add(nil, nil, {delete: "operation.new"}) # TODO: make this a bit more elegant.
|
11
|
-
|
12
|
-
step = Wrap.for(wrap, pipe)
|
13
|
-
|
14
|
-
[ step, {} ]
|
15
|
-
end
|
16
|
-
|
17
|
-
module Wrap
|
18
|
-
def self.for(wrap, pipe)
|
19
|
-
->(input, options) { wrap.(options, input, pipe, & ->{ pipe.(input, options) }) }
|
20
|
-
end
|
21
|
-
end # Wrap
|
22
|
-
end
|
23
|
-
|
24
|
-
# (options, *) => (options, operation, bla)
|
25
|
-
# (*, params:, **) => (options, operation, bla, options)
|
@@ -1,30 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
require "dry/container"
|
3
|
-
require "trailblazer/operation/auto_inject"
|
4
|
-
|
5
|
-
class DryAutoInjectTest < Minitest::Spec
|
6
|
-
my_container = Dry::Container.new
|
7
|
-
my_container.register(:user_repository, -> { Object })
|
8
|
-
|
9
|
-
AutoInject = Trailblazer::Operation::AutoInject(my_container)
|
10
|
-
|
11
|
-
class Create < Trailblazer::Operation
|
12
|
-
include AutoInject[:user_repository]
|
13
|
-
end
|
14
|
-
|
15
|
-
it "auto-injects user_repository" do
|
16
|
-
res = Create.({})
|
17
|
-
res[:user_repository].must_equal Object
|
18
|
-
end
|
19
|
-
|
20
|
-
it "allows dependency injection via ::call" do
|
21
|
-
Create.({}, user_repository: String)[:user_repository].must_equal String
|
22
|
-
end
|
23
|
-
|
24
|
-
describe "inheritance" do
|
25
|
-
class Update < Create
|
26
|
-
end
|
27
|
-
|
28
|
-
it { Update.()[:user_repository].must_equal Object }
|
29
|
-
end
|
30
|
-
end
|
data/test/docs/contract_test.rb
DELETED
@@ -1,525 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class DocsContractOverviewTest < Minitest::Spec
|
4
|
-
Song = Struct.new(:length, :title)
|
5
|
-
|
6
|
-
#:overv-reform
|
7
|
-
# app/concepts/song/create.rb
|
8
|
-
class Create < Trailblazer::Operation
|
9
|
-
#~bla
|
10
|
-
extend Contract::DSL
|
11
|
-
|
12
|
-
contract do
|
13
|
-
property :title
|
14
|
-
#~contractonly
|
15
|
-
property :length
|
16
|
-
|
17
|
-
validates :title, presence: true
|
18
|
-
validates :length, numericality: true
|
19
|
-
#~contractonly end
|
20
|
-
end
|
21
|
-
#~contractonly
|
22
|
-
|
23
|
-
#~bla end
|
24
|
-
step Model( Song, :new )
|
25
|
-
step Contract::Build()
|
26
|
-
step Contract::Validate()
|
27
|
-
step Contract::Persist( method: :sync )
|
28
|
-
#~contractonly end
|
29
|
-
end
|
30
|
-
#:overv-reform end
|
31
|
-
|
32
|
-
puts Create["pipetree"].inspect(style: :rows)
|
33
|
-
|
34
|
-
=begin
|
35
|
-
#:overv-reform-pipe
|
36
|
-
0 =======================>>operation.new
|
37
|
-
1 ==========================&model.build
|
38
|
-
2 =======================>contract.build
|
39
|
-
3 ==============&validate.params.extract
|
40
|
-
4 ====================&contract.validate
|
41
|
-
5 =========================&persist.save
|
42
|
-
#:overv-reform-pipe end
|
43
|
-
=end
|
44
|
-
|
45
|
-
it do
|
46
|
-
assert Create["contract.default.class"] < Reform::Form
|
47
|
-
end
|
48
|
-
|
49
|
-
#- result
|
50
|
-
it do
|
51
|
-
#:result
|
52
|
-
result = Create.({ length: "A" })
|
53
|
-
|
54
|
-
result["result.contract.default"].success? #=> false
|
55
|
-
result["result.contract.default"].errors #=> Errors object
|
56
|
-
result["result.contract.default"].errors.messages #=> {:length=>["is not a number"]}
|
57
|
-
|
58
|
-
#:result end
|
59
|
-
result["result.contract.default"].success?.must_equal false
|
60
|
-
result["result.contract.default"].errors.messages.must_equal ({:title=>["can't be blank"], :length=>["is not a number"]})
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
class DocsContractNameTest < Minitest::Spec
|
65
|
-
Song = Struct.new(:length, :title)
|
66
|
-
|
67
|
-
#:contract-name
|
68
|
-
# app/concepts/comment/update.rb
|
69
|
-
class Update < Trailblazer::Operation
|
70
|
-
#~contract
|
71
|
-
extend Contract::DSL
|
72
|
-
|
73
|
-
contract :params do
|
74
|
-
property :id
|
75
|
-
validates :id, presence: true
|
76
|
-
end
|
77
|
-
#~contract end
|
78
|
-
#~pipe
|
79
|
-
step Model( Song, :new )
|
80
|
-
step Contract::Build( name: "params" )
|
81
|
-
step Contract::Validate( name: "params" )
|
82
|
-
#~pipe end
|
83
|
-
end
|
84
|
-
#:contract-name end
|
85
|
-
end
|
86
|
-
|
87
|
-
class DocsContractReferenceTest < Minitest::Spec
|
88
|
-
MyContract = Class.new
|
89
|
-
#:contract-ref
|
90
|
-
# app/concepts/comment/update.rb
|
91
|
-
class Update < Trailblazer::Operation
|
92
|
-
#~contract
|
93
|
-
extend Contract::DSL
|
94
|
-
contract :user, MyContract
|
95
|
-
end
|
96
|
-
#:contract-ref end
|
97
|
-
end
|
98
|
-
|
99
|
-
#---
|
100
|
-
# contract MyContract
|
101
|
-
class DocsContractExplicitTest < Minitest::Spec
|
102
|
-
Song = Struct.new(:length, :title)
|
103
|
-
|
104
|
-
#:reform-inline
|
105
|
-
class MyContract < Reform::Form
|
106
|
-
property :title
|
107
|
-
property :length
|
108
|
-
|
109
|
-
validates :title, presence: true
|
110
|
-
validates :length, numericality: true
|
111
|
-
end
|
112
|
-
#:reform-inline end
|
113
|
-
|
114
|
-
#:reform-inline-op
|
115
|
-
# app/concepts/comment/create.rb
|
116
|
-
class Create < Trailblazer::Operation
|
117
|
-
extend Contract::DSL
|
118
|
-
|
119
|
-
contract MyContract
|
120
|
-
|
121
|
-
step Model( Song, :new )
|
122
|
-
step Contract::Build()
|
123
|
-
step Contract::Validate()
|
124
|
-
step Contract::Persist( method: :sync )
|
125
|
-
end
|
126
|
-
#:reform-inline-op end
|
127
|
-
end
|
128
|
-
|
129
|
-
#- Validate with manual key extraction
|
130
|
-
class DocsContractSeparateKeyTest < Minitest::Spec
|
131
|
-
Song = Struct.new(:id, :title)
|
132
|
-
#:key-extr
|
133
|
-
class Create < Trailblazer::Operation
|
134
|
-
extend Contract::DSL
|
135
|
-
|
136
|
-
contract do
|
137
|
-
property :title
|
138
|
-
end
|
139
|
-
|
140
|
-
def type
|
141
|
-
"evergreen" # this is how you could do polymorphic lookups.
|
142
|
-
end
|
143
|
-
|
144
|
-
step Model( Song, :new )
|
145
|
-
step Contract::Build()
|
146
|
-
step :extract_params!
|
147
|
-
step Contract::Validate( skip_extract: true )
|
148
|
-
step Contract::Persist( method: :sync )
|
149
|
-
|
150
|
-
def extract_params!(options)
|
151
|
-
options["contract.default.params"] = options["params"][type]
|
152
|
-
end
|
153
|
-
end
|
154
|
-
#:key-extr end
|
155
|
-
|
156
|
-
it { Create.({ }).inspect("model").must_equal %{<Result:false [#<struct DocsContractSeparateKeyTest::Song id=nil, title=nil>] >} }
|
157
|
-
it { Create.({"evergreen" => { title: "SVG" }}).inspect("model").must_equal %{<Result:true [#<struct DocsContractSeparateKeyTest::Song id=nil, title="SVG">] >} }
|
158
|
-
end
|
159
|
-
|
160
|
-
#---
|
161
|
-
#- Contract::Build( constant: XXX )
|
162
|
-
class ContractConstantTest < Minitest::Spec
|
163
|
-
Song = Struct.new(:title, :length) do
|
164
|
-
def save
|
165
|
-
true
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
#:constant-contract
|
170
|
-
# app/concepts/song/contract/create.rb
|
171
|
-
module Song::Contract
|
172
|
-
class Create < Reform::Form
|
173
|
-
property :title
|
174
|
-
property :length
|
175
|
-
|
176
|
-
validates :title, length: 2..33
|
177
|
-
validates :length, numericality: true
|
178
|
-
end
|
179
|
-
end
|
180
|
-
#:constant-contract end
|
181
|
-
|
182
|
-
#:constant
|
183
|
-
class Song::Create < Trailblazer::Operation
|
184
|
-
step Model( Song, :new )
|
185
|
-
step Contract::Build( constant: Song::Contract::Create )
|
186
|
-
step Contract::Validate()
|
187
|
-
step Contract::Persist()
|
188
|
-
end
|
189
|
-
#:constant end
|
190
|
-
|
191
|
-
it { Song::Create.({ title: "A" }).inspect("model").must_equal %{<Result:false [#<struct ContractConstantTest::Song title=nil, length=nil>] >} }
|
192
|
-
it { Song::Create.({ title: "Anthony's Song", length: 12 }).inspect("model").must_equal %{<Result:true [#<struct ContractConstantTest::Song title="Anthony's Song", length=12>] >} }
|
193
|
-
it do
|
194
|
-
#:constant-result
|
195
|
-
result = Song::Create.( title: "A" )
|
196
|
-
result.success? #=> false
|
197
|
-
result["contract.default"].errors.messages
|
198
|
-
#=> {:title=>["is too short (minimum is 2 characters)"], :length=>["is not a number"]}
|
199
|
-
#:constant-result end
|
200
|
-
|
201
|
-
#:constant-result-true
|
202
|
-
result = Song::Create.( title: "Rising Force", length: 13 )
|
203
|
-
result.success? #=> true
|
204
|
-
result["model"] #=> #<Song title="Rising Force", length=13>
|
205
|
-
#:constant-result-true end
|
206
|
-
end
|
207
|
-
|
208
|
-
#---
|
209
|
-
# Song::New
|
210
|
-
#:constant-new
|
211
|
-
class Song::New < Trailblazer::Operation
|
212
|
-
step Model( Song, :new )
|
213
|
-
step Contract::Build( constant: Song::Contract::Create )
|
214
|
-
end
|
215
|
-
#:constant-new end
|
216
|
-
|
217
|
-
it { Song::New.().inspect("model").must_equal %{<Result:true [#<struct ContractConstantTest::Song title=nil, length=nil>] >} }
|
218
|
-
it { Song::New.()["contract.default"].model.inspect.must_equal %{#<struct ContractConstantTest::Song title=nil, length=nil>} }
|
219
|
-
it do
|
220
|
-
#:constant-new-result
|
221
|
-
result = Song::New.()
|
222
|
-
result["model"] #=> #<struct Song title=nil, length=nil>
|
223
|
-
result["contract.default"]
|
224
|
-
#=> #<Song::Contract::Create model=#<struct Song title=nil, length=nil>>
|
225
|
-
#:constant-new-result end
|
226
|
-
end
|
227
|
-
|
228
|
-
#---
|
229
|
-
#:validate-only
|
230
|
-
class Song::ValidateOnly < Trailblazer::Operation
|
231
|
-
step Model( Song, :new )
|
232
|
-
step Contract::Build( constant: Song::Contract::Create )
|
233
|
-
step Contract::Validate()
|
234
|
-
end
|
235
|
-
#:validate-only end
|
236
|
-
|
237
|
-
it { Song::ValidateOnly.().inspect("model").must_equal %{<Result:false [#<struct ContractConstantTest::Song title=nil, length=nil>] >} }
|
238
|
-
it do
|
239
|
-
result = Song::ValidateOnly.({ title: "Rising Forse", length: 13 })
|
240
|
-
result.inspect("model").must_equal %{<Result:true [#<struct ContractConstantTest::Song title=nil, length=nil>] >}
|
241
|
-
end
|
242
|
-
|
243
|
-
it do
|
244
|
-
#:validate-only-result-false
|
245
|
-
result = Song::ValidateOnly.({}) # empty params
|
246
|
-
result.success? #=> false
|
247
|
-
#:validate-only-result-false end
|
248
|
-
end
|
249
|
-
|
250
|
-
it do
|
251
|
-
#:validate-only-result
|
252
|
-
result = Song::ValidateOnly.({ title: "Rising Force", length: 13 })
|
253
|
-
|
254
|
-
result.success? #=> true
|
255
|
-
result["model"] #=> #<struct Song title=nil, length=nil>
|
256
|
-
result["contract.default"].title #=> "Rising Force"
|
257
|
-
#:validate-only-result end
|
258
|
-
end
|
259
|
-
end
|
260
|
-
|
261
|
-
#---
|
262
|
-
#- Validate( key: :song )
|
263
|
-
class DocsContractKeyTest < Minitest::Spec
|
264
|
-
Song = Class.new(ContractConstantTest::Song)
|
265
|
-
|
266
|
-
module Song::Contract
|
267
|
-
Create = ContractConstantTest::Song::Contract::Create
|
268
|
-
end
|
269
|
-
|
270
|
-
#:key
|
271
|
-
class Song::Create < Trailblazer::Operation
|
272
|
-
step Model( Song, :new )
|
273
|
-
step Contract::Build( constant: Song::Contract::Create )
|
274
|
-
step Contract::Validate( key: "song" )
|
275
|
-
step Contract::Persist( )
|
276
|
-
end
|
277
|
-
#:key end
|
278
|
-
|
279
|
-
it { Song::Create.({}).inspect("model", "result.contract.default.extract").must_equal %{<Result:false [#<struct DocsContractKeyTest::Song title=nil, length=nil>, nil] >} }
|
280
|
-
it { Song::Create.({"song" => { title: "SVG", length: 13 }}).inspect("model").must_equal %{<Result:true [#<struct DocsContractKeyTest::Song title=\"SVG\", length=13>] >} }
|
281
|
-
it do
|
282
|
-
#:key-res
|
283
|
-
result = Song::Create.({ "song" => { title: "Rising Force", length: 13 } })
|
284
|
-
result.success? #=> true
|
285
|
-
#:key-res end
|
286
|
-
|
287
|
-
#:key-res-false
|
288
|
-
result = Song::Create.({ title: "Rising Force", length: 13 })
|
289
|
-
result.success? #=> false
|
290
|
-
#:key-res-false end
|
291
|
-
end
|
292
|
-
end
|
293
|
-
|
294
|
-
#- Contract::Build[ constant: XXX, name: AAA ]
|
295
|
-
class ContractNamedConstantTest < Minitest::Spec
|
296
|
-
Song = Class.new(ContractConstantTest::Song)
|
297
|
-
|
298
|
-
module Song::Contract
|
299
|
-
Create = ContractConstantTest::Song::Contract::Create
|
300
|
-
end
|
301
|
-
|
302
|
-
#:constant-name
|
303
|
-
class Song::Create < Trailblazer::Operation
|
304
|
-
step Model( Song, :new )
|
305
|
-
step Contract::Build( name: "form", constant: Song::Contract::Create )
|
306
|
-
step Contract::Validate( name: "form" )
|
307
|
-
step Contract::Persist( name: "form" )
|
308
|
-
end
|
309
|
-
#:constant-name end
|
310
|
-
|
311
|
-
it { Song::Create.({ title: "A" }).inspect("model").must_equal %{<Result:false [#<struct ContractNamedConstantTest::Song title=nil, length=nil>] >} }
|
312
|
-
it { Song::Create.({ title: "Anthony's Song", length: 13 }).inspect("model").must_equal %{<Result:true [#<struct ContractNamedConstantTest::Song title="Anthony's Song", length=13>] >} }
|
313
|
-
|
314
|
-
it do
|
315
|
-
#:name-res
|
316
|
-
result = Song::Create.({ title: "A" })
|
317
|
-
result["contract.form"].errors.messages #=> {:title=>["is too short (minimum is 2 ch...
|
318
|
-
#:name-res end
|
319
|
-
end
|
320
|
-
end
|
321
|
-
|
322
|
-
#---
|
323
|
-
#- dependency injection
|
324
|
-
#- contract class
|
325
|
-
class ContractInjectConstantTest < Minitest::Spec
|
326
|
-
Song = Struct.new(:id, :title)
|
327
|
-
#:di-constant-contract
|
328
|
-
class MyContract < Reform::Form
|
329
|
-
property :title
|
330
|
-
validates :title, length: 2..33
|
331
|
-
end
|
332
|
-
#:di-constant-contract end
|
333
|
-
#:di-constant
|
334
|
-
class Create < Trailblazer::Operation
|
335
|
-
step Model( Song, :new )
|
336
|
-
step Contract::Build()
|
337
|
-
step Contract::Validate()
|
338
|
-
step Contract::Persist( method: :sync )
|
339
|
-
end
|
340
|
-
#:di-constant end
|
341
|
-
|
342
|
-
it do
|
343
|
-
#:di-contract-call
|
344
|
-
Create.(
|
345
|
-
{ title: "Anthony's Song" },
|
346
|
-
"contract.default.class" => MyContract
|
347
|
-
)
|
348
|
-
#:di-contract-call end
|
349
|
-
end
|
350
|
-
it { Create.({ title: "A" }, "contract.default.class" => MyContract).inspect("model").must_equal %{<Result:false [#<struct ContractInjectConstantTest::Song id=nil, title=nil>] >} }
|
351
|
-
it { Create.({ title: "Anthony's Song" }, "contract.default.class" => MyContract).inspect("model").must_equal %{<Result:true [#<struct ContractInjectConstantTest::Song id=nil, title="Anthony's Song">] >} }
|
352
|
-
end
|
353
|
-
|
354
|
-
class DryValidationContractTest < Minitest::Spec
|
355
|
-
Song = Struct.new(:id, :title)
|
356
|
-
#---
|
357
|
-
# DRY-validation params validation before op,
|
358
|
-
# plus main contract.
|
359
|
-
#- result.path
|
360
|
-
#:dry-schema
|
361
|
-
require "dry/validation"
|
362
|
-
class Create < Trailblazer::Operation
|
363
|
-
extend Contract::DSL
|
364
|
-
|
365
|
-
# contract to verify params formally.
|
366
|
-
contract "params", (Dry::Validation.Schema do
|
367
|
-
required(:id).filled
|
368
|
-
end)
|
369
|
-
#~form
|
370
|
-
# domain validations.
|
371
|
-
contract "form" do
|
372
|
-
property :title
|
373
|
-
validates :title, length: 1..3
|
374
|
-
end
|
375
|
-
#~form end
|
376
|
-
|
377
|
-
step Contract::Validate( name: "params" )
|
378
|
-
#~form
|
379
|
-
step Model( Song, :new ) # create the op's main model.
|
380
|
-
step Contract::Build( name: "form" ) # create the Reform contract.
|
381
|
-
step Contract::Validate( name: "form" ) # validate the Reform contract.
|
382
|
-
step Contract::Persist( method: :sync, name: "form" ) # persist the contract's data via the model.
|
383
|
-
#~form end
|
384
|
-
end
|
385
|
-
#:dry-schema end
|
386
|
-
|
387
|
-
puts "@@@@@ #{Create["pipetree"].inspect(style: :rows)}"
|
388
|
-
|
389
|
-
it { Create.({}).inspect("model", "result.contract.params").must_equal %{<Result:false [nil, #<Dry::Validation::Result output={} errors={:id=>[\"is missing\"]}>] >} }
|
390
|
-
it { Create.({ id: 1 }).inspect("model", "result.contract.params").must_equal %{<Result:false [#<struct DryValidationContractTest::Song id=nil, title=nil>, #<Dry::Validation::Result output={:id=>1} errors={}>] >} }
|
391
|
-
# it { Create.({ id: 1, title: "" }).inspect("model", "result.contract.form").must_equal %{<Result:false [#<struct DryValidationContractTest::Song id=nil, title=nil>] >} }
|
392
|
-
it { Create.({ id: 1, title: "" }).inspect("model").must_equal %{<Result:false [#<struct DryValidationContractTest::Song id=nil, title=nil>] >} }
|
393
|
-
it { Create.({ id: 1, title: "Yo" }).inspect("model").must_equal %{<Result:true [#<struct DryValidationContractTest::Song id=nil, title="Yo">] >} }
|
394
|
-
|
395
|
-
#:dry-schema-first
|
396
|
-
require "dry/validation"
|
397
|
-
class Delete < Trailblazer::Operation
|
398
|
-
extend Contract::DSL
|
399
|
-
|
400
|
-
contract "params", (Dry::Validation.Schema do
|
401
|
-
required(:id).filled
|
402
|
-
end)
|
403
|
-
|
404
|
-
step Contract::Validate( name: "params" ), before: "operation.new"
|
405
|
-
#~more
|
406
|
-
#~more end
|
407
|
-
end
|
408
|
-
#:dry-schema-first end
|
409
|
-
end
|
410
|
-
|
411
|
-
class DryExplicitSchemaTest < Minitest::Spec
|
412
|
-
#:dry-schema-explsch
|
413
|
-
# app/concepts/comment/contract/params.rb
|
414
|
-
require "dry/validation"
|
415
|
-
MySchema = Dry::Validation.Schema do
|
416
|
-
required(:id).filled
|
417
|
-
end
|
418
|
-
#:dry-schema-explsch end
|
419
|
-
|
420
|
-
#:dry-schema-expl
|
421
|
-
# app/concepts/comment/delete.rb
|
422
|
-
class Delete < Trailblazer::Operation
|
423
|
-
extend Contract::DSL
|
424
|
-
contract "params", MySchema
|
425
|
-
|
426
|
-
step Contract::Validate( name: "params" ), before: "operation.new"
|
427
|
-
end
|
428
|
-
#:dry-schema-expl end
|
429
|
-
end
|
430
|
-
|
431
|
-
class DocContractBuilderTest < Minitest::Spec
|
432
|
-
Song = Struct.new(:id, :title)
|
433
|
-
#---
|
434
|
-
#- builder:
|
435
|
-
#:builder-option
|
436
|
-
class Create < Trailblazer::Operation
|
437
|
-
extend Contract::DSL
|
438
|
-
|
439
|
-
contract do
|
440
|
-
property :title
|
441
|
-
property :current_user, virtual: true
|
442
|
-
validates :current_user, presence: true
|
443
|
-
end
|
444
|
-
|
445
|
-
step Model( Song, :new )
|
446
|
-
step Contract::Build( builder: :default_contract! )
|
447
|
-
step Contract::Validate()
|
448
|
-
step Contract::Persist( method: :sync )
|
449
|
-
|
450
|
-
def default_contract!(options, constant:, model:, **)
|
451
|
-
constant.new(model, current_user: self["current_user"])
|
452
|
-
end
|
453
|
-
end
|
454
|
-
#:builder-option end
|
455
|
-
|
456
|
-
it { Create.({}).inspect("model").must_equal %{<Result:false [#<struct DocContractBuilderTest::Song id=nil, title=nil>] >} }
|
457
|
-
it { Create.({ title: 1}, "current_user" => Module).inspect("model").must_equal %{<Result:true [#<struct DocContractBuilderTest::Song id=nil, title=1>] >} }
|
458
|
-
|
459
|
-
#- proc
|
460
|
-
class Update < Trailblazer::Operation
|
461
|
-
extend Contract::DSL
|
462
|
-
|
463
|
-
contract do
|
464
|
-
property :title
|
465
|
-
property :current_user, virtual: true
|
466
|
-
validates :current_user, presence: true
|
467
|
-
end
|
468
|
-
|
469
|
-
step Model( Song, :new )
|
470
|
-
#:builder-proc
|
471
|
-
step Contract::Build( builder: ->(options, constant:, model:, **) {
|
472
|
-
constant.new(model, current_user: options["current_user"])
|
473
|
-
})
|
474
|
-
#:builder-proc end
|
475
|
-
step Contract::Validate()
|
476
|
-
step Contract::Persist( method: :sync )
|
477
|
-
end
|
478
|
-
|
479
|
-
it { Update.({}).inspect("model").must_equal %{<Result:false [#<struct DocContractBuilderTest::Song id=nil, title=nil>] >} }
|
480
|
-
it { Update.({ title: 1}, "current_user" => Module).inspect("model").must_equal %{<Result:true [#<struct DocContractBuilderTest::Song id=nil, title=1>] >} }
|
481
|
-
end
|
482
|
-
|
483
|
-
class DocContractTest < Minitest::Spec
|
484
|
-
Song = Struct.new(:id, :title)
|
485
|
-
#---
|
486
|
-
# with contract block, and inheritance, the old way.
|
487
|
-
class Block < Trailblazer::Operation
|
488
|
-
extend Contract::DSL
|
489
|
-
contract do
|
490
|
-
property :title
|
491
|
-
end
|
492
|
-
|
493
|
-
step Model( Song, :new )
|
494
|
-
step Contract::Build() # resolves to "contract.class.default" and is resolved at runtime.
|
495
|
-
step Contract::Validate()
|
496
|
-
step Contract::Persist( method: :sync )
|
497
|
-
end
|
498
|
-
|
499
|
-
it { Block.({}).inspect("model").must_equal %{<Result:true [#<struct DocContractTest::Song id=nil, title=nil>] >} }
|
500
|
-
it { Block.({ id:1, title: "Fame" }).inspect("model").must_equal %{<Result:true [#<struct DocContractTest::Song id=nil, title="Fame">] >} }
|
501
|
-
|
502
|
-
class Breach < Block
|
503
|
-
contract do
|
504
|
-
property :id
|
505
|
-
end
|
506
|
-
end
|
507
|
-
|
508
|
-
it { Breach.({ id:1, title: "Fame" }).inspect("model").must_equal %{<Result:true [#<struct DocContractTest::Song id=1, title="Fame">] >} }
|
509
|
-
|
510
|
-
#-
|
511
|
-
# with constant.
|
512
|
-
class Break < Block
|
513
|
-
class MyContract < Reform::Form
|
514
|
-
property :id
|
515
|
-
end
|
516
|
-
# override the original block as if it's never been there.
|
517
|
-
contract MyContract
|
518
|
-
end
|
519
|
-
|
520
|
-
it { Break.({ id:1, title: "Fame" }).inspect("model").must_equal %{<Result:true [#<struct DocContractTest::Song id=1, title=nil>] >} }
|
521
|
-
end
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|