trailblazer 2.1.0.beta4 → 2.1.0.beta5

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -1,268 +0,0 @@
1
- require "test_helper"
2
- require "representable/json"
3
-
4
- #---
5
- # infer
6
- class DocsRepresenterInferTest < Minitest::Spec
7
- Song = Struct.new(:id, :title)
8
-
9
- #:infer
10
- class Create < Trailblazer::Operation
11
- class MyContract < Reform::Form
12
- property :id
13
- end
14
-
15
- step Model( Song, :new )
16
- step Contract::Build( constant: MyContract )
17
- step Contract::Validate( representer: Representer.infer(MyContract, format: Representable::JSON) )
18
- step Contract::Persist( method: :sync )
19
- end
20
- #:infer end
21
-
22
- let (:json) { MultiJson.dump(id: 1) }
23
- it { Create.( params: {}, document: json ).inspect(:model).must_equal %{<Result:true [#<struct DocsRepresenterInferTest::Song id=1, title=nil>] >} }
24
- end
25
-
26
- #---
27
- # explicit
28
- class DocsRepresenterExplicitTest < Minitest::Spec
29
- Song = Struct.new(:id, :title)
30
-
31
- #:explicit-rep
32
- class MyRepresenter < Representable::Decorator
33
- include Representable::JSON
34
- property :id
35
- end
36
- #:explicit-rep end
37
-
38
- #:explicit-op
39
- class Create < Trailblazer::Operation
40
- class MyContract < Reform::Form
41
- property :id
42
- end
43
-
44
- step Model( Song, :new )
45
- step Contract::Build( constant: MyContract )
46
- step Contract::Validate( representer: MyRepresenter ) # :representer
47
- step Contract::Persist( method: :sync )
48
- end
49
- #:explicit-op end
50
-
51
- let (:json) { MultiJson.dump(id: 1) }
52
- it { Create.(params: {}, document: json).inspect(:model).must_equal %{<Result:true [#<struct DocsRepresenterExplicitTest::Song id=1, title=nil>] >} }
53
- it do
54
- #:explicit-call
55
- Create.(params: {}, document: '{"id": 1}')
56
- #:explicit-call end
57
- end
58
-
59
- #- render
60
- it do
61
- #:render
62
- result = Create.( params: {}, document: '{"id": 1}' )
63
- json = result["representer.default.class"].new(result[:model]).to_json
64
- json #=> '{"id":1}'
65
- #:render end
66
- json.must_equal '{"id":1}'
67
- end
68
-
69
- #-
70
- # with dependency injection
71
- # overriding the JSON representer with an XML one.
72
- #:di-rep
73
- require "representable/xml"
74
-
75
- class MyXMLRepresenter < Representable::Decorator
76
- include Representable::XML
77
- property :id
78
- end
79
- #:di-rep end
80
-
81
- let (:xml) { %{<body><id>1</id></body>} }
82
- it do
83
- #:di-call
84
- result = Create.(params: {},
85
- document: '<body><id>1</id></body>',
86
- "representer.default.class" => MyXMLRepresenter # injection
87
- )
88
- #:di-call end
89
- result.inspect(:model).must_equal %{<Result:true [#<struct DocsRepresenterExplicitTest::Song id="1", title=nil>] >}
90
- end
91
- end
92
-
93
- #---
94
- # dependency injection
95
- class DocsRepresenterDITest < Minitest::Spec
96
- Song = Struct.new(:id, :title)
97
-
98
- class MyRepresenter < Representable::Decorator
99
- include Representable::JSON
100
- property :id
101
- end
102
-
103
- class Create < Trailblazer::Operation
104
- class MyContract < Reform::Form
105
- property :id
106
- end
107
-
108
- step Model( Song, :new )
109
- step Contract::Build( constant: MyContract )
110
- step Contract::Validate()
111
- step Contract::Persist( method: :sync )
112
- end
113
-
114
- let (:json) { MultiJson.dump(id: 1) }
115
- it { Create.(params: {}, document: json,
116
- "representer.default.class" => MyRepresenter).inspect(:model).must_equal %{<Result:true [#<struct DocsRepresenterDITest::Song id=1, title=nil>] >} }
117
- end
118
-
119
- #---
120
- # inline
121
- class DocsRepresenterInlineTest < Minitest::Spec
122
- Song = Struct.new(:id, :title)
123
-
124
- #:inline
125
- class Create < Trailblazer::Operation
126
- class MyContract < Reform::Form
127
- property :id
128
- end
129
-
130
- extend Representer::DSL
131
-
132
- representer do
133
- property :id
134
- end
135
-
136
- step Model( Song, :new )
137
- step Contract::Build( constant: MyContract )
138
- step Contract::Validate( representer: self["representer.default.class"] )
139
- step Contract::Persist( method: :sync )
140
- end
141
- #:inline end
142
-
143
- let (:json) { MultiJson.dump(id: 1) }
144
- it { Create.(params: {}, document: json).inspect(:model).must_equal %{<Result:true [#<struct DocsRepresenterInlineTest::Song id=1, title=nil>] >} }
145
- end
146
-
147
- #---
148
- # rendering
149
- class DocsRepresenterManualRenderTest < Minitest::Spec
150
- Song = Struct.new(:id, :title) do
151
- def self.find(id)
152
- new(id)
153
- end
154
- end
155
-
156
- class Show < Trailblazer::Operation
157
- extend Representer::DSL
158
- representer do
159
- property :id
160
- end
161
-
162
- step Model( Song, :find )
163
- end
164
-
165
- it do
166
- result =Show.(params: { id: 1 })
167
- json = result["representer.default.class"].new(result[:model]).to_json
168
- json.must_equal %{{"id":1}}
169
- end
170
- end
171
-
172
- #---
173
- # naming
174
-
175
- class DocsRepresenterNamingTest < Minitest::Spec
176
- MyRepresenter = Object
177
-
178
- #:naming
179
- class Create < Trailblazer::Operation
180
- extend Representer::DSL
181
- representer MyRepresenter
182
- end
183
-
184
- Create["representer.default.class"] #=> MyRepresenter
185
- #:naming end
186
- it { Create["representer.default.class"].must_be_kind_of MyRepresenter }
187
- end
188
-
189
- #---
190
- # rendering
191
- require "roar/json/hal"
192
-
193
- class DocsRepresenterFullExampleTest < Minitest::Spec
194
- Song = Struct.new(:id, :title) do
195
- def initialize(*)
196
- self.id = 1
197
- end
198
- end
199
-
200
- #:errors-rep
201
- class ErrorsRepresenter < Representable::Decorator
202
- include Representable::JSON
203
- collection :errors
204
- end
205
- #:errors-rep end
206
-
207
- #:full
208
- class Create < Trailblazer::Operation
209
- extend Contract::DSL
210
- extend Representer::DSL
211
-
212
- contract do
213
- property :title
214
- validates :title, presence: true
215
- end
216
-
217
- representer :parse do
218
- property :title
219
- end
220
-
221
- representer :render do
222
- include Roar::JSON::HAL
223
-
224
- property :id
225
- property :title
226
- link(:self) { "/songs/#{represented.id}" }
227
- end
228
-
229
- representer :errors, ErrorsRepresenter # explicit reference.
230
-
231
- step Model( Song, :new )
232
- step Contract::Build()
233
- step Contract::Validate( representer: self["representer.parse.class"] )
234
- step Contract::Persist( method: :sync )
235
- end
236
- #:full end
237
-
238
- it do
239
- result =Create.(params: {}, document: '{"title": "Tested"}')
240
-
241
- json = result["representer.render.class"].new(result[:model]).to_json
242
-
243
- json.must_equal %{{"id":1,"title":"Tested","_links":{"self":{"href":"/songs/1"}}}}
244
-
245
-
246
- #:full-call
247
- def create
248
- result = Create.(params, document: request.body.read)
249
-
250
- if result.success?
251
- result["representer.render.class"].new(result[:model]).to_json
252
- else
253
- result["representer.errors.class"].new(result["result.contract.default"]).to_json
254
- end
255
- end
256
- #:full-call end
257
- end
258
-
259
- it do
260
- result =Create.(params: {}, document: '{"title": ""}')
261
-
262
- if result.failure?
263
- json = result["representer.errors.class"].new(result["result.contract.default"]).to_json
264
- end
265
-
266
- json.must_equal %{{"errors":[["title","can't be blank"]]}}
267
- end
268
- end
@@ -1,154 +0,0 @@
1
- require "test_helper"
2
-
3
- class NestedRescueTest < Minitest::Spec
4
- #---
5
- # nested raise (i hope people won't use this but it works)
6
- A = Class.new(RuntimeError)
7
- Y = Class.new(RuntimeError)
8
-
9
- class NestedInsanity < Trailblazer::Operation
10
- step Rescue {
11
- step ->(options, **) { options["a"] = true }
12
- step Rescue {
13
- step ->(options, **) { options["y"] = true }
14
- success ->(options, **) { raise Y if options["raise-y"] }
15
- step ->(options, **) { options["z"] = true }
16
- }
17
- step ->(options, **) { options["b"] = true }
18
- success ->(options, **) { raise A if options["raise-a"] }
19
- step ->(options, **) { options["c"] = true }
20
- failure ->(options, **) { options["inner-err"] = true }
21
- }
22
- step ->(options, **) { options["e"] = true }, name: "nested/e"
23
- failure ->(options, **) { options["outer-err"] = true }, name: "nested/failure"
24
- end
25
-
26
- it { Trailblazer::Operation::Inspect.(NestedInsanity).must_match /\[>Rescue\(\d+\),>nested/ } # FIXME: better introspect tests for all id-generating macros.
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
- 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
- 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] >} }
30
-
31
- #-
32
- # inheritance
33
- class UbernestedInsanity < NestedInsanity
34
- end
35
-
36
- it { UbernestedInsanity.().inspect("a", "y", "z", "b", "c", "e", "inner-err", "outer-err").must_equal %{<Result:true [true, true, true, true, true, true, nil, nil] >} }
37
- it { UbernestedInsanity.( "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] >} }
38
- end
39
-
40
- class RescueTest < Minitest::Spec
41
- RecordNotFound = Class.new(RuntimeError)
42
-
43
- Song = Struct.new(:id, :title) do
44
- def self.find(id)
45
- raise if id == "RuntimeError!"
46
- id.nil? ? raise(RecordNotFound) : new(id)
47
- end
48
-
49
- def lock!
50
- true
51
- end
52
- end
53
-
54
- #:simple
55
- class Create < Trailblazer::Operation
56
- class MyContract < Reform::Form
57
- property :title
58
- end
59
-
60
- step Rescue {
61
- step Model(Song, :find)
62
- step Contract::Build( constant: MyContract )
63
- }
64
- step Contract::Validate()
65
- step Contract::Persist( method: :sync )
66
- end
67
- #:simple end
68
-
69
- it { Create.( params: {id: 1, title: "Prodigal Son"} )["contract.default"].model.inspect.must_equal %{#<struct RescueTest::Song id=1, title="Prodigal Son">} }
70
- it { Create.( params: {id: nil} ).inspect(:model).must_equal %{<Result:false [nil] >} }
71
-
72
- #-
73
- # Rescue ExceptionClass, handler: ->(*) { }
74
- class WithExceptionNameTest < Minitest::Spec
75
- #
76
- class MyContract < Reform::Form
77
- property :title
78
- end
79
- #:name
80
- class Create < Trailblazer::Operation
81
- step Rescue( RecordNotFound, KeyError, handler: :rollback! ) {
82
- step Model( Song, :find )
83
- step Contract::Build( constant: MyContract )
84
- }
85
- step Contract::Validate()
86
- step Contract::Persist( method: :sync )
87
-
88
- def rollback!(exception, options)
89
- options["x"] = exception.class
90
- end
91
- end
92
- #:name end
93
-
94
- it { Create.( params: {id: 1, title: "Prodigal Son"} )["contract.default"].model.inspect.must_equal %{#<struct RescueTest::Song id=1, title="Prodigal Son">} }
95
- it { Create.( params: {id: 1, title: "Prodigal Son"} ).inspect("x").must_equal %{<Result:true [nil] >} }
96
- it { Create.( params: {id: nil} ).inspect(:model, "x").must_equal %{<Result:false [nil, RescueTest::RecordNotFound] >} }
97
- it { assert_raises(RuntimeError) { Create.( params: {id: "RuntimeError!"} ) } }
98
- end
99
-
100
-
101
- #-
102
- # cdennl use-case
103
- class CdennlRescueAndTransactionTest < Minitest::Spec
104
- module Sequel
105
- cattr_accessor :result
106
-
107
- def self.transaction
108
- yield.tap do |res|
109
- self.result = res
110
- end
111
- end
112
- end
113
-
114
- #:example
115
- class Create < Trailblazer::Operation
116
- class MyContract < Reform::Form
117
- property :title
118
- end
119
-
120
- step Rescue( RecordNotFound, handler: :rollback! ) {
121
- step Wrap (->(*, &block) { Sequel.transaction do block.call end }) {
122
- step Model( Song, :find )
123
- step ->(options, *) { options[:model].lock! } # lock the model.
124
- step Contract::Build( constant: MyContract )
125
- step Contract::Validate( )
126
- step Contract::Persist( method: :sync )
127
- }
128
- }
129
- failure :error! # handle all kinds of errors.
130
-
131
- def rollback!(exception, options)
132
- #~ex
133
- options["x"] = exception.class
134
- #~ex end
135
- end
136
-
137
- def error!(options, *)
138
- #~ex
139
- options["err"] = true
140
- #~ex end
141
- end
142
- end
143
- #:example end
144
-
145
- it { Create.( params: {id: 1, title: "Pie"} ).inspect(:model, "x", "err").must_equal %{<Result:true [#<struct RescueTest::Song id=1, title=\"Pie\">, nil, nil] >} }
146
- # raise exceptions in Model:
147
- it { Create.( params: {id: nil} ).inspect(:model, "x").must_equal %{<Result:false [nil, RescueTest::RecordNotFound] >} }
148
- it { assert_raises(RuntimeError) { Create.( params: {id: "RuntimeError!"} ) } }
149
- it do
150
- Create.( params: {id: 1, title: "Pie"} )
151
- Sequel.result.first.must_be_kind_of Trailblazer::Operation::Railway::End::Success
152
- end
153
- end
154
- end
@@ -1,219 +0,0 @@
1
- require "test_helper"
2
-
3
- # TODO: consume End signal from wrapped
4
-
5
- class WrapTest < Minitest::Spec
6
- Song = Struct.new(:id, :title) do
7
- def self.find(id)
8
- id.nil? ? raise : new(id)
9
- end
10
- end
11
-
12
- class DirectWiringTest < Minitest::Spec
13
- class Create < Trailblazer::Operation
14
- class MyContract < Reform::Form
15
- property :title
16
- end
17
-
18
- step( Wrap( ->(options, *args, &block) {
19
- begin
20
- block.call
21
- rescue => exception
22
- options["result.model.find"] = "argh! because #{exception.class}"
23
- [ Railway.fail_fast!, options, *args ]
24
- end }) {
25
- step ->(options, **) { options["x"] = true }
26
- step Model( Song, :find )
27
- step Contract::Build( constant: MyContract )
28
- }.merge(fast_track: true))
29
- step Contract::Validate()
30
- step Contract::Persist( method: :sync )
31
- end
32
-
33
- it { Create.( params: {id: 1, title: "Prodigal Son"} ).inspect("x", :model).must_equal %{<Result:true [true, #<struct WrapTest::Song id=1, title=\"Prodigal Son\">] >} }
34
-
35
- it "goes directly from Wrap to End.fail_fast" do
36
- Create.(params: {}).inspect("x", :model, "result.model.find").must_equal %{<Result:false [true, nil, "argh! because RuntimeError"] >}
37
- end
38
- end
39
-
40
- # it allows returning legacy true/false
41
- class Create < Trailblazer::Operation
42
- class MyContract < Reform::Form
43
- property :title
44
- end
45
-
46
- step Wrap( ->(options, *, &block) {
47
- begin
48
- block.call
49
- rescue => exception
50
- options["result.model.find"] = "argh! because #{exception.class}"
51
- return false
52
- end
53
- true
54
- }) {
55
- step Model( Song, :find )
56
- step Contract::Build( constant: MyContract )
57
- }
58
- step Contract::Validate()
59
- step Contract::Persist( method: :sync )
60
- end
61
-
62
- it { Create.( params: {id: 1, title: "Prodigal Son"} )["contract.default"].model.inspect.must_equal %{#<struct WrapTest::Song id=1, title="Prodigal Son">} }
63
- it { Create.( params: {id: nil }).inspect("result.model.find").must_equal %{<Result:false [\"argh! because RuntimeError\"] >} }
64
-
65
- #-
66
- # Wrap return
67
- class WrapReturnTest < Minitest::Spec
68
- class Create < Trailblazer::Operation
69
- step Wrap( ->(options, *, &block) { options["yield?"] ? block.call : false }) {
70
- step ->(options, **) { options["x"] = true }
71
- success :noop!
72
- # ...
73
- }
74
-
75
- def noop!(options, **)
76
- end
77
- end
78
-
79
- it { Create.(params: {}).inspect("x").must_equal %{<Result:false [nil] >} }
80
- # returns falsey means deviate to left.
81
- it { Create.("yield?" => true).inspect("x").must_equal %{<Result:true [true] >} }
82
- end
83
-
84
- class WrapWithCallableTest < Minitest::Spec
85
- class MyWrapper
86
- extend Uber::Callable
87
-
88
- def self.call(options, *, &block)
89
- options["yield?"] ? yield : false
90
- end
91
- end
92
-
93
- class Create < Trailblazer::Operation
94
- step Wrap( MyWrapper ) {
95
- step ->(options, **) { options["x"] = true }
96
- # ...
97
- }
98
- end
99
-
100
- it { Create.(params: {}).inspect("x").must_equal %{<Result:false [nil] >} }
101
- # returns falsey means deviate to left.
102
- it { Create.("yield?" => true).inspect("x").must_equal %{<Result:true [true] >} }
103
- end
104
-
105
- class WrapExampleProcTest < Minitest::Spec
106
- module Sequel
107
- def self.transaction
108
- yield
109
- end
110
- end
111
-
112
- module MyNotifier
113
- def self.mail; true; end
114
- end
115
-
116
- #:sequel-transaction
117
- class Create < Trailblazer::Operation
118
- #~wrap-only
119
- class MyContract < Reform::Form
120
- property :title
121
- end
122
-
123
- #~wrap-only end
124
- step Wrap( ->(*, &block) { Sequel.transaction do block.call end } ) {
125
- step Model( Song, :new )
126
- #~wrap-only
127
- step Contract::Build( constant: MyContract )
128
- step Contract::Validate( )
129
- step Contract::Persist( method: :sync )
130
- #~wrap-only end
131
- }
132
- failure :error! # handle all kinds of errors.
133
- #~wrap-only
134
- step :notify!
135
-
136
- def error!(options)
137
- # handle errors after the wrap
138
- end
139
-
140
- def notify!(options, **)
141
- MyNotifier.mail
142
- end
143
- #~wrap-only end
144
- end
145
- #:sequel-transaction end
146
-
147
- it { Create.( params: {title: "Pie"} ).inspect(:model, "x", "err").must_equal %{<Result:true [#<struct WrapTest::Song id=nil, title=\"Pie\">, nil, nil] >} }
148
- end
149
-
150
- class WrapExampleCallableTest < Minitest::Spec
151
- module Sequel
152
- def self.transaction
153
- yield
154
- end
155
- end
156
-
157
- module MyNotifier
158
- def self.mail; true; end
159
- end
160
-
161
- #:callable-t
162
- class MyTransaction
163
- def self.call(options, *)
164
- Sequel.transaction { yield } # yield runs the nested pipe.
165
- # return value decides about left or right track!
166
- end
167
- end
168
- #:callable-t end
169
- #:sequel-transaction-callable
170
- class Create < Trailblazer::Operation
171
- #~wrap-onlyy
172
- class MyContract < Reform::Form
173
- property :title
174
- end
175
-
176
- #~wrap-onlyy end
177
- step Wrap( MyTransaction ) {
178
- step Model( Song, :new )
179
- #~wrap-onlyy
180
- step Contract::Build( constant: MyContract )
181
- step Contract::Validate( )
182
- step Contract::Persist( method: :sync )
183
- #~wrap-onlyy end
184
- }
185
- failure :error! # handle all kinds of errors.
186
- #~wrap-onlyy
187
- step :notify!
188
-
189
- def error!(options)
190
- # handle errors after the wrap
191
- end
192
-
193
- def notify!(options, **)
194
- MyNotifier.mail # send emails, because success...
195
- end
196
- #~wrap-onlyy end
197
- end
198
- #:sequel-transaction-callable end
199
-
200
- it { Create.( params: {title: "Pie"} ).inspect(:model, "x", "err").must_equal %{<Result:true [#<struct WrapTest::Song id=nil, title=\"Pie\">, nil, nil] >} }
201
- end
202
-
203
- class WrapWithMethodTest < Minitest::Spec
204
- class Create < Trailblazer::Operation
205
- step Model( Song, :new )
206
- step Wrap( ->(options, *, &block) { block.call } ) {
207
- step :check_model!
208
-
209
- }
210
-
211
- def check_model!(options, model:, **)
212
- options["x"] = model
213
- end
214
- end
215
-
216
- it { Create.(params: {}) }
217
- end
218
- end
219
-