trailblazer 2.0.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +13 -0
- data/COMM-LICENSE +21 -21
- data/Gemfile +3 -0
- data/README.md +15 -77
- data/Rakefile +1 -2
- data/doc/operation-2017.png +0 -0
- data/lib/trailblazer.rb +0 -1
- data/lib/trailblazer/operation/callback.rb +10 -19
- data/lib/trailblazer/operation/contract.rb +7 -8
- data/lib/trailblazer/operation/guard.rb +4 -13
- data/lib/trailblazer/operation/model.rb +30 -32
- data/lib/trailblazer/operation/nested.rb +21 -32
- data/lib/trailblazer/operation/persist.rb +4 -8
- data/lib/trailblazer/operation/policy.rb +5 -15
- data/lib/trailblazer/operation/pundit.rb +8 -17
- data/lib/trailblazer/operation/rescue.rb +17 -17
- data/lib/trailblazer/operation/validate.rb +34 -21
- data/lib/trailblazer/operation/wrap.rb +12 -25
- data/lib/trailblazer/version.rb +1 -1
- data/test/docs/dry_test.rb +4 -4
- data/test/docs/fast_test.rb +164 -0
- data/test/docs/guard_test.rb +2 -2
- data/test/docs/macro_test.rb +36 -0
- data/test/docs/model_test.rb +52 -0
- data/test/docs/nested_test.rb +101 -4
- data/test/docs/operation_test.rb +225 -1
- data/test/docs/pundit_test.rb +18 -17
- data/test/docs/rescue_test.rb +3 -3
- data/test/docs/wrap_test.rb +1 -0
- data/test/operation/callback_test.rb +5 -5
- data/test/operation/contract_test.rb +30 -19
- data/test/operation/dsl/callback_test.rb +2 -2
- data/test/operation/dsl/contract_test.rb +4 -4
- data/test/operation/model_test.rb +12 -57
- data/test/operation/persist_test.rb +7 -7
- data/test/operation/pipedream_test.rb +9 -9
- data/test/operation/pipetree_test.rb +5 -5
- data/test/operation/pundit_test.rb +7 -7
- data/test/operation/resolver_test.rb +47 -47
- data/trailblazer.gemspec +1 -1
- metadata +18 -11
- data/lib/trailblazer/operation/builder.rb +0 -24
- data/lib/trailblazer/operation/resolver.rb +0 -22
- data/test/controller_test.rb +0 -115
- data/test/operation/builder_test.rb +0 -89
data/test/docs/pundit_test.rb
CHANGED
@@ -1,20 +1,20 @@
|
|
1
1
|
require "test_helper"
|
2
2
|
|
3
|
-
#:policy
|
4
|
-
class MyPolicy
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
#:policy
|
4
|
+
class MyPolicy
|
5
|
+
def initialize(user, model)
|
6
|
+
@user, @model = user, model
|
7
|
+
end
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
def create?
|
10
|
+
@user == Module && @model.id.nil?
|
11
|
+
end
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
def new?
|
14
|
+
@user == Class
|
15
|
+
end
|
15
16
|
end
|
16
|
-
end
|
17
|
-
#:policy end
|
17
|
+
#:policy end
|
18
18
|
|
19
19
|
#--
|
20
20
|
# with policy
|
@@ -29,6 +29,7 @@ class DocsPunditProcTest < Minitest::Spec
|
|
29
29
|
end
|
30
30
|
#:pundit end
|
31
31
|
|
32
|
+
it { Create["pipetree"].inspect.must_equal %{[>operation.new,>model.build,>policy.default.eval]} }
|
32
33
|
it { Create.({}, "current_user" => Module).inspect("model").must_equal %{<Result:true [#<struct DocsPunditProcTest::Song id=nil>] >} }
|
33
34
|
it { Create.({} ).inspect("model").must_equal %{<Result:false [#<struct DocsPunditProcTest::Song id=nil>] >} }
|
34
35
|
|
@@ -45,10 +46,10 @@ class DocsPunditProcTest < Minitest::Spec
|
|
45
46
|
#---
|
46
47
|
#- override
|
47
48
|
class New < Create
|
48
|
-
|
49
|
+
step Policy::Pundit( MyPolicy, :new? ), override: true
|
49
50
|
end
|
50
51
|
|
51
|
-
it { New["pipetree"].inspect.must_equal %{[
|
52
|
+
it { New["pipetree"].inspect.must_equal %{[>operation.new,>model.build,>policy.default.eval]} }
|
52
53
|
it { New.({}, "current_user" => Class ).inspect("model").must_equal %{<Result:true [#<struct DocsPunditProcTest::Song id=nil>] >} }
|
53
54
|
it { New.({}, "current_user" => nil ).inspect("model").must_equal %{<Result:false [#<struct DocsPunditProcTest::Song id=nil>] >} }
|
54
55
|
|
@@ -60,12 +61,12 @@ class DocsPunditProcTest < Minitest::Spec
|
|
60
61
|
end
|
61
62
|
|
62
63
|
class Update < Edit
|
63
|
-
|
64
|
+
step Policy::Pundit( MyPolicy, :new?, name: "first" ), override: true
|
64
65
|
end
|
65
66
|
|
66
|
-
it { Edit["pipetree"].inspect.must_equal %{[
|
67
|
+
it { Edit["pipetree"].inspect.must_equal %{[>operation.new,>policy.first.eval,>policy.second.eval]} }
|
67
68
|
it { Edit.({}, "current_user" => Class).inspect("model").must_equal %{<Result:false [nil] >} }
|
68
|
-
it { Update["pipetree"].inspect.must_equal %{[
|
69
|
+
it { Update["pipetree"].inspect.must_equal %{[>operation.new,>policy.first.eval,>policy.second.eval]} }
|
69
70
|
it { Update.({}, "current_user" => Class).inspect("model").must_equal %{<Result:true [nil] >} }
|
70
71
|
|
71
72
|
#---
|
data/test/docs/rescue_test.rb
CHANGED
@@ -17,13 +17,13 @@ class NestedRescueTest < Minitest::Spec
|
|
17
17
|
step ->(options) { options["b"] = true }
|
18
18
|
success ->(options) { raise A if options["raise-a"] }
|
19
19
|
step ->(options) { options["c"] = true }
|
20
|
-
|
20
|
+
failure ->(options) { options["inner-err"] = true }
|
21
21
|
}
|
22
22
|
step ->(options) { options["e"] = true }
|
23
23
|
failure ->(options) { options["outer-err"] = true }
|
24
24
|
end
|
25
25
|
|
26
|
-
it { NestedInsanity["pipetree"].inspect.must_equal %{[
|
26
|
+
it { NestedInsanity["pipetree"].inspect.must_equal %{[>operation.new,>Rescue:10,>rescue_test.rb:22,<rescue_test.rb:23]} }
|
27
27
|
it { NestedInsanity.({}).inspect("a", "y", "z", "b", "c", "e", "inner-err", "outer-err").must_equal %{<Result:true [true, true, true, true, true, true, nil, nil] >} }
|
28
28
|
it { NestedInsanity.({}, "raise-y" => true).inspect("a", "y", "z", "b", "c", "e", "inner-err", "outer-err").must_equal %{<Result:false [true, true, nil, nil, nil, nil, true, true] >} }
|
29
29
|
it { NestedInsanity.({}, "raise-a" => true).inspect("a", "y", "z", "b", "c", "e", "inner-err", "outer-err").must_equal %{<Result:false [true, true, true, true, nil, nil, nil, true] >} }
|
@@ -148,7 +148,7 @@ class RescueTest < Minitest::Spec
|
|
148
148
|
it { assert_raises(RuntimeError) { Create.( id: "RuntimeError!" ) } }
|
149
149
|
it do
|
150
150
|
Create.( id: 1, title: "Pie" )
|
151
|
-
Sequel.result.first.must_equal Pipetree::
|
151
|
+
Sequel.result.first.must_equal Pipetree::Railway::Right
|
152
152
|
end
|
153
153
|
end
|
154
154
|
end
|
data/test/docs/wrap_test.rb
CHANGED
@@ -13,10 +13,10 @@ class OperationCallbackTest < MiniTest::Spec
|
|
13
13
|
property :name
|
14
14
|
end
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
step Model( Song, :new )
|
17
|
+
step Contract::Build()
|
18
|
+
step Contract::Validate()
|
19
|
+
step Callback( :default )
|
20
20
|
|
21
21
|
|
22
22
|
extend Callback::DSL
|
@@ -55,7 +55,7 @@ class OperationCallbackTest < MiniTest::Spec
|
|
55
55
|
|
56
56
|
#---
|
57
57
|
#- inheritance
|
58
|
-
it { Update["pipetree"].inspect.must_equal %{[
|
58
|
+
it { Update["pipetree"].inspect.must_equal %{[>operation.new,>model.build,>contract.build,>contract.default.validate,>callback.default]} }
|
59
59
|
|
60
60
|
|
61
61
|
it "invokes all callbacks" do
|
@@ -1,5 +1,16 @@
|
|
1
1
|
require "test_helper"
|
2
|
-
|
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 { Create["pipetree"].inspect.must_equal %{[>operation.new,>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
|
+
|
3
14
|
|
4
15
|
require "dry/validation"
|
5
16
|
|
@@ -14,7 +25,7 @@ class DryValidationTest < Minitest::Spec
|
|
14
25
|
# required(:id).filled
|
15
26
|
# end
|
16
27
|
|
17
|
-
|
28
|
+
step :process
|
18
29
|
|
19
30
|
include Procedural::Validate
|
20
31
|
|
@@ -40,7 +51,7 @@ class DryValidationTest < Minitest::Spec
|
|
40
51
|
required(:id).filled
|
41
52
|
end)
|
42
53
|
|
43
|
-
|
54
|
+
step ->(options) { options["contract.params"].(options["params"]).success? }, before: "operation.new"
|
44
55
|
end
|
45
56
|
|
46
57
|
it { Update.( id: 1 ).success?.must_equal true }
|
@@ -71,10 +82,10 @@ class ContractTest < Minitest::Spec
|
|
71
82
|
property :title
|
72
83
|
end
|
73
84
|
|
74
|
-
|
75
|
-
#
|
76
|
-
|
77
|
-
|
85
|
+
success ->(options) { options["model"] = Song.new }
|
86
|
+
# step Model( Song, :new )
|
87
|
+
step Contract::Build()
|
88
|
+
step :process
|
78
89
|
|
79
90
|
include Procedural::Validate
|
80
91
|
# TODO: get model automatically in validate!
|
@@ -216,9 +227,9 @@ class ValidateTest < Minitest::Spec
|
|
216
227
|
end
|
217
228
|
end
|
218
229
|
|
219
|
-
|
220
|
-
|
221
|
-
|
230
|
+
step Model( Song, :new ) # FIXME.
|
231
|
+
step Contract::Build()
|
232
|
+
step :process
|
222
233
|
end
|
223
234
|
|
224
235
|
# validate returns the #validate result
|
@@ -258,9 +269,9 @@ class ValidateTest < Minitest::Spec
|
|
258
269
|
validates :title, presence: true
|
259
270
|
end
|
260
271
|
|
261
|
-
|
262
|
-
|
263
|
-
|
272
|
+
step Model( Song, :new ) # FIXME.
|
273
|
+
step Contract::Build()
|
274
|
+
step Contract::Validate() # generic validate call for you.
|
264
275
|
|
265
276
|
# include Procedural::Validate
|
266
277
|
->(*) { validate(options["params"][:song]) } # <-- TODO
|
@@ -291,9 +302,9 @@ class ValidateTest < Minitest::Spec
|
|
291
302
|
validates :title, presence: true
|
292
303
|
end
|
293
304
|
|
294
|
-
|
295
|
-
|
296
|
-
|
305
|
+
step Model( Song, :new ) # FIXME.
|
306
|
+
step Contract::Build()
|
307
|
+
step Contract::Validate( key: :song) # generic validate call for you.
|
297
308
|
# ->(*) { validate(options["params"][:song]) } # <-- TODO
|
298
309
|
step Contract::Persist( method: :sync )
|
299
310
|
end
|
@@ -315,14 +326,14 @@ class ValidateTest < Minitest::Spec
|
|
315
326
|
class New < Upsert
|
316
327
|
end
|
317
328
|
|
318
|
-
it { New["pipetree"].inspect.must_equal %{[
|
329
|
+
it { New["pipetree"].inspect.must_equal %{[>operation.new,>model.build,>contract.build,>contract.default.validate,>persist.save]} }
|
319
330
|
|
320
331
|
#- overwriting Validate
|
321
332
|
class NewHit < Upsert
|
322
|
-
|
333
|
+
step Contract::Validate( key: :hit ), override: true
|
323
334
|
end
|
324
335
|
|
325
|
-
it { NewHit["pipetree"].inspect.must_equal %{[
|
336
|
+
it { NewHit["pipetree"].inspect.must_equal %{[>operation.new,>model.build,>contract.build,>contract.default.validate,>persist.save]} }
|
326
337
|
it { NewHit.(:hit => { title: "Hooray For Me" }).inspect("model").must_equal %{<Result:true [#<struct ContractTest::Song title=\"Hooray For Me\">] >} }
|
327
338
|
end
|
328
339
|
|
@@ -37,7 +37,7 @@ class DslCallbackTest < MiniTest::Spec
|
|
37
37
|
def admin_default!(*); _invocations << :admin_default!; end
|
38
38
|
def after_save!(*); _invocations << :after_save!; end
|
39
39
|
|
40
|
-
|
40
|
+
step Trailblazer::Operation::Callback[:after_save]
|
41
41
|
end
|
42
42
|
|
43
43
|
def default!(*); _invocations << :default!; end
|
@@ -61,7 +61,7 @@ class DslCallbackTest < MiniTest::Spec
|
|
61
61
|
extend Callback::DSL
|
62
62
|
callback :after_save, AfterSaveCallback
|
63
63
|
|
64
|
-
|
64
|
+
step Callback[:after_save]
|
65
65
|
end
|
66
66
|
|
67
67
|
it { OpWithExternalCallback.("title"=>"Thunder Rising").must_equal([:after_save!]) }
|
@@ -14,10 +14,10 @@ class DslContractTest < MiniTest::Spec
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def self.included(includer)
|
17
|
-
includer
|
18
|
-
includer
|
19
|
-
includer
|
20
|
-
includer
|
17
|
+
includer.step Trailblazer::Operation::Model( OpenStruct, :new )
|
18
|
+
includer.step Trailblazer::Operation::Contract::Build()
|
19
|
+
includer.step Trailblazer::Operation::Contract::Validate()
|
20
|
+
includer.step Trailblazer::Operation::Contract::Persist( method: :sync )
|
21
21
|
# includer.> ->(op, *) { op["x"] = [] }
|
22
22
|
end
|
23
23
|
end
|
@@ -9,34 +9,27 @@ class ModelTest < Minitest::Spec
|
|
9
9
|
#---
|
10
10
|
# use Model semantics, no customizations.
|
11
11
|
class Create < Trailblazer::Operation
|
12
|
-
|
12
|
+
step Model Song, :new
|
13
13
|
end
|
14
14
|
|
15
15
|
# :new new.
|
16
16
|
it { Create.({})["model"].inspect.must_equal %{#<struct ModelTest::Song id=nil>} }
|
17
17
|
|
18
18
|
class Update < Create
|
19
|
-
|
19
|
+
step Model( Song, :find ), override: true
|
20
20
|
end
|
21
21
|
|
22
22
|
# :find it
|
23
23
|
it { Update.({ id: 1 })["model"].inspect.must_equal %{#<struct ModelTest::Song id=1>} }
|
24
24
|
|
25
25
|
#- inheritance
|
26
|
-
it { Update["pipetree"].inspect.must_equal %{[
|
27
|
-
|
28
|
-
# override #model with Model included.
|
29
|
-
class Upsert < Create
|
30
|
-
def model!(params); params.to_s end
|
31
|
-
end
|
32
|
-
|
33
|
-
it { Upsert.(id: 9)["model"].must_equal %{{:id=>9}} }
|
26
|
+
it { Update["pipetree"].inspect.must_equal %{[>operation.new,>model.build]} }
|
34
27
|
|
35
28
|
#---
|
36
29
|
# :find_by, exceptionless.
|
37
30
|
class Find < Trailblazer::Operation
|
38
|
-
|
39
|
-
|
31
|
+
step Model Song, :find_by
|
32
|
+
step :process
|
40
33
|
|
41
34
|
def process(*); self["x"] = true end
|
42
35
|
end
|
@@ -56,50 +49,12 @@ class ModelTest < Minitest::Spec
|
|
56
49
|
Find.(id: 9)["model"].inspect.must_equal %{#<struct ModelTest::Song id=9>}
|
57
50
|
end
|
58
51
|
|
59
|
-
#---
|
60
|
-
#
|
61
|
-
class
|
62
|
-
|
63
|
-
|
64
|
-
end
|
65
|
-
|
66
|
-
it { Delete.(id: 1)["model"].must_equal %{{:id=>1}} }
|
67
|
-
|
68
|
-
#---
|
69
|
-
# creating the model before operation instantiation (ex Model::External)
|
70
|
-
class Show < Create
|
71
|
-
extend Model::BuildMethods # FIXME: how do we communicate that and prevent the include from Model[] ?
|
72
|
-
self.| Model( Song, :update ), before: "operation.new"
|
73
|
-
end
|
74
|
-
|
75
|
-
it { Show.({id: 1})["model"].inspect.must_equal %{#<struct ModelTest::Song id=1>} }
|
76
|
-
|
77
|
-
|
78
|
-
# TODO: with builder!
|
79
|
-
|
80
|
-
#---
|
81
|
-
# provide your own object ModelBuilder that includes BuilderMethods.
|
82
|
-
# this tests that BuildMethods is interchangable and acts as an example how to decouple
|
83
|
-
# the model building from the operation.
|
84
|
-
class Index < Trailblazer::Operation
|
85
|
-
# DISCUSS: help user to do this kind of behavior?
|
86
|
-
# model Song, :find # ModelBuilder can read this via skills that we pass to it.
|
87
|
-
self["model.class"] = Song
|
88
|
-
self["model.action"] = :find
|
89
|
-
|
90
|
-
# this is to be able to use BuildModel.
|
91
|
-
class ModelBuilder
|
92
|
-
include Trailblazer::Operation::Model::BuildMethods # #instantiate_model and so on.
|
93
|
-
alias_method :call, :model!
|
94
|
-
|
95
|
-
def initialize(skills); @delegator = skills end
|
96
|
-
|
97
|
-
extend Uber::Delegates
|
98
|
-
delegates :@delegator, :[]
|
99
|
-
end
|
100
|
-
|
101
|
-
self.> ->(options) { options["model"] = ModelBuilder.new(options).(options["params"]) }, after: "operation.new"
|
102
|
-
end
|
52
|
+
# #---
|
53
|
+
# # creating the model before operation instantiation (ex Model::External)
|
54
|
+
# class Show < Create
|
55
|
+
# extend Model::BuildMethods # FIXME: how do we communicate that and prevent the include from Model[] ?
|
56
|
+
# step Model( Song, :update ), before: "operation.new"
|
57
|
+
# end
|
103
58
|
|
104
|
-
it {
|
59
|
+
# it { Show.({id: 1})["model"].inspect.must_equal %{#<struct ModelTest::Song id=1>} }
|
105
60
|
end
|
@@ -11,12 +11,12 @@ class PersistTest < Minitest::Spec
|
|
11
11
|
property :title
|
12
12
|
end
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
14
|
+
step Model( Song, :new )
|
15
|
+
step Contract::Build()
|
16
|
+
step Contract::Validate()
|
17
|
+
failure ->(options) { options["1. fail"] = "Validate" }
|
18
|
+
step Contract::Persist()
|
19
|
+
failure ->(options) { options["2. fail"] = "Persist" }
|
20
20
|
end
|
21
21
|
|
22
22
|
it { Create.(title: "In Recital")["model"].title.must_equal "In Recital" }
|
@@ -35,7 +35,7 @@ class PersistTest < Minitest::Spec
|
|
35
35
|
class Update < Create
|
36
36
|
end
|
37
37
|
|
38
|
-
it { Update["pipetree"].inspect.must_equal %{[
|
38
|
+
it { Update["pipetree"].inspect.must_equal %{[>operation.new,>model.build,>contract.build,>contract.default.validate,<persist_test.rb:17,>persist.save,<persist_test.rb:19]} }
|
39
39
|
|
40
40
|
#---
|
41
41
|
it do
|
@@ -20,22 +20,22 @@ class PipedreamTest < Minitest::Spec
|
|
20
20
|
|
21
21
|
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
23
|
+
step Model[ Song, :new ] # model!)
|
24
|
+
step Policy::Guard[ ->(options){ options["current_user"] == ::Module } ]
|
25
|
+
step Contract[ MyContract]
|
26
|
+
step Policy[ Auth, :user_and_model?]
|
27
|
+
failure Contract[ MyContract]
|
28
28
|
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
29
|
+
# step :model
|
30
|
+
# step :guard
|
31
|
+
# step :contract
|
32
32
|
|
33
33
|
|
34
34
|
# ok Model[Song, :new] # model!)
|
35
35
|
# ok Policy::Guard[ ->(options){ options["current_user"] == ::Module } ]
|
36
36
|
# ok Contract[MyContract]
|
37
37
|
# fail Contract[MyContract]
|
38
|
-
#
|
38
|
+
# step> "contract"
|
39
39
|
|
40
40
|
# | :bla
|
41
41
|
# | ->
|
@@ -42,21 +42,21 @@ class PipetreeTest < Minitest::Spec
|
|
42
42
|
|
43
43
|
MyValidate = ->(input, options) { res= input.validate(options["params"]) { |f| f.sync } }
|
44
44
|
# we can have a separate persist step and wrap in transaction. where do we pass contract, though?
|
45
|
-
|
45
|
+
step MyValidate, before: Call #replace: Contract::ValidLegacySwitch
|
46
46
|
#
|
47
47
|
MyAfterSave = ->(input, options) { input["after_save"] = true }
|
48
|
-
|
48
|
+
success MyAfterSave, after: MyValidate
|
49
49
|
|
50
50
|
ValidateFailureLogger = ->(input, options) { input["validate fail"] = true }
|
51
|
-
|
51
|
+
failure ValidateFailureLogger, after: MyValidate
|
52
52
|
|
53
|
-
|
53
|
+
success ->(input, options) { input.process(options["params"]) }, replace: Call, name: "my.params"
|
54
54
|
|
55
55
|
include Model
|
56
56
|
|
57
57
|
LogBreach = ->(input, options) { input.log_breach! }
|
58
58
|
|
59
|
-
|
59
|
+
failure LogBreach, after: Policy::Evaluate
|
60
60
|
|
61
61
|
model Song
|
62
62
|
policy ->(*) { self["current_user"] }
|