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
@@ -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
-