trailblazer 2.0.0 → 2.0.1

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +13 -0
  3. data/COMM-LICENSE +21 -21
  4. data/Gemfile +3 -0
  5. data/README.md +15 -77
  6. data/Rakefile +1 -2
  7. data/doc/operation-2017.png +0 -0
  8. data/lib/trailblazer.rb +0 -1
  9. data/lib/trailblazer/operation/callback.rb +10 -19
  10. data/lib/trailblazer/operation/contract.rb +7 -8
  11. data/lib/trailblazer/operation/guard.rb +4 -13
  12. data/lib/trailblazer/operation/model.rb +30 -32
  13. data/lib/trailblazer/operation/nested.rb +21 -32
  14. data/lib/trailblazer/operation/persist.rb +4 -8
  15. data/lib/trailblazer/operation/policy.rb +5 -15
  16. data/lib/trailblazer/operation/pundit.rb +8 -17
  17. data/lib/trailblazer/operation/rescue.rb +17 -17
  18. data/lib/trailblazer/operation/validate.rb +34 -21
  19. data/lib/trailblazer/operation/wrap.rb +12 -25
  20. data/lib/trailblazer/version.rb +1 -1
  21. data/test/docs/dry_test.rb +4 -4
  22. data/test/docs/fast_test.rb +164 -0
  23. data/test/docs/guard_test.rb +2 -2
  24. data/test/docs/macro_test.rb +36 -0
  25. data/test/docs/model_test.rb +52 -0
  26. data/test/docs/nested_test.rb +101 -4
  27. data/test/docs/operation_test.rb +225 -1
  28. data/test/docs/pundit_test.rb +18 -17
  29. data/test/docs/rescue_test.rb +3 -3
  30. data/test/docs/wrap_test.rb +1 -0
  31. data/test/operation/callback_test.rb +5 -5
  32. data/test/operation/contract_test.rb +30 -19
  33. data/test/operation/dsl/callback_test.rb +2 -2
  34. data/test/operation/dsl/contract_test.rb +4 -4
  35. data/test/operation/model_test.rb +12 -57
  36. data/test/operation/persist_test.rb +7 -7
  37. data/test/operation/pipedream_test.rb +9 -9
  38. data/test/operation/pipetree_test.rb +5 -5
  39. data/test/operation/pundit_test.rb +7 -7
  40. data/test/operation/resolver_test.rb +47 -47
  41. data/trailblazer.gemspec +1 -1
  42. metadata +18 -11
  43. data/lib/trailblazer/operation/builder.rb +0 -24
  44. data/lib/trailblazer/operation/resolver.rb +0 -22
  45. data/test/controller_test.rb +0 -115
  46. data/test/operation/builder_test.rb +0 -89
@@ -29,10 +29,10 @@ class DocsGuardProcTest < Minitest::Spec
29
29
  #---
30
30
  #- Guard inheritance
31
31
  class New < Create
32
- override Policy::Guard( ->(options) { options["current_user"] } )
32
+ step Policy::Guard( ->(options) { options["current_user"] } ), override: true
33
33
  end
34
34
 
35
- it { New["pipetree"].inspect.must_equal %{[>>operation.new,&policy.default.eval,&process]} }
35
+ it { New["pipetree"].inspect.must_equal %{[>operation.new,>policy.default.eval,>process]} }
36
36
  end
37
37
 
38
38
  #---
@@ -0,0 +1,36 @@
1
+ require "test_helper"
2
+
3
+ class DocsMacroTest < Minitest::Spec
4
+ #:simple
5
+ module Macro
6
+ def self.MyPolicy(allowed_role: "admin")
7
+ step = ->(input, options) { options["current_user"].type == allowed_role }
8
+
9
+ [ step, name: "my_policy.#{allowed_role}" ] # :before, :replace, etc. work, too.
10
+ end
11
+ end
12
+ #:simple end
13
+
14
+ #:simple-op
15
+ class Create < Trailblazer::Operation
16
+ step Macro::MyPolicy( allowed_role: "manager" )
17
+ # ..
18
+ end
19
+ #:simple-op end
20
+
21
+ =begin
22
+ it do
23
+ #:simple-pipe
24
+ puts Create["pipetree"].inspect(style: :rows) #=>
25
+ 0 ========================>operation.new
26
+ 1 ====================>my_policy.manager
27
+ #:simple-pipe end
28
+ end
29
+ =end
30
+
31
+ it { Create["pipetree"].inspect.must_equal %{[>operation.new,>my_policy.manager]} }
32
+ end
33
+
34
+ # injectable option
35
+ # nested pipe
36
+ # using macros in macros
@@ -0,0 +1,52 @@
1
+ require "test_helper"
2
+
3
+ class DocsModelTest < Minitest::Spec
4
+ Song = Struct.new(:id, :title) do
5
+ def self.find_by(id:nil)
6
+ id.nil? ? nil : new(id)
7
+ end
8
+ end
9
+
10
+ #:op
11
+ class Create < Trailblazer::Operation
12
+ step Model( Song, :new )
13
+ # ..
14
+ end
15
+ #:op end
16
+
17
+ it do
18
+ #:create
19
+ result = Create.({})
20
+ result["model"] #=> #<struct Song id=nil, title=nil>
21
+ #:create end
22
+
23
+ result["model"].inspect.must_equal %{#<struct DocsModelTest::Song id=nil, title=nil>}
24
+ end
25
+
26
+ #:update
27
+ class Update < Trailblazer::Operation
28
+ step Model( Song, :find_by )
29
+ # ..
30
+ end
31
+ #:update end
32
+
33
+ it do
34
+ #:update-ok
35
+ result = Update.({ id: 1 })
36
+ result["model"] #=> #<struct Song id=1, title="Roxanne">
37
+ #:update-ok end
38
+
39
+ result["model"].inspect.must_equal %{#<struct DocsModelTest::Song id=1, title=nil>}
40
+ end
41
+
42
+ it do
43
+ #:update-fail
44
+ result = Update.({})
45
+ result["model"] #=> nil
46
+ result.success? #=> false
47
+ #:update-fail end
48
+
49
+ result["model"].must_equal nil
50
+ result.success?.must_equal false
51
+ end
52
+ end
@@ -73,15 +73,15 @@ class DocsNestedOperationTest < Minitest::Spec
73
73
 
74
74
  #- shared data
75
75
  class B < Trailblazer::Operation
76
- self.> ->(options) { options["can.B.see.it?"] = options["this.should.not.be.visible.in.B"] }
77
- self.> ->(options) { options["can.B.see.current_user?"] = options["current_user"] }
78
- self.> ->(options) { options["can.B.see.A.class.data?"] = options["A.class.data"] }
76
+ success ->(options) { options["can.B.see.it?"] = options["this.should.not.be.visible.in.B"] }
77
+ success ->(options) { options["can.B.see.current_user?"] = options["current_user"] }
78
+ success ->(options) { options["can.B.see.A.class.data?"] = options["A.class.data"] }
79
79
  end
80
80
 
81
81
  class A < Trailblazer::Operation
82
82
  self["A.class.data"] = true
83
83
 
84
- self.> ->(options) { options["this.should.not.be.visible.in.B"] = true }
84
+ success ->(options) { options["this.should.not.be.visible.in.B"] = true }
85
85
  step Nested B
86
86
  end
87
87
 
@@ -115,3 +115,100 @@ class NestedClassLevelTest < Minitest::Spec
115
115
  it { Create.().inspect("x", "y").must_equal %{<Result:true [true, true] >} }
116
116
  it { Create.(); Create["class"].must_equal nil }
117
117
  end
118
+
119
+ class NestedWithCallableTest < Minitest::Spec
120
+ Song = Struct.new(:id, :title)
121
+
122
+ class X < Trailblazer::Operation
123
+ step ->(options) { options["x"] = true }
124
+ end
125
+
126
+ class Y < Trailblazer::Operation
127
+ step ->(options) { options["y"] = true }
128
+ end
129
+
130
+ class A < Trailblazer::Operation
131
+ step ->(options) { options["z"] = true }
132
+ step Nested( ->(options, *) { options["class"] } )
133
+ end
134
+
135
+ it { A.({}, "class" => X).inspect("x", "y", "z").must_equal "<Result:true [true, nil, true] >" }
136
+ it { A.({}, "class" => Y).inspect("x", "y", "z").must_equal "<Result:true [nil, true, true] >" }
137
+ # it { Create.({}).inspect("x", "y", "z").must_equal "<Result:true [nil, true, true] >" }
138
+
139
+ class Song
140
+ module Contract
141
+ class Create < Reform::Form
142
+ property :title
143
+ end
144
+ end
145
+ end
146
+
147
+ User = Struct.new(:is_admin) do
148
+ def admin?
149
+ !! is_admin
150
+ end
151
+ end
152
+
153
+ class Create < Trailblazer::Operation
154
+ step Nested( ->(options, current_user:nil, **) { current_user.admin? ? Admin : NeedsModeration })
155
+
156
+ class NeedsModeration < Trailblazer::Operation
157
+ step Model( Song, :new )
158
+ step Contract::Build( constant: Song::Contract::Create )
159
+ step Contract::Validate()
160
+ step :notify_moderator!
161
+
162
+ def notify_moderator!(options, **)
163
+ #~noti
164
+ options["x"] = true
165
+ #~noti end
166
+ end
167
+ end
168
+
169
+ class Admin < Trailblazer::Operation # TODO: test if current_user is passed in.
170
+
171
+ end
172
+ end
173
+
174
+ let (:admin) { User.new(true) }
175
+ let (:anonymous) { User.new(false) }
176
+
177
+ it { Create.({}, "current_user" => anonymous).inspect("x").must_equal %{<Result:true [true] >} }
178
+ it { Create.({}, "current_user" => admin) .inspect("x").must_equal %{<Result:true [nil] >} }
179
+
180
+ #:method
181
+ class Update < Trailblazer::Operation
182
+ step Nested( :build! )
183
+
184
+ def build!(options, current_user:nil, **)
185
+ current_user.admin? ? Create::Admin : Create::NeedsModeration
186
+ end
187
+ end
188
+ #:method end
189
+
190
+ it { Update.({}, "current_user" => anonymous).inspect("x").must_equal %{<Result:true [true] >} }
191
+ it { Update.({}, "current_user" => admin) .inspect("x").must_equal %{<Result:true [nil] >} }
192
+
193
+ #:callable-builder
194
+ class MyBuilder
195
+ extend Uber::Callable
196
+
197
+ def self.call(options, current_user:nil, **)
198
+ current_user.admin? ? Create::Admin : Create::NeedsModeration
199
+ end
200
+ end
201
+ #:callable-builder end
202
+
203
+ #:callable
204
+ class Delete < Trailblazer::Operation
205
+ step Nested( MyBuilder )
206
+ # ..
207
+ end
208
+ #:callable end
209
+
210
+ it { Delete.({}, "current_user" => anonymous).inspect("x").must_equal %{<Result:true [true] >} }
211
+ it { Delete.({}, "current_user" => admin) .inspect("x").must_equal %{<Result:true [nil] >} }
212
+ end
213
+
214
+ # builder: Nested + deviate to left if nil / skip_track if true
@@ -4,6 +4,32 @@ class DocsOperationExampleTest < Minitest::Spec
4
4
  Song = Struct.new(:id, :title, :created_by) do
5
5
  def save; true; end
6
6
  end
7
+ User = Struct.new(:name)
8
+
9
+ #:invocation-dep
10
+ class Create < Trailblazer::Operation
11
+ step Model( Song, :new )
12
+ step :assign_current_user!
13
+ # ..
14
+ def assign_current_user!(options)
15
+ options["model"].created_by = options["current_user"]
16
+ end
17
+ end
18
+ #:invocation-dep end
19
+
20
+ it do
21
+ current_user = User.new("Ema")
22
+ #:invocation-dep-call
23
+ result = Create.( { title: "Roxanne" }, "current_user" => current_user )
24
+ #:invocation-dep-call end
25
+
26
+ #:invocation-dep-res
27
+ result["current_user"] #=> #<User name="Ema">
28
+ result["model"] #=> #<Song id=nil, title=nil, created_by=#<User name="Ema">>
29
+ #:invocation-dep-res end
30
+ end
31
+
32
+ it { Create.({ title: "Roxanne" }, "current_user" => Module).inspect("model").must_equal %{<Result:true [#<struct DocsOperationExampleTest::Song id=nil, title=nil, created_by=Module>] >} }
7
33
 
8
34
  #:op
9
35
  class Song::Create < Trailblazer::Operation
@@ -41,7 +67,7 @@ class DndTest < Minitest::Spec
41
67
  step :authorize!
42
68
  failure :auth_err!
43
69
  step :save!
44
- self.< Wrap
70
+ failure Wrap
45
71
  end
46
72
  end
47
73
 
@@ -182,3 +208,201 @@ class DocsOperationAPIExampleTest < Minitest::Spec
182
208
  it { Song::Create.({ }).inspect("model").must_equal %{<Result:false [#<struct DocsOperationAPIExampleTest::Song id=nil, title=nil, created_by=nil>] >} }
183
209
  it { Song::Create.({ title: "Nothin'" }, "current_user"=>Module).inspect("model").must_equal %{<Result:true [#<struct DocsOperationAPIExampleTest::Song id=nil, title="Nothin'", created_by=Module>] >} }
184
210
  end
211
+
212
+
213
+ class DocsOperationInheritanceTest < Minitest::Spec
214
+ Song = Struct.new(:id, :title, :created_by) do
215
+ def save; true; end
216
+ end
217
+
218
+ class MyContract < Reform::Form
219
+ property :title
220
+ validates :title, presence: true
221
+ end
222
+
223
+ #:inh-new
224
+ class New < Trailblazer::Operation
225
+ step Model( Song, :new )
226
+ step Contract::Build( constant: MyContract )
227
+ end
228
+ #:inh-new end
229
+
230
+ puts New["pipetree"].inspect(style: :row)
231
+ =begin
232
+ #:inh-new-pipe
233
+ 0 =======================>>operation.new
234
+ 1 ==========================&model.build
235
+ 2 =======================>contract.build
236
+ #:inh-new-pipe end
237
+ =end
238
+
239
+ #:inh-create
240
+ class Create < New
241
+ step Contract::Validate()
242
+ step Contract::Persist()
243
+ end
244
+ #:inh-create end
245
+
246
+ puts Create["pipetree"].inspect(style: :row)
247
+ =begin
248
+ #:inh-create-pipe
249
+ 0 =======================>>operation.new
250
+ 1 ==========================&model.build
251
+ 2 =======================>contract.build
252
+ 3 ==============&contract.default.params
253
+ 4 ============&contract.default.validate
254
+ 5 =========================&persist.save
255
+ #:inh-create-pipe end
256
+ =end
257
+
258
+ module MyApp
259
+ end
260
+
261
+ #:override-app
262
+ module MyApp::Operation
263
+ class New < Trailblazer::Operation
264
+ extend Contract::DSL
265
+
266
+ contract do
267
+ property :title
268
+ end
269
+
270
+ step Model( nil, :new )
271
+ step Contract::Build()
272
+ end
273
+ end
274
+ #:override-app end
275
+
276
+ #:override-new
277
+ class Song::New < MyApp::Operation::New
278
+ step Model( Song, :new ), override: true
279
+ end
280
+ #:override-new end
281
+
282
+ puts Song::New["pipetree"].inspect(style: :row)
283
+ =begin
284
+ #:override-pipe
285
+ Song::New["pipetree"].inspect(style: :row)
286
+ 0 =======================>>operation.new
287
+ 1 ==========================&model.build
288
+ 2 =======================>contract.build
289
+ #:override-pipe end
290
+ =end
291
+
292
+ it do
293
+ Song::New["pipetree"].inspect.must_equal %{[>operation.new,>model.build,>contract.build]}
294
+ Song::New.().inspect("model").must_equal %{<Result:true [#<struct DocsOperationInheritanceTest::Song id=nil, title=nil, created_by=nil>] >}
295
+ end
296
+ end
297
+
298
+ class DocsOperationStepOptionsTest < Minitest::Spec
299
+ Song = Struct.new(:title) do
300
+ def self.find_by(*)
301
+ nil
302
+ end
303
+ end
304
+
305
+ class AutomaticNameTest < Minitest::Spec
306
+ #:name-auto
307
+ class New < Trailblazer::Operation
308
+ step Model( Song, :new )
309
+ end
310
+ #:name-auto end
311
+
312
+ puts New["pipetree"].inspect(style: :row)
313
+ =begin
314
+ #:name-auto-pipe
315
+ 0 =======================>>operation.new
316
+ 1 ==========================&model.build
317
+ #:name-auto-pipe end
318
+ =end
319
+
320
+ #:replace-inh
321
+ class Update < New
322
+ step Model(Song, :find_by), replace: "model.build"
323
+ end
324
+ #:replace-inh end
325
+
326
+ puts Update["pipetree"].inspect(style: :row)
327
+ =begin
328
+ #:replace-inh-pipe
329
+ 0 =======================>>operation.new
330
+ 2 ==========================&model.build
331
+ #:replace-inh-pipe end
332
+ =end
333
+
334
+ it { Update.({}).inspect("model").must_equal %{<Result:false [nil] >} }
335
+
336
+
337
+ # #:delete-inh
338
+ # class Noop < New
339
+ # step nil, delete: "model.build"
340
+ # end
341
+ # #:delete-inh end
342
+
343
+ # puts "yo"
344
+ # puts Update["pipetree"].inspect(style: :row)
345
+ # =begin
346
+ # #:delete-inh-pipe
347
+ # 0 =======================>>operation.new
348
+ # 2 ==========================&model.build
349
+ # #:delete-inh-pipe end
350
+ # =end
351
+
352
+ # it { Noop.({}).inspect("model").must_equal %{<Result:false [nil] >} }
353
+ end
354
+
355
+ class ManualNameTest < Minitest::Spec
356
+ #:name-manu
357
+ class New < Trailblazer::Operation
358
+ step Model( Song, :new ), name: "build.song.model"
359
+ step :validate_params!, name: "my.params.validate"
360
+ # ..
361
+ end
362
+ #:name-manu end
363
+
364
+ puts New["pipetree"].inspect(style: :row)
365
+ =begin
366
+ #:name-manu-pipe
367
+ 0 =======================>>operation.new
368
+ 1 =====================&build.song.model
369
+ 2 ===================&my.params.validate
370
+ #:name-manu-pipe end
371
+ =end
372
+ end
373
+
374
+ class BeforeTest < Minitest::Spec
375
+ #:pos-before
376
+ class New < Trailblazer::Operation
377
+ step Model( Song, :new )
378
+ step :validate_params!, before: "model.build"
379
+ # ..
380
+ end
381
+ #:pos-before end
382
+
383
+ puts New["pipetree"].inspect(style: :row)
384
+ =begin
385
+ #:pos-before-pipe
386
+ 0 =======================>>operation.new
387
+ 1 =====================&validate_params!
388
+ 2 ==========================&model.build
389
+ #:pos-before-pipe end
390
+ =end
391
+
392
+ #:pos-inh
393
+ class Create < New
394
+ step :policy!, after: "operation.new"
395
+ end
396
+ #:pos-inh end
397
+
398
+ puts Create["pipetree"].inspect(style: :row)
399
+ =begin
400
+ #:pos-inh-pipe
401
+ 0 =======================>>operation.new
402
+ 1 ==============================&policy!
403
+ 2 =====================&validate_params!
404
+ 3 ==========================&model.build
405
+ #:pos-inh-pipe end
406
+ =end
407
+ end
408
+ end