trailblazer 2.1.0.beta4 → 2.1.0.beta5

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +194 -502
  3. data/CHANGES.md +4 -0
  4. data/CONTRIBUTING.md +170 -0
  5. data/Gemfile +4 -1
  6. data/README.md +183 -40
  7. data/Rakefile +6 -2
  8. data/lib/trailblazer/version.rb +1 -1
  9. data/lib/trailblazer.rb +3 -12
  10. data/test/{operation/dsl → dsl}/contract_test.rb +2 -2
  11. data/trailblazer.gemspec +3 -3
  12. metadata +17 -63
  13. data/lib/trailblazer/operation/contract.rb +0 -82
  14. data/lib/trailblazer/operation/guard.rb +0 -18
  15. data/lib/trailblazer/operation/model.rb +0 -65
  16. data/lib/trailblazer/operation/nested.rb +0 -91
  17. data/lib/trailblazer/operation/persist.rb +0 -14
  18. data/lib/trailblazer/operation/policy.rb +0 -44
  19. data/lib/trailblazer/operation/pundit.rb +0 -38
  20. data/lib/trailblazer/operation/representer.rb +0 -36
  21. data/lib/trailblazer/operation/rescue.rb +0 -24
  22. data/lib/trailblazer/operation/validate.rb +0 -74
  23. data/lib/trailblazer/operation/wrap.rb +0 -64
  24. data/test/docs/contract_test.rb +0 -545
  25. data/test/docs/dry_test.rb +0 -31
  26. data/test/docs/guard_test.rb +0 -162
  27. data/test/docs/macro_test.rb +0 -36
  28. data/test/docs/model_test.rb +0 -75
  29. data/test/docs/nested_test.rb +0 -300
  30. data/test/docs/policy_test.rb +0 -2
  31. data/test/docs/pundit_test.rb +0 -133
  32. data/test/docs/representer_test.rb +0 -268
  33. data/test/docs/rescue_test.rb +0 -154
  34. data/test/docs/wrap_test.rb +0 -219
  35. data/test/nested_test.rb +0 -293
  36. data/test/operation/contract_test.rb +0 -290
  37. data/test/operation/dsl/representer_test.rb +0 -169
  38. data/test/operation/model_test.rb +0 -54
  39. data/test/operation/persist_test.rb +0 -51
  40. data/test/operation/pundit_test.rb +0 -106
  41. data/test/operation/representer_test.rb +0 -254
data/test/nested_test.rb DELETED
@@ -1,293 +0,0 @@
1
- require "test_helper"
2
-
3
- # exec_context nach Nested
4
-
5
- class NestedTest < Minitest::Spec
6
- #---
7
- #- shared data
8
- class B < Trailblazer::Operation
9
- pass ->(options, **) { options["can.B.see.A.mutable.data?"] = options["mutable.data.from.A"] }
10
- pass ->(options, **) { options["can.B.see.current_user?"] = options["current_user"] }
11
- pass ->(options, **) { options["can.B.see.params?"] = options["params"] }
12
- pass ->(options, **) { options["can.B.see.A.class.data?"] = options["A.class.data"] }
13
- pass ->(options, **) { options["can.B.see.container.data?"] = options["some.container.data"] }
14
- pass ->(options, **) { options["mutable.data.from.B"] = "from B!" }
15
- end
16
-
17
- class A < Trailblazer::Operation
18
- extend ClassDependencies
19
- self["A.class.data"] = "yes" # class data on A
20
-
21
- pass ->(options, **) { options["mutable.data.from.A"] = "from A!" } # mutable data on A
22
- step Nested( B )
23
- pass ->(options, **) { options["can.A.see.B.mutable.data?"] = options["mutable.data.from.B"] }
24
- end
25
-
26
- #---
27
- #- default behavior: share everything.
28
- # no containers
29
- # no runtime data
30
- # no params
31
- it do
32
- result = A.("params" => {})
33
- # everything from A visible
34
- result["A.class.data"]. must_equal "yes"
35
- result["mutable.data.from.A"].must_equal "from A!"
36
-
37
- # B can see everything
38
- result["can.B.see.A.mutable.data?"].must_equal "from A!"
39
- result["can.B.see.current_user?"].must_be_nil
40
- result["can.B.see.params?"].must_equal({})
41
- result["can.B.see.A.class.data?"].must_equal "yes"
42
- result["can.B.see.container.data?"].must_be_nil
43
-
44
- result["can.A.see.B.mutable.data?"].must_equal "from B!"
45
- end
46
-
47
- #---
48
- #- Nested::NonActivity
49
- class AlmostB < Trailblazer::Operation
50
- step ->(options, is_successful:raise, **) { is_successful } # {AlmostB} fails if {is_successful} isn't true.
51
- step ->(options, **) { options["can.B.see.A.mutable.data?"] = options["mutable.data.from.A"] }
52
- pass ->(options, **) { options["mutable.data.from.B"] = "from AlmostB!" }
53
- end
54
-
55
- #- Nested( ->{} )
56
- class SomeNestedWithProc < Trailblazer::Operation
57
- extend ClassDependencies
58
- self["A.class.data"] = "yes" # class data on A
59
-
60
- Decider = ->(options, use_class:raise, **) { use_class }
61
-
62
- pass ->(options, **) { options["mutable.data.from.A"] = "from A!" } # mutable data on A
63
- step Nested( Decider )
64
- pass ->(options, **) { options["can.A.see.B.mutable.data?"] = options["mutable.data.from.B"] }
65
- end
66
-
67
- #- Nested( Callable )
68
- class SomeNestedWithCallable < Trailblazer::Operation
69
- extend ClassDependencies
70
- self["A.class.data"] = "yes" # class data on A
71
-
72
- class Decider
73
- def self.call(options, use_class:raise, **)
74
- use_class
75
- end
76
- end
77
-
78
- pass ->(options, **) { options["mutable.data.from.A"] = "from A!" } # mutable data on A
79
- step Nested( Decider )
80
- pass ->(options, **) { options["can.A.see.B.mutable.data?"] = options["mutable.data.from.B"] }
81
- end
82
-
83
- #- Nested( :method )
84
- class SomeNestedWithMethod < Trailblazer::Operation
85
- extend ClassDependencies
86
- self["A.class.data"] = "yes" # class data on A
87
-
88
- def decider(options, use_class:raise, **)
89
- use_class
90
- end
91
-
92
- pass ->(options, **) { options["mutable.data.from.A"] = "from A!" } # mutable data on A
93
- step Nested( :decider )
94
- pass ->(options, **) { options["can.A.see.B.mutable.data?"] = options["mutable.data.from.B"] }
95
- end
96
-
97
-
98
- #- test callable
99
- # B with Callable, successful
100
- it do
101
- result = SomeNestedWithCallable.("params" => {}, use_class: B)
102
- assert_b(result, is_successful: "whatever")
103
- end
104
-
105
- # AlmostB with Callable, successful
106
- it do
107
- result = SomeNestedWithCallable.("params" => {}, use_class: AlmostB, is_successful: true)
108
- assert_almost_b(result, is_successful: true)
109
- end
110
-
111
- # AlmostB with Callable, failure
112
- it do
113
- result = SomeNestedWithCallable.("params" => {}, use_class: AlmostB, is_successful: false)
114
- assert_almost_b(result, is_successful: false)
115
- end
116
-
117
- #- test proc
118
- # B with proc, successful
119
- it do
120
- result = SomeNestedWithProc.("params" => {}, use_class: B)
121
- assert_b(result, is_successful: "whatever")
122
- end
123
-
124
- # AlmostB with proc, successful
125
- it do
126
- result = SomeNestedWithProc.("params" => {}, use_class: AlmostB, is_successful: true)
127
-
128
- assert_almost_b(result, is_successful: true)
129
- end
130
-
131
- # AlmostB with proc, failure.
132
- it do
133
- result = SomeNestedWithProc.("params" => {}, use_class: AlmostB, is_successful: false)
134
-
135
- assert_almost_b(result, is_successful: false)
136
- end
137
-
138
- #- test :method
139
- # B with method, successful
140
- it do
141
- result = SomeNestedWithMethod.("params" => {}, use_class: B)
142
- assert_b(result, is_successful: "whatever")
143
- end
144
-
145
- # AlmostB with method, successful
146
- it do
147
- result = SomeNestedWithMethod.("params" => {}, use_class: AlmostB, is_successful: true)
148
-
149
- assert_almost_b(result, is_successful: true)
150
- end
151
-
152
- # AlmostB with method, failure.
153
- it do
154
- result = SomeNestedWithMethod.("params" => {}, use_class: AlmostB, is_successful: false)
155
-
156
- assert_almost_b(result, is_successful: false)
157
- end
158
-
159
-
160
- def assert_almost_b(result, is_successful:raise)
161
- result.success?.must_equal is_successful # AlmostB was successful, so A is successful.
162
-
163
- # everything from A visible
164
- result["A.class.data"]. must_equal "yes"
165
- result["mutable.data.from.A"].must_equal "from A!"
166
-
167
- # AlmostB doesn't look for everything
168
- result["can.B.see.current_user?"].must_be_nil
169
- result["can.B.see.params?"].must_be_nil
170
- if is_successful
171
- result["can.B.see.A.mutable.data?"].must_equal "from A!"
172
- result["can.B.see.A.class.data?"].must_be_nil # we don't look for it.
173
- result["can.A.see.B.mutable.data?"].must_equal "from AlmostB!"
174
- else
175
- result["can.B.see.A.mutable.data?"].must_be_nil
176
- result["can.B.see.A.class.data?"].must_be_nil
177
- result["can.A.see.B.mutable.data?"].must_be_nil
178
- end
179
- result["can.B.see.container.data?"].must_be_nil
180
-
181
-
182
- # result[:is_successful].must_equal is_successful # FIXME: this is wrong!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! key is symbol
183
- end
184
-
185
- def assert_b(result, is_successful:raise)
186
- # everything from A visible
187
- result["A.class.data"]. must_equal "yes"
188
- result["mutable.data.from.A"].must_equal "from A!"
189
-
190
- # B can see everything
191
- result["can.B.see.A.mutable.data?"].must_equal "from A!"
192
- result["can.B.see.current_user?"].must_be_nil
193
- result["can.B.see.params?"].must_equal({})
194
- result["can.B.see.A.class.data?"].must_equal "yes"
195
- result["can.B.see.container.data?"].must_be_nil
196
-
197
- result["can.A.see.B.mutable.data?"].must_equal "from B!"
198
-
199
- result[:is_successful].must_be_nil
200
- result.success?.must_equal true # B was successful, so A is successful.
201
- end
202
-
203
-
204
- #---
205
- #- :exec_context
206
- class Create < Trailblazer::Operation
207
- class Edit < Trailblazer::Operation
208
- step :c!
209
-
210
- def c!(options, **); options[:c] = 1 end
211
- end
212
-
213
- step :a!
214
- step Nested( Edit )
215
- step :b!
216
-
217
- def a!(options, **); options[:a] = 2 end
218
- def b!(options, **); options[:b] = 3 end
219
- end
220
-
221
- it { Create.().inspect(:a, :b, :c).must_equal %{<Result:true [2, 3, 1] >} }
222
- end
223
-
224
- class NestedWithFastTrackTest < Minitest::Spec
225
- module Steps
226
- def b(options, a:, **)
227
- options["b"] = a+1
228
- end
229
-
230
- def f(options, **)
231
- options["f"] = 3
232
- end
233
- end
234
-
235
- class Edit < Trailblazer::Operation
236
- pass :a, pass_fast: true
237
-
238
- def a(options, **)
239
- options["a"] = 1
240
- end
241
- end
242
-
243
- class Update < Trailblazer::Operation
244
- step Nested( Edit )
245
- step :b
246
- fail :f
247
-
248
- include Steps
249
- end
250
-
251
- # from Nested straight to End.pass_fast.
252
- it { Update.({}).inspect("a", "b", "f").must_equal %{<Result:true [1, nil, nil] >} }
253
-
254
- #- Nested, pass_fast: true
255
- class Upsert < Trailblazer::Operation
256
- step Nested( Edit ), pass_fast: true # this option is unnecessary.
257
- step :b
258
- fail :f
259
-
260
- include Steps
261
- end
262
-
263
- # from Nested straight to End.pass_fast.
264
- it { Upsert.({}).inspect("a", "b", "f").must_equal %{<Result:true [1, nil, nil] >} }
265
-
266
- #- mapping
267
- #- Nested, :pass_fast => :failure
268
- it "attaches :pass_fast => :failure" do
269
- op = Class.new(Trailblazer::Operation) do
270
- step Nested( Edit ), Output(:pass_fast) => Track(:failure)
271
- step :b
272
- fail :f
273
-
274
- include Steps
275
- end
276
-
277
- # from Nested to :failure track.
278
- op.({}).inspect("a", "b", "c", "f").must_equal %{<Result:false [1, nil, nil, 3] >}
279
- end
280
-
281
- it "goes straigt to End.failure" do
282
- op = Class.new(Trailblazer::Operation) do
283
- step Nested( Edit ), Output(:pass_fast) => "End.failure"
284
- step :b
285
- fail :f
286
-
287
- include Steps
288
- end
289
-
290
- # from Nested straight to End.failure, no fail step will be visited.
291
- op.({}).inspect("a", "b", "c", "f").must_equal %{<Result:false [1, nil, nil, nil] >}
292
- end
293
- end
@@ -1,290 +0,0 @@
1
- require "test_helper"
2
-
3
- # class ContractExtractMacroTest < Minitest::Spec
4
- # class Create < Trailblazer::Operation
5
- # step Contract::Validate::Extract( key: "song", params_path: "x" )
6
- # end
7
-
8
- # it { Trailblazer::Operation::Inspect.(Create).must_equal %{[>x]} }
9
- # it { Create.({}).inspect("x").must_equal %{<Result:false [nil] >} }
10
- # it { Create.({ "song" => Object }).inspect("x").must_equal %{<Result:true [Object] >} }
11
- # end
12
-
13
- class ContractTest < Minitest::Spec
14
- Song = Struct.new(:title)
15
- # # generic form for testing.
16
- # class Form
17
- # def initialize(model, options={})
18
- # @inspect = "#{self.class}: #{model} #{options.inspect}"
19
- # end
20
-
21
- # def validate
22
- # @inspect
23
- # end
24
- # end
25
-
26
- # # TODO: in all step tests.
27
- # describe "dependency injection" do
28
- # class Delete < Trailblazer::Operation
29
- # include Contract::Step
30
- # end
31
-
32
- # class Follow < Trailblazer::Operation
33
- # include Contract::Step
34
- # end
35
-
36
- # # inject contract instance via constructor.
37
- # it { Delete.({}, "contract" => "contract/instance")["contract"].must_equal "contract/instance" }
38
- # # inject contract class.
39
- # it { Follow.({}, "contract.default.class" => Form)["contract"].class.must_equal Form }
40
- # end
41
-
42
-
43
- # # contract(model, [admin: true]).validate
44
- # class Create < Trailblazer::Operation
45
- # include Test::ReturnProcess
46
- # include Contract::Explicit
47
-
48
- # def call(options:false)
49
- # return contract(model: Object, options: { admin: true }).validate if options
50
- # contract(model: Object).validate
51
- # end
52
- # end
53
-
54
- # # inject class, pass in model and options when constructing.
55
- # # contract(model)
56
- # it { Create.({}, "contract.default.class" => Form).must_equal "ContractTest::Form: Object {}" }
57
- # # contract(model, options)
58
- # it { Create.({ options: true }, "contract.default.class" => Form).must_equal "ContractTest::Form: Object {:admin=>true}" }
59
-
60
- # # ::contract Form
61
- # # contract(model).validate
62
- # class Update < Trailblazer::Operation
63
- # include Test::ReturnProcess
64
- # include Contract::Explicit
65
-
66
- # = Form
67
-
68
- # def call(*)
69
- # contract.validate
70
- # end
71
-
72
- # include Model( :Builder
73
- # ) def model!(*)
74
- # Object
75
- # end
76
- # end
77
-
78
- # # use the class contract.
79
- # it { Update.().must_equal "ContractTest::Form: Object {}" }
80
- # # injected contract overrides class.
81
- # it { Update.({}, "contract.default.class" => Injected = Class.new(Form)).must_equal "ContractTest::Injected: Object {}" }
82
-
83
- # # passing Constant into #contract
84
- # # contract(Object.new, { title: "Bad Feeling" }, Contract)
85
- # class Operation < Trailblazer::Operation
86
- # include Contract::Explicit
87
-
88
- # class Contract < Reform::Form
89
- # property :title, virtual: true
90
- # end
91
-
92
- # def process(params)
93
- # contract(model: Object.new, options: { title: "Bad Feeling" }, contract_class: Contract)
94
-
95
- # validate(params)
96
- # end
97
- # end
98
-
99
- # # allow using #contract to inject model, options and class.
100
- # it do
101
- # contract = Operation.(id: 1)["contract"]
102
- # contract.title.must_equal "Bad Feeling"
103
- # contract.must_be_instance_of Operation::Contract
104
- # end
105
-
106
- # # allow using #contract before #validate.
107
- # class Upsert < Trailblazer::Operation
108
- # include Contract::Explicit
109
-
110
- # contract do
111
- # property :id
112
- # property :title
113
- # property :length
114
- # end
115
-
116
- # def process(params)
117
- # self["model"] = Struct.new(:id, :title, :length).new
118
- # contract.id = 1
119
- # validate(params) { contract.length = 3 }
120
- # end
121
- # end
122
-
123
- # it do
124
- # contract = Upsert.(title: "Beethoven")["contract"]
125
- # contract.id.must_equal 1
126
- # contract.title.must_equal "Beethoven"
127
- # contract.length.must_equal 3
128
- # end
129
- # end
130
-
131
- #---
132
- #- validate
133
- class ValidateTest < Minitest::Spec
134
-
135
- #---
136
- # Contract::Validate[]
137
- class Update < Trailblazer::Operation
138
- class Form < Reform::Form
139
- property :title
140
- validates :title, presence: true
141
- end
142
-
143
- step Model( Song, :new )
144
- step Contract::Build( constant: Form )
145
- step Contract::Validate() # generic validate call for you.
146
-
147
- # include Procedural::Validate
148
- ->(*) { validate(options["params"][:song]) } # <-- TODO
149
- end
150
-
151
- # success
152
- it do
153
- result = Update.(params: {title: "SVG"})
154
- result.success?.must_equal true
155
- result["result.contract.default"].success?.must_equal true
156
- result["result.contract.default"].errors.messages.must_equal({})
157
- end
158
-
159
- # failure
160
- it do
161
- result = Update.(params: {title: nil})
162
- result.success?.must_equal false
163
- result["result.contract.default"].success?.must_equal false
164
- result["result.contract.default"].errors.messages.must_equal({:title=>["can't be blank"]})
165
- end
166
-
167
- #---
168
- # Contract::Validate[key: :song]
169
- class Upsert < Trailblazer::Operation
170
- class Form < Reform::Form
171
- property :title
172
- validates :title, presence: true
173
- end
174
-
175
- step Model( Song, :new ) # FIXME.
176
- step Contract::Build( constant: Form )
177
- step Contract::Validate( key: :song) # generic validate call for you.
178
- # ->(*) { validate(options["params"][:song]) } # <-- TODO
179
- step Contract::Persist( method: :sync )
180
- end
181
-
182
- # success
183
- it { Upsert.(params: {song: { title: "SVG" }}).success?.must_equal true }
184
- # failure
185
- it { Upsert.(params: {song: { title: nil }}).success?.must_equal false }
186
- # key not found
187
- it { Upsert.(params: {}).success?.must_equal false }
188
-
189
- #---
190
- # contract.default.params gets set (TODO: change in 2.1)
191
- it { Upsert.( params: {song: { title: "SVG" }})[:params].must_equal({:song=>{:title=>"SVG"}} ) }
192
- it { Upsert.( params: {song: { title: "SVG" }})["contract.default.params"].must_equal({:title=>"SVG"} ) }
193
-
194
- #---
195
- #- inheritance
196
- class New < Upsert
197
- end
198
-
199
- it { Trailblazer::Operation::Inspect.(New).must_equal %{[>model.build,>contract.build,>contract.default.validate,>persist.save]} }
200
-
201
- #- overwriting Validate
202
- class NewHit < Upsert
203
- step Contract::Validate( key: :hit ), override: true
204
- end
205
-
206
- it { Trailblazer::Operation::Inspect.(NewHit).must_equal %{[>model.build,>contract.build,>contract.default.validate,>persist.save]} }
207
- it { NewHit.(params: {:hit => { title: "Hooray For Me" }}).inspect(:model).must_equal %{<Result:true [#<struct ContractTest::Song title=\"Hooray For Me\">] >} }
208
- end
209
-
210
- # #---
211
- # # allow using #contract to inject model and arguments.
212
- # class OperationContractWithOptionsTest < Minitest::Spec
213
- # # contract(model, title: "Bad Feeling")
214
- # class Operation < Trailblazer::Operation
215
- # include Contract::Explicit
216
- # contract do
217
- # property :id
218
- # property :title, virtual: true
219
- # end
220
-
221
- # def process(params)
222
- # model = Struct.new(:id).new
223
-
224
- # contract(model: model, options: { title: "Bad Feeling" })
225
-
226
- # validate(params)
227
- # end
228
- # end
229
-
230
- # it do
231
- # op = Operation.(id: 1)
232
- # op["contract"].id.must_equal 1
233
- # op["contract"].title.must_equal "Bad Feeling"
234
- # end
235
-
236
- # # contract({ song: song, album: album }, title: "Medicine Balls")
237
- # class CompositionOperation < Trailblazer::Operation
238
- # include Contract::Explicit
239
- # contract do
240
- # include Reform::Form::Composition
241
- # property :song_id, on: :song
242
- # property :album_name, on: :album
243
- # property :title, virtual: true
244
- # end
245
-
246
- # def process(params)
247
- # song = Struct.new(:song_id).new(1)
248
- # album = Struct.new(:album_name).new("Forever Malcom Young")
249
-
250
- # contract(model: { song: song, album: album }, options: { title: "Medicine Balls" })
251
-
252
- # validate(params)
253
- # end
254
- # end
255
-
256
- # it do
257
- # contract = CompositionOperation.({})["contract"]
258
- # contract.song_id.must_equal 1
259
- # contract.album_name.must_equal "Forever Malcom Young"
260
- # contract.title.must_equal "Medicine Balls"
261
- # end
262
-
263
- # # validate(params, { song: song, album: album }, title: "Medicine Balls")
264
- # class CompositionValidateOperation < Trailblazer::Operation
265
- # include Contract::Explicit
266
- # contract do
267
- # include Reform::Form::Composition
268
- # property :song_id, on: :song
269
- # property :album_name, on: :album
270
- # property :title, virtual: true
271
- # end
272
-
273
- # def process(params)
274
- # song = Struct.new(:song_id).new(1)
275
- # album = Struct.new(:album_name).new("Forever Malcom Young")
276
-
277
- # validate(params, model: { song: song, album: album }, options: { title: "Medicine Balls" })
278
- # end
279
- # end
280
-
281
- # it do
282
- # contract = CompositionValidateOperation.({})["contract"]
283
- # contract.song_id.must_equal 1
284
- # contract.album_name.must_equal "Forever Malcom Young"
285
- # contract.title.must_equal "Medicine Balls"
286
- # end
287
- end
288
-
289
- # TODO: full stack test with validate, process, save, etc.
290
- # with model!