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.
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
-