trailblazer 2.0.7 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.rubocop-https---raw-githubusercontent-com-trailblazer-meta-master-rubocop-yml +101 -0
  4. data/.rubocop.yml +20 -0
  5. data/.rubocop_todo.yml +556 -0
  6. data/.travis.yml +6 -10
  7. data/CHANGES.md +83 -1
  8. data/COMM-LICENSE +46 -75
  9. data/CONTRIBUTING.md +179 -0
  10. data/Gemfile +0 -27
  11. data/{LICENSE.txt → LICENSE} +4 -4
  12. data/README.md +39 -138
  13. data/Rakefile +2 -19
  14. data/lib/trailblazer.rb +3 -17
  15. data/lib/trailblazer/version.rb +3 -1
  16. data/test/test_helper.rb +12 -3
  17. data/trailblazer.gemspec +10 -14
  18. metadata +22 -147
  19. data/doc/Trb-The-Stack.png +0 -0
  20. data/doc/operation-2017.png +0 -0
  21. data/doc/trb.jpg +0 -0
  22. data/lib/trailblazer/dsl.rb +0 -47
  23. data/lib/trailblazer/operation/auto_inject.rb +0 -47
  24. data/lib/trailblazer/operation/callback.rb +0 -35
  25. data/lib/trailblazer/operation/contract.rb +0 -46
  26. data/lib/trailblazer/operation/guard.rb +0 -18
  27. data/lib/trailblazer/operation/model.rb +0 -60
  28. data/lib/trailblazer/operation/module.rb +0 -29
  29. data/lib/trailblazer/operation/nested.rb +0 -113
  30. data/lib/trailblazer/operation/persist.rb +0 -10
  31. data/lib/trailblazer/operation/policy.rb +0 -35
  32. data/lib/trailblazer/operation/procedural/contract.rb +0 -15
  33. data/lib/trailblazer/operation/procedural/validate.rb +0 -22
  34. data/lib/trailblazer/operation/pundit.rb +0 -38
  35. data/lib/trailblazer/operation/representer.rb +0 -31
  36. data/lib/trailblazer/operation/rescue.rb +0 -21
  37. data/lib/trailblazer/operation/test.rb +0 -17
  38. data/lib/trailblazer/operation/validate.rb +0 -68
  39. data/lib/trailblazer/operation/wrap.rb +0 -25
  40. data/test/docs/auto_inject_test.rb +0 -30
  41. data/test/docs/contract_test.rb +0 -525
  42. data/test/docs/dry_test.rb +0 -31
  43. data/test/docs/fast_test.rb +0 -164
  44. data/test/docs/guard_test.rb +0 -169
  45. data/test/docs/macro_test.rb +0 -36
  46. data/test/docs/model_test.rb +0 -75
  47. data/test/docs/nested_test.rb +0 -334
  48. data/test/docs/operation_test.rb +0 -408
  49. data/test/docs/policy_test.rb +0 -2
  50. data/test/docs/pundit_test.rb +0 -133
  51. data/test/docs/representer_test.rb +0 -268
  52. data/test/docs/rescue_test.rb +0 -154
  53. data/test/docs/wrap_test.rb +0 -183
  54. data/test/gemfiles/Gemfile.ruby-1.9 +0 -3
  55. data/test/gemfiles/Gemfile.ruby-2.0 +0 -12
  56. data/test/gemfiles/Gemfile.ruby-2.3 +0 -12
  57. data/test/module_test.rb +0 -100
  58. data/test/operation/callback_test.rb +0 -70
  59. data/test/operation/contract_test.rb +0 -420
  60. data/test/operation/dsl/callback_test.rb +0 -106
  61. data/test/operation/dsl/contract_test.rb +0 -294
  62. data/test/operation/dsl/representer_test.rb +0 -169
  63. data/test/operation/model_test.rb +0 -60
  64. data/test/operation/params_test.rb +0 -36
  65. data/test/operation/persist_test.rb +0 -44
  66. data/test/operation/pipedream_test.rb +0 -59
  67. data/test/operation/pipetree_test.rb +0 -104
  68. data/test/operation/present_test.rb +0 -24
  69. data/test/operation/pundit_test.rb +0 -104
  70. data/test/operation/representer_test.rb +0 -254
  71. data/test/operation/resolver_test.rb +0 -47
  72. 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
@@ -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
-