trailblazer 1.1.2 → 2.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +10 -7
- data/CHANGES.md +108 -0
- data/COMM-LICENSE +91 -0
- data/Gemfile +18 -4
- data/LICENSE.txt +7 -20
- data/README.md +55 -15
- data/Rakefile +21 -2
- data/draft-1.2.rb +7 -0
- data/lib/trailblazer.rb +17 -4
- data/lib/trailblazer/dsl.rb +47 -0
- data/lib/trailblazer/operation/auto_inject.rb +47 -0
- data/lib/trailblazer/operation/builder.rb +18 -18
- data/lib/trailblazer/operation/callback.rb +31 -38
- data/lib/trailblazer/operation/contract.rb +46 -0
- data/lib/trailblazer/operation/controller.rb +45 -27
- data/lib/trailblazer/operation/guard.rb +24 -0
- data/lib/trailblazer/operation/model.rb +41 -33
- data/lib/trailblazer/operation/nested.rb +43 -0
- data/lib/trailblazer/operation/params.rb +13 -0
- data/lib/trailblazer/operation/persist.rb +13 -0
- data/lib/trailblazer/operation/policy.rb +26 -72
- data/lib/trailblazer/operation/present.rb +19 -0
- data/lib/trailblazer/operation/procedural/contract.rb +15 -0
- data/lib/trailblazer/operation/procedural/validate.rb +22 -0
- data/lib/trailblazer/operation/pundit.rb +42 -0
- data/lib/trailblazer/operation/representer.rb +25 -92
- data/lib/trailblazer/operation/rescue.rb +23 -0
- data/lib/trailblazer/operation/resolver.rb +18 -24
- data/lib/trailblazer/operation/validate.rb +50 -0
- data/lib/trailblazer/operation/wrap.rb +37 -0
- data/lib/trailblazer/version.rb +1 -1
- data/test/{operation/controller_test.rb → controller_test.rb} +8 -4
- data/test/docs/auto_inject_test.rb +30 -0
- data/test/docs/contract_test.rb +429 -0
- data/test/docs/dry_test.rb +31 -0
- data/test/docs/guard_test.rb +143 -0
- data/test/docs/nested_test.rb +117 -0
- data/test/docs/policy_test.rb +2 -0
- data/test/docs/pundit_test.rb +109 -0
- data/test/docs/representer_test.rb +268 -0
- data/test/docs/rescue_test.rb +153 -0
- data/test/docs/wrap_test.rb +174 -0
- data/test/gemfiles/Gemfile.ruby-1.9 +3 -0
- data/test/gemfiles/Gemfile.ruby-2.0 +12 -0
- data/test/gemfiles/Gemfile.ruby-2.3 +12 -0
- data/test/module_test.rb +22 -15
- data/test/operation/builder_test.rb +66 -18
- data/test/operation/callback_test.rb +70 -0
- data/test/operation/contract_test.rb +385 -15
- data/test/operation/dsl/callback_test.rb +18 -30
- data/test/operation/dsl/contract_test.rb +209 -19
- data/test/operation/dsl/representer_test.rb +42 -15
- data/test/operation/guard_test.rb +1 -147
- data/test/operation/model_test.rb +105 -0
- data/test/operation/params_test.rb +36 -0
- data/test/operation/persist_test.rb +44 -0
- data/test/operation/pipedream_test.rb +59 -0
- data/test/operation/pipetree_test.rb +104 -0
- data/test/operation/present_test.rb +24 -0
- data/test/operation/pundit_test.rb +104 -0
- data/test/{representer_test.rb → operation/representer_test.rb} +58 -42
- data/test/operation/resolver_test.rb +34 -70
- data/test/operation_test.rb +57 -189
- data/test/test_helper.rb +23 -3
- data/trailblazer.gemspec +8 -7
- metadata +91 -59
- data/gemfiles/Gemfile.rails.lock +0 -130
- data/gemfiles/Gemfile.reform-2.0 +0 -6
- data/gemfiles/Gemfile.reform-2.1 +0 -7
- data/lib/trailblazer/autoloading.rb +0 -15
- data/lib/trailblazer/endpoint.rb +0 -31
- data/lib/trailblazer/operation.rb +0 -175
- data/lib/trailblazer/operation/collection.rb +0 -6
- data/lib/trailblazer/operation/dispatch.rb +0 -3
- data/lib/trailblazer/operation/model/dsl.rb +0 -29
- data/lib/trailblazer/operation/model/external.rb +0 -34
- data/lib/trailblazer/operation/policy/guard.rb +0 -35
- data/lib/trailblazer/operation/uploaded_file.rb +0 -77
- data/test/callback_test.rb +0 -104
- data/test/collection_test.rb +0 -57
- data/test/model_test.rb +0 -148
- data/test/operation/external_model_test.rb +0 -71
- data/test/operation/policy_test.rb +0 -97
- data/test/operation/reject_test.rb +0 -34
- data/test/rollback_test.rb +0 -47
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
# callbacks are tested in Disposable::Callback::Group.
|
4
|
+
class OperationCallbackTest < MiniTest::Spec
|
5
|
+
Song = Struct.new(:name)
|
6
|
+
|
7
|
+
#---
|
8
|
+
# with contract and disposable semantics
|
9
|
+
class Create < Trailblazer::Operation
|
10
|
+
extend Contract::DSL
|
11
|
+
|
12
|
+
contract do
|
13
|
+
property :name
|
14
|
+
end
|
15
|
+
|
16
|
+
self.| Model( Song, :new )
|
17
|
+
self.| Contract::Build()
|
18
|
+
self.| Contract::Validate()
|
19
|
+
self.| Callback( :default )
|
20
|
+
|
21
|
+
|
22
|
+
extend Callback::DSL
|
23
|
+
|
24
|
+
callback do
|
25
|
+
on_change :notify_me!
|
26
|
+
on_change :notify_you!
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
# TODO: always dispatch, pass params.
|
31
|
+
|
32
|
+
def dispatched
|
33
|
+
self["dispatched"] ||= []
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def notify_me!(*)
|
38
|
+
dispatched << :notify_me!
|
39
|
+
end
|
40
|
+
|
41
|
+
def notify_you!(*)
|
42
|
+
dispatched << :notify_you!
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
class Update < Create
|
48
|
+
# TODO: allow skipping groups.
|
49
|
+
# skip_dispatch :notify_me!
|
50
|
+
|
51
|
+
callback do
|
52
|
+
remove! :on_change, :notify_me!
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
#---
|
57
|
+
#- inheritance
|
58
|
+
it { Update["pipetree"].inspect.must_equal %{[>>operation.new,&model.build,>contract.build,&validate.params.extract,&contract.validate,&callback.default]} }
|
59
|
+
|
60
|
+
|
61
|
+
it "invokes all callbacks" do
|
62
|
+
res = Create.({"name"=>"Keep On Running"})
|
63
|
+
res["dispatched"].must_equal [:notify_me!, :notify_you!]
|
64
|
+
end
|
65
|
+
|
66
|
+
it "does not invoke removed callbacks" do
|
67
|
+
res = Update.({"name"=>"Keep On Running"})
|
68
|
+
res["dispatched"].must_equal [:notify_you!]
|
69
|
+
end
|
70
|
+
end
|
@@ -1,30 +1,400 @@
|
|
1
1
|
require "test_helper"
|
2
|
+
require "trailblazer/operation/contract"
|
2
3
|
|
4
|
+
require "dry/validation"
|
3
5
|
|
4
|
-
class
|
6
|
+
class DryValidationTest < Minitest::Spec
|
7
|
+
class Create < Trailblazer::Operation
|
8
|
+
extend Contract::DSL
|
5
9
|
|
6
|
-
|
10
|
+
contract "params", (Dry::Validation.Schema do
|
11
|
+
required(:id).filled
|
12
|
+
end)
|
13
|
+
# self["contract.params"] = Dry::Validation.Schema do
|
14
|
+
# required(:id).filled
|
15
|
+
# end
|
16
|
+
|
17
|
+
self.| :process
|
18
|
+
|
19
|
+
include Procedural::Validate
|
20
|
+
|
21
|
+
def process(options)
|
22
|
+
validate(options["params"], contract: self["contract.params"], path: "contract.params") { |f| puts f.inspect }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
#- result object, contract
|
27
|
+
# success
|
28
|
+
it { Create.(id: 1)["result.contract.params"].success?.must_equal true }
|
29
|
+
it { Create.(id: 1)["result.contract.params"].errors.must_equal({}) }
|
30
|
+
# failure
|
31
|
+
it { Create.(id: nil)["result.contract.params"].success?.must_equal false }
|
32
|
+
it { Create.(id: nil)["result.contract.params"].errors.must_equal({:id=>["must be filled"]}) }
|
33
|
+
|
34
|
+
#---
|
35
|
+
# with Contract::Validate, but before op even gets instantiated.
|
36
|
+
class Update < Trailblazer::Operation #["contract"]
|
37
|
+
extend Contract::DSL
|
38
|
+
|
39
|
+
contract "params", (Dry::Validation.Schema do
|
40
|
+
required(:id).filled
|
41
|
+
end)
|
42
|
+
|
43
|
+
self.& ->(options) { options["contract.params"].(options["params"]).success? }, before: "operation.new"
|
44
|
+
end
|
45
|
+
|
46
|
+
it { Update.( id: 1 ).success?.must_equal true }
|
47
|
+
it { Update.( ).success?.must_equal false }
|
48
|
+
end
|
49
|
+
|
50
|
+
class ContractTest < Minitest::Spec
|
51
|
+
Song = Struct.new(:title)
|
52
|
+
# # generic form for testing.
|
53
|
+
# class Form
|
54
|
+
# def initialize(model, options={})
|
55
|
+
# @inspect = "#{self.class}: #{model} #{options.inspect}"
|
56
|
+
# end
|
57
|
+
|
58
|
+
# def validate
|
59
|
+
# @inspect
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
|
63
|
+
#---
|
64
|
+
# contract do..end (without constant)
|
65
|
+
#- explicit validate call
|
66
|
+
describe "contract do .. end" do
|
67
|
+
class Index < Trailblazer::Operation
|
68
|
+
extend Contract::DSL
|
69
|
+
|
70
|
+
contract do
|
71
|
+
property :title
|
72
|
+
end
|
73
|
+
|
74
|
+
self.> ->(options) { options["model"] = Song.new }
|
75
|
+
# self.| Model( Song, :new )
|
76
|
+
self.| Contract::Build()
|
77
|
+
self.| :process
|
78
|
+
|
79
|
+
include Procedural::Validate
|
80
|
+
# TODO: get model automatically in validate!
|
81
|
+
|
82
|
+
def process(options)
|
83
|
+
# validate(params, model: Song.new) { |f| self["x"] = f.to_nested_hash }
|
84
|
+
validate(options["params"]) { |f| self["x"] = f.to_nested_hash }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# will create a Reform::Form for us.
|
89
|
+
it { Index.(title: "Falling Down")["x"].must_equal({"title"=>"Falling Down"}) }
|
90
|
+
end
|
91
|
+
|
92
|
+
# # TODO: in all step tests.
|
93
|
+
# describe "dependency injection" do
|
94
|
+
# class Delete < Trailblazer::Operation
|
95
|
+
# include Contract::Step
|
96
|
+
# end
|
97
|
+
|
98
|
+
# class Follow < Trailblazer::Operation
|
99
|
+
# include Contract::Step
|
100
|
+
# end
|
101
|
+
|
102
|
+
# # inject contract instance via constructor.
|
103
|
+
# it { Delete.({}, "contract" => "contract/instance")["contract"].must_equal "contract/instance" }
|
104
|
+
# # inject contract class.
|
105
|
+
# it { Follow.({}, "contract.default.class" => Form)["contract"].class.must_equal Form }
|
106
|
+
# end
|
107
|
+
|
108
|
+
|
109
|
+
# # contract(model, [admin: true]).validate
|
110
|
+
# class Create < Trailblazer::Operation
|
111
|
+
# include Test::ReturnProcess
|
112
|
+
# include Contract::Explicit
|
113
|
+
|
114
|
+
# def call(options:false)
|
115
|
+
# return contract(model: Object, options: { admin: true }).validate if options
|
116
|
+
# contract(model: Object).validate
|
117
|
+
# end
|
118
|
+
# end
|
119
|
+
|
120
|
+
# # inject class, pass in model and options when constructing.
|
121
|
+
# # contract(model)
|
122
|
+
# it { Create.({}, "contract.default.class" => Form).must_equal "ContractTest::Form: Object {}" }
|
123
|
+
# # contract(model, options)
|
124
|
+
# it { Create.({ options: true }, "contract.default.class" => Form).must_equal "ContractTest::Form: Object {:admin=>true}" }
|
125
|
+
|
126
|
+
# # ::contract Form
|
127
|
+
# # contract(model).validate
|
128
|
+
# class Update < Trailblazer::Operation
|
129
|
+
# include Test::ReturnProcess
|
130
|
+
# include Contract::Explicit
|
131
|
+
|
132
|
+
# = Form
|
133
|
+
|
134
|
+
# def call(*)
|
135
|
+
# contract.validate
|
136
|
+
# end
|
137
|
+
|
138
|
+
# include Model( :Builder
|
139
|
+
# ) def model!(*)
|
140
|
+
# Object
|
141
|
+
# end
|
142
|
+
# end
|
143
|
+
|
144
|
+
# # use the class contract.
|
145
|
+
# it { Update.().must_equal "ContractTest::Form: Object {}" }
|
146
|
+
# # injected contract overrides class.
|
147
|
+
# it { Update.({}, "contract.default.class" => Injected = Class.new(Form)).must_equal "ContractTest::Injected: Object {}" }
|
148
|
+
|
149
|
+
# # passing Constant into #contract
|
150
|
+
# # contract(Object.new, { title: "Bad Feeling" }, Contract)
|
151
|
+
# class Operation < Trailblazer::Operation
|
152
|
+
# include Contract::Explicit
|
153
|
+
|
154
|
+
# class Contract < Reform::Form
|
155
|
+
# property :title, virtual: true
|
156
|
+
# end
|
157
|
+
|
158
|
+
# def process(params)
|
159
|
+
# contract(model: Object.new, options: { title: "Bad Feeling" }, contract_class: Contract)
|
160
|
+
|
161
|
+
# validate(params)
|
162
|
+
# end
|
163
|
+
# end
|
164
|
+
|
165
|
+
# # allow using #contract to inject model, options and class.
|
166
|
+
# it do
|
167
|
+
# contract = Operation.(id: 1)["contract"]
|
168
|
+
# contract.title.must_equal "Bad Feeling"
|
169
|
+
# contract.must_be_instance_of Operation::Contract
|
170
|
+
# end
|
171
|
+
|
172
|
+
# # allow using #contract before #validate.
|
173
|
+
# class Upsert < Trailblazer::Operation
|
174
|
+
# include Contract::Explicit
|
175
|
+
|
176
|
+
# contract do
|
177
|
+
# property :id
|
178
|
+
# property :title
|
179
|
+
# property :length
|
180
|
+
# end
|
181
|
+
|
182
|
+
# def process(params)
|
183
|
+
# self["model"] = Struct.new(:id, :title, :length).new
|
184
|
+
# contract.id = 1
|
185
|
+
# validate(params) { contract.length = 3 }
|
186
|
+
# end
|
187
|
+
# end
|
188
|
+
|
189
|
+
# it do
|
190
|
+
# contract = Upsert.(title: "Beethoven")["contract"]
|
191
|
+
# contract.id.must_equal 1
|
192
|
+
# contract.title.must_equal "Beethoven"
|
193
|
+
# contract.length.must_equal 3
|
194
|
+
# end
|
195
|
+
# end
|
196
|
+
|
197
|
+
#---
|
198
|
+
#- validate
|
199
|
+
class ValidateTest < Minitest::Spec
|
200
|
+
class Create < Trailblazer::Operation
|
201
|
+
extend Contract::DSL
|
7
202
|
contract do
|
8
|
-
property :id
|
9
203
|
property :title
|
10
|
-
|
204
|
+
validates :title, presence: true
|
11
205
|
end
|
12
206
|
|
13
|
-
def process(params)
|
14
|
-
@model = Struct.new(:id, :title, :length).new
|
15
207
|
|
16
|
-
|
17
|
-
|
18
|
-
|
208
|
+
include Procedural::Validate
|
209
|
+
def process(options)
|
210
|
+
if validate(options["params"])
|
211
|
+
self["x"] = "works!"
|
212
|
+
true
|
213
|
+
else
|
214
|
+
self["x"] = "try again"
|
215
|
+
false
|
19
216
|
end
|
20
217
|
end
|
218
|
+
|
219
|
+
self.| Model( Song, :new ) # FIXME.
|
220
|
+
self.| Contract::Build()
|
221
|
+
self.& :process
|
222
|
+
end
|
223
|
+
|
224
|
+
# validate returns the #validate result
|
225
|
+
it do
|
226
|
+
Create.(title: nil)["x"].must_equal "try again"
|
227
|
+
Create.(title: nil).success?.must_equal false
|
228
|
+
end
|
229
|
+
it { Create.(title: "SVG")["x"].must_equal "works!" }
|
230
|
+
it { Create.(title: "SVG").success?.must_equal true }
|
231
|
+
|
232
|
+
# result object from validation.
|
233
|
+
#- result object, contract
|
234
|
+
it { Create.(title: 1)["result.contract.default"].success?.must_equal true }
|
235
|
+
it { Create.(title: 1)["result.contract.default"].errors.messages.must_equal({}) } # FIXME: change API with Fran.
|
236
|
+
it { Create.(title: nil)["result.contract.default"].success?.must_equal false }
|
237
|
+
it { Create.(title: nil)["result.contract.default"].errors.messages.must_equal({:title=>["can't be blank"]}) } # FIXME: change API with Fran.
|
238
|
+
# #---
|
239
|
+
# # validate with block returns result.
|
240
|
+
# class Update < Trailblazer::Operation
|
241
|
+
# include Contract::Explicit
|
242
|
+
# contract Form
|
243
|
+
|
244
|
+
# def process(params)
|
245
|
+
# self["x"] = validate(params) { }
|
246
|
+
# end
|
247
|
+
# end
|
248
|
+
|
249
|
+
# it { Update.(false)["x"].must_equal false}
|
250
|
+
# it { Update.(true)["x"]. must_equal true}
|
251
|
+
|
252
|
+
#---
|
253
|
+
# Contract::Validate[]
|
254
|
+
class Update < Trailblazer::Operation
|
255
|
+
extend Contract::DSL
|
256
|
+
contract do
|
257
|
+
property :title
|
258
|
+
validates :title, presence: true
|
259
|
+
end
|
260
|
+
|
261
|
+
self.| Model( Song, :new ) # FIXME.
|
262
|
+
self.| Contract::Build()
|
263
|
+
self.| Contract::Validate() # generic validate call for you.
|
264
|
+
|
265
|
+
# include Procedural::Validate
|
266
|
+
->(*) { validate(options["params"][:song]) } # <-- TODO
|
21
267
|
end
|
22
268
|
|
23
|
-
#
|
269
|
+
# success
|
24
270
|
it do
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
271
|
+
result = Update.(title: "SVG")
|
272
|
+
result.success?.must_equal true
|
273
|
+
result["result.contract.default"].success?.must_equal true
|
274
|
+
result["result.contract.default"].errors.messages.must_equal({})
|
275
|
+
end
|
276
|
+
|
277
|
+
# failure
|
278
|
+
it do
|
279
|
+
result = Update.(title: nil)
|
280
|
+
result.success?.must_equal false
|
281
|
+
result["result.contract.default"].success?.must_equal false
|
282
|
+
result["result.contract.default"].errors.messages.must_equal({:title=>["can't be blank"]})
|
283
|
+
end
|
284
|
+
|
285
|
+
#---
|
286
|
+
# Contract::Validate[key: :song]
|
287
|
+
class Upsert < Trailblazer::Operation
|
288
|
+
extend Contract::DSL
|
289
|
+
contract do
|
290
|
+
property :title
|
291
|
+
validates :title, presence: true
|
292
|
+
end
|
293
|
+
|
294
|
+
self.| Model( Song, :new ) # FIXME.
|
295
|
+
self.| Contract::Build()
|
296
|
+
self.| Contract::Validate( key: :song) # generic validate call for you.
|
297
|
+
# ->(*) { validate(options["params"][:song]) } # <-- TODO
|
298
|
+
end
|
299
|
+
|
300
|
+
# success
|
301
|
+
it { Upsert.(song: { title: "SVG" }).success?.must_equal true }
|
302
|
+
# failure
|
303
|
+
it { Upsert.(song: { title: nil }).success?.must_equal false }
|
304
|
+
# key not found
|
305
|
+
it { Upsert.().success?.must_equal false }
|
306
|
+
|
307
|
+
#---
|
308
|
+
# params.validate gets set (TODO: change in 2.1)
|
309
|
+
it { Upsert.(song: { title: "SVG" })["params"].must_equal({:song=>{:title=>"SVG"}}) }
|
310
|
+
it { Upsert.(song: { title: "SVG" })["params.validate"].must_equal({:title=>"SVG"}) }
|
311
|
+
|
312
|
+
#---
|
313
|
+
#- inheritance
|
314
|
+
class New < Upsert
|
29
315
|
end
|
30
|
-
|
316
|
+
|
317
|
+
it { New["pipetree"].inspect.must_equal %{[>>operation.new,&model.build,>contract.build,&validate.params.extract,&contract.validate]} }
|
318
|
+
end
|
319
|
+
|
320
|
+
# #---
|
321
|
+
# # allow using #contract to inject model and arguments.
|
322
|
+
# class OperationContractWithOptionsTest < Minitest::Spec
|
323
|
+
# # contract(model, title: "Bad Feeling")
|
324
|
+
# class Operation < Trailblazer::Operation
|
325
|
+
# include Contract::Explicit
|
326
|
+
# contract do
|
327
|
+
# property :id
|
328
|
+
# property :title, virtual: true
|
329
|
+
# end
|
330
|
+
|
331
|
+
# def process(params)
|
332
|
+
# model = Struct.new(:id).new
|
333
|
+
|
334
|
+
# contract(model: model, options: { title: "Bad Feeling" })
|
335
|
+
|
336
|
+
# validate(params)
|
337
|
+
# end
|
338
|
+
# end
|
339
|
+
|
340
|
+
# it do
|
341
|
+
# op = Operation.(id: 1)
|
342
|
+
# op["contract"].id.must_equal 1
|
343
|
+
# op["contract"].title.must_equal "Bad Feeling"
|
344
|
+
# end
|
345
|
+
|
346
|
+
# # contract({ song: song, album: album }, title: "Medicine Balls")
|
347
|
+
# class CompositionOperation < Trailblazer::Operation
|
348
|
+
# include Contract::Explicit
|
349
|
+
# contract do
|
350
|
+
# include Reform::Form::Composition
|
351
|
+
# property :song_id, on: :song
|
352
|
+
# property :album_name, on: :album
|
353
|
+
# property :title, virtual: true
|
354
|
+
# end
|
355
|
+
|
356
|
+
# def process(params)
|
357
|
+
# song = Struct.new(:song_id).new(1)
|
358
|
+
# album = Struct.new(:album_name).new("Forever Malcom Young")
|
359
|
+
|
360
|
+
# contract(model: { song: song, album: album }, options: { title: "Medicine Balls" })
|
361
|
+
|
362
|
+
# validate(params)
|
363
|
+
# end
|
364
|
+
# end
|
365
|
+
|
366
|
+
# it do
|
367
|
+
# contract = CompositionOperation.({})["contract"]
|
368
|
+
# contract.song_id.must_equal 1
|
369
|
+
# contract.album_name.must_equal "Forever Malcom Young"
|
370
|
+
# contract.title.must_equal "Medicine Balls"
|
371
|
+
# end
|
372
|
+
|
373
|
+
# # validate(params, { song: song, album: album }, title: "Medicine Balls")
|
374
|
+
# class CompositionValidateOperation < Trailblazer::Operation
|
375
|
+
# include Contract::Explicit
|
376
|
+
# contract do
|
377
|
+
# include Reform::Form::Composition
|
378
|
+
# property :song_id, on: :song
|
379
|
+
# property :album_name, on: :album
|
380
|
+
# property :title, virtual: true
|
381
|
+
# end
|
382
|
+
|
383
|
+
# def process(params)
|
384
|
+
# song = Struct.new(:song_id).new(1)
|
385
|
+
# album = Struct.new(:album_name).new("Forever Malcom Young")
|
386
|
+
|
387
|
+
# validate(params, model: { song: song, album: album }, options: { title: "Medicine Balls" })
|
388
|
+
# end
|
389
|
+
# end
|
390
|
+
|
391
|
+
# it do
|
392
|
+
# contract = CompositionValidateOperation.({})["contract"]
|
393
|
+
# contract.song_id.must_equal 1
|
394
|
+
# contract.album_name.must_equal "Forever Malcom Young"
|
395
|
+
# contract.title.must_equal "Medicine Balls"
|
396
|
+
# end
|
397
|
+
end
|
398
|
+
|
399
|
+
# TODO: full stack test with validate, process, save, etc.
|
400
|
+
# with model!
|