trailblazer 1.1.1 → 2.1.0

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 (60) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.rubocop-https---raw-githubusercontent-com-trailblazer-meta-master-rubocop-yml +101 -0
  4. data/.rubocop.yml +20 -0
  5. data/.rubocop_todo.yml +556 -0
  6. data/.travis.yml +6 -7
  7. data/CHANGES.md +224 -0
  8. data/COMM-LICENSE +62 -0
  9. data/CONTRIBUTING.md +179 -0
  10. data/Gemfile +0 -10
  11. data/LICENSE +9 -0
  12. data/README.md +68 -189
  13. data/Rakefile +3 -3
  14. data/lib/trailblazer/version.rb +3 -1
  15. data/lib/trailblazer.rb +3 -6
  16. data/test/test_helper.rb +32 -3
  17. data/trailblazer.gemspec +11 -15
  18. metadata +28 -132
  19. data/LICENSE.txt +0 -22
  20. data/TODO.md +0 -11
  21. data/doc/Trb-The-Stack.png +0 -0
  22. data/doc/trb.jpg +0 -0
  23. data/gemfiles/Gemfile.rails.lock +0 -130
  24. data/gemfiles/Gemfile.reform-2.0 +0 -6
  25. data/gemfiles/Gemfile.reform-2.1 +0 -7
  26. data/lib/trailblazer/autoloading.rb +0 -15
  27. data/lib/trailblazer/endpoint.rb +0 -31
  28. data/lib/trailblazer/operation/builder.rb +0 -26
  29. data/lib/trailblazer/operation/callback.rb +0 -53
  30. data/lib/trailblazer/operation/collection.rb +0 -6
  31. data/lib/trailblazer/operation/controller.rb +0 -72
  32. data/lib/trailblazer/operation/dispatch.rb +0 -3
  33. data/lib/trailblazer/operation/model/dsl.rb +0 -29
  34. data/lib/trailblazer/operation/model/external.rb +0 -34
  35. data/lib/trailblazer/operation/model.rb +0 -50
  36. data/lib/trailblazer/operation/module.rb +0 -29
  37. data/lib/trailblazer/operation/policy/guard.rb +0 -35
  38. data/lib/trailblazer/operation/policy.rb +0 -85
  39. data/lib/trailblazer/operation/representer.rb +0 -98
  40. data/lib/trailblazer/operation/resolver.rb +0 -30
  41. data/lib/trailblazer/operation/uploaded_file.rb +0 -77
  42. data/lib/trailblazer/operation.rb +0 -175
  43. data/test/callback_test.rb +0 -104
  44. data/test/collection_test.rb +0 -57
  45. data/test/model_test.rb +0 -148
  46. data/test/module_test.rb +0 -93
  47. data/test/operation/builder_test.rb +0 -41
  48. data/test/operation/contract_test.rb +0 -30
  49. data/test/operation/controller_test.rb +0 -111
  50. data/test/operation/dsl/callback_test.rb +0 -118
  51. data/test/operation/dsl/contract_test.rb +0 -104
  52. data/test/operation/dsl/representer_test.rb +0 -142
  53. data/test/operation/external_model_test.rb +0 -71
  54. data/test/operation/guard_test.rb +0 -152
  55. data/test/operation/policy_test.rb +0 -97
  56. data/test/operation/reject_test.rb +0 -34
  57. data/test/operation/resolver_test.rb +0 -83
  58. data/test/operation_test.rb +0 -275
  59. data/test/representer_test.rb +0 -238
  60. data/test/rollback_test.rb +0 -47
@@ -1,275 +0,0 @@
1
- require "test_helper"
2
-
3
- module Inspect
4
- def inspect
5
- "<#{self.class.to_s.split("::").last} @model=#{@model}>"
6
- end
7
- alias_method :to_s, :inspect
8
- end
9
-
10
- class OperationSetupParamsTest < MiniTest::Spec
11
- class OperationSetupParam < Trailblazer::Operation
12
- def process(params)
13
- @model = params
14
- end
15
-
16
- def setup_params!(params)
17
- params.merge!(garrett: "Rocks!")
18
- end
19
-
20
- include Inspect
21
- end
22
-
23
- # allows you changing params in #setup_params!.
24
- it { OperationSetupParam.run({valid: true}).to_s.must_equal "[true, <OperationSetupParam @model={:valid=>true, :garrett=>\"Rocks!\"}>]" }
25
- end
26
-
27
- class OperationParamsTest < MiniTest::Spec
28
- class Operation < Trailblazer::Operation
29
- def process(params)
30
- @model = "#{params} and #{@params==params}"
31
- end
32
-
33
- def params!(params)
34
- { params: params }
35
- end
36
- end
37
-
38
- # allows you returning new params in #params!.
39
- it { Operation.({valid: true}).model.to_s.must_equal "{:params=>{:valid=>true}} and true" }
40
- end
41
-
42
- # Operation#model.
43
- class OperationModelTest < MiniTest::Spec
44
- class Operation < Trailblazer::Operation
45
- def process(params)
46
- end
47
-
48
- def model!(params)
49
- params
50
- end
51
- end
52
-
53
- # #model.
54
- it { Operation.(Object).model.must_equal Object }
55
- end
56
-
57
- # Operation#model=.
58
- class OperationModelWriterTest < MiniTest::Spec
59
- class Operation < Trailblazer::Operation
60
- def process(params)
61
- self.model = "#{params}"
62
- end
63
- end
64
-
65
- it { Operation.("I can set @model via a private setter").model.to_s.must_equal "I can set @model via a private setter" }
66
- end
67
-
68
- class OperationRunTest < MiniTest::Spec
69
- class Operation < Trailblazer::Operation
70
- # allow providing your own contract.
71
- self.contract_class = class Contract
72
- def initialize(*)
73
- end
74
- def validate(params)
75
- return true if params == "yes, true"
76
- false
77
- end
78
-
79
- def errors
80
- Struct.new(:to_s).new("Op just calls #to_s on Errors!")
81
- end
82
- self
83
- end
84
-
85
- def process(params)
86
- model = Object
87
- validate(params, model)
88
- end
89
-
90
- include Inspect
91
- end
92
-
93
- # contract is inferred from self::contract_class.
94
- # ::run returns result set when run without block.
95
- it { Operation.run("not true").to_s.must_equal %{[false, <Operation @model=>]} }
96
- it { Operation.run("yes, true").to_s.must_equal %{[true, <Operation @model=>]} }
97
-
98
- # ::call raises exception when invalid.
99
- it do
100
- exception = assert_raises(Trailblazer::Operation::InvalidContract) { Operation.("not true") }
101
- exception.message.must_equal "Op just calls #to_s on Errors!"
102
- end
103
-
104
- # return operation when ::call
105
- it { Operation.("yes, true").to_s.must_equal %{<Operation @model=>} }
106
- # #[] is alias for .()
107
- it { Operation["yes, true"].to_s.must_equal %{<Operation @model=>} }
108
-
109
-
110
- # ::run with block returns operation.
111
- # valid executes block.
112
- it "block" do
113
- outcome = nil
114
- res = Operation.run("yes, true") do
115
- outcome = "true"
116
- end
117
-
118
- outcome.must_equal "true" # block was executed.
119
- res.to_s.must_equal %{<Operation @model=>}
120
- end
121
-
122
- # invalid doesn't execute block.
123
- it "block, invalid" do
124
- outcome = nil
125
- res = Operation.run("no, not true, false") do
126
- outcome = "true"
127
- end
128
-
129
- outcome.must_equal nil # block was _not_ executed.
130
- res.to_s.must_equal %{<Operation @model=>}
131
- end
132
-
133
- # block yields operation
134
- it do
135
- outcome = nil
136
- res = Operation.run("yes, true") do |op|
137
- outcome = op
138
- end
139
-
140
- outcome.to_s.must_equal %{<Operation @model=>} # block was executed.
141
- res.to_s.must_equal %{<Operation @model=>}
142
- end
143
-
144
- # # Operation#contract returns @contract
145
- it { Operation.("yes, true").contract.class.to_s.must_equal "OperationRunTest::Operation::Contract" }
146
-
147
-
148
-
149
-
150
- describe "::present" do
151
- class NoContractOp < Trailblazer::Operation
152
- self.contract_class = nil
153
-
154
- def model!(*)
155
- Object
156
- end
157
- end
158
-
159
- # the operation and model are available, but no contract.
160
- it { NoContractOp.present({}).model.must_equal Object }
161
- # no contract is built.
162
- it { assert_raises(NoMethodError) { NoContractOp.present({}).contract } }
163
- it { assert_raises(NoMethodError) { NoContractOp.run({}) } }
164
- end
165
- end
166
-
167
-
168
- class OperationTest < MiniTest::Spec
169
- # test #invalid!
170
- class OperationWithoutValidateCall < Trailblazer::Operation
171
- def process(params)
172
- params || invalid!(params)
173
- end
174
-
175
- include Inspect
176
- end
177
-
178
- # ::run
179
- it { OperationWithoutValidateCall.run(true).to_s.must_equal %{[true, <OperationWithoutValidateCall @model=>]} }
180
- # invalid.
181
- it { OperationWithoutValidateCall.run(false).to_s.must_equal %{[false, <OperationWithoutValidateCall @model=>]} }
182
-
183
-
184
- # #validate yields contract when valid
185
- class OperationWithValidateBlock < Trailblazer::Operation
186
- self.contract_class = class Contract
187
- def initialize(*)
188
- end
189
-
190
- def validate(params)
191
- params
192
- end
193
- self
194
- end
195
-
196
- def process(params)
197
- validate(params, Object.new) do |c|
198
- @secret_contract = c.class
199
- end
200
- end
201
-
202
- attr_reader :secret_contract
203
- end
204
-
205
- it { OperationWithValidateBlock.run(false).last.secret_contract.must_equal nil }
206
- it { OperationWithValidateBlock.(true).secret_contract.must_equal OperationWithValidateBlock::Contract }
207
-
208
-
209
- # test validate wit if/else
210
- class OperationWithValidateAndIf < Trailblazer::Operation
211
- self.contract_class = class Contract
212
- def initialize(*)
213
- end
214
-
215
- def validate(params)
216
- params
217
- end
218
- self
219
- end
220
-
221
- def process(params)
222
- if validate(params, Object.new)
223
- @secret_contract = contract.class
224
- else
225
- @secret_contract = "so wrong!"
226
- end
227
- end
228
-
229
- attr_reader :secret_contract
230
- end
231
-
232
- it { OperationWithValidateAndIf.run(false).last.secret_contract.must_equal "so wrong!" }
233
- it { OperationWithValidateAndIf.(true).secret_contract.must_equal OperationWithValidateAndIf::Contract }
234
-
235
-
236
-
237
- # ::present only runs #setup! which runs #model!.
238
- class ContractOnlyOperation < Trailblazer::Operation
239
- self.contract_class = class Contract
240
- def initialize(model)
241
- @_model = model
242
- end
243
- attr_reader :_model
244
- self
245
- end
246
-
247
- def model!(params)
248
- Object
249
- end
250
-
251
- def process(params)
252
- raise "This is not run!"
253
- end
254
- end
255
-
256
- it { ContractOnlyOperation.present({}).contract._model.must_equal Object }
257
- end
258
-
259
-
260
- class OperationErrorsTest < MiniTest::Spec
261
- class Operation < Trailblazer::Operation
262
- contract do
263
- property :title, validates: {presence: true}
264
- end
265
-
266
- def process(params)
267
- validate(params, OpenStruct.new) {}
268
- end
269
- end
270
-
271
- it do
272
- res, op = Operation.run({})
273
- op.errors.to_s.must_equal "{:title=>[\"can't be blank\"]}"
274
- end
275
- end
@@ -1,238 +0,0 @@
1
- require "test_helper"
2
-
3
- require "representable/json"
4
-
5
- class RepresenterTest < MiniTest::Spec
6
- Album = Struct.new(:title, :artist)
7
- Artist = Struct.new(:name)
8
-
9
- class Create < Trailblazer::Operation
10
- require "trailblazer/operation/representer"
11
- include Representer
12
-
13
- contract do
14
- property :title
15
- validates :title, presence: true
16
- property :artist, populate_if_empty: Artist do
17
- property :name
18
- validates :name, presence: true
19
- end
20
- end
21
-
22
- def process(params)
23
- @model = Album.new # NO artist!!!
24
- validate(params[:album], @model)
25
- end
26
- end
27
-
28
-
29
- # Infers representer from contract, no customization.
30
- class Show < Create
31
- def process(params)
32
- @model = Album.new("After The War", Artist.new("Gary Moore"))
33
- @contract = @model
34
- end
35
- end
36
-
37
-
38
- # Infers representer, adds hypermedia.
39
- require "roar/json/hal"
40
- class HypermediaCreate < Create
41
- representer do
42
- include Roar::JSON::HAL
43
-
44
- link(:self) { "//album/#{represented.title}" }
45
- end
46
- end
47
-
48
- class HypermediaShow < HypermediaCreate
49
- def process(params)
50
- @model = Album.new("After The War", Artist.new("Gary Moore"))
51
- @contract = @model
52
- end
53
- end
54
-
55
-
56
- # rendering
57
- # generic contract -> representer
58
- it do
59
- res, op = Show.run({})
60
- op.to_json.must_equal %{{"title":"After The War","artist":{"name":"Gary Moore"}}}
61
- end
62
-
63
- # contract -> representer with hypermedia
64
- it do
65
- res, op = HypermediaShow.run({})
66
- op.to_json.must_equal %{{"title":"After The War","artist":{"name":"Gary Moore"},"_links":{"self":{"href":"//album/After The War"}}}}
67
- end
68
-
69
-
70
- # parsing
71
- it do
72
- res, op = Create.run(album: %{{"title":"Run For Cover","artist":{"name":"Gary Moore"}}})
73
- op.contract.title.must_equal "Run For Cover"
74
- op.contract.artist.name.must_equal "Gary Moore"
75
- end
76
-
77
- it do
78
- res, op = HypermediaCreate.run(album: %{{"title":"After The War","artist":{"name":"Gary Moore"},"_links":{"self":{"href":"//album/After The War"}}}})
79
- op.contract.title.must_equal "After The War"
80
- op.contract.artist.name.must_equal "Gary Moore"
81
- end
82
-
83
-
84
-
85
-
86
-
87
- # explicit representer set with ::representer_class=.
88
- require "roar/decorator"
89
- class JsonApiCreate < Trailblazer::Operation
90
- include Representer
91
-
92
- contract do # we still need contract as the representer writes to the contract twin.
93
- property :title
94
- end
95
-
96
- class AlbumRepresenter < Roar::Decorator
97
- include Roar::JSON
98
- property :title
99
- end
100
- self.representer_class = AlbumRepresenter
101
-
102
- def process(params)
103
- @model = Album.new # NO artist!!!
104
- validate(params[:album], @model)
105
- end
106
- end
107
-
108
- class JsonApiShow < JsonApiCreate
109
- def process(params)
110
- @model = Album.new("After The War", Artist.new("Gary Moore"))
111
- @contract = @model
112
- end
113
- end
114
-
115
- # render.
116
- it do
117
- res, op = JsonApiShow.run({})
118
- op.to_json.must_equal %{{"title":"After The War"}}
119
- end
120
-
121
- # parse.
122
- it do
123
- res, op = JsonApiCreate.run(album: %{{"title":"Run For Cover"}})
124
- op.contract.title.must_equal "Run For Cover"
125
- end
126
- end
127
-
128
- class InternalRepresenterAPITest < MiniTest::Spec
129
- Song = Struct.new(:id)
130
-
131
- describe "#represented" do
132
- class Show < Trailblazer::Operation
133
- include Representer, Model
134
- model Song, :create
135
-
136
- representer do
137
- property :class
138
- end
139
- end
140
-
141
- it "uses #model as represented, per default" do
142
- Show.present({}).to_json.must_equal '{"class":"InternalRepresenterAPITest::Song"}'
143
- end
144
-
145
- class ShowContract < Show
146
- def represented
147
- contract
148
- end
149
- end
150
-
151
- it "can be overriden to use the contract" do
152
- ShowContract.present({}).to_json.must_equal %{{"class":"#{ShowContract.contract_class}"}}
153
- end
154
- end
155
-
156
- describe "#to_json" do
157
- class OptionsShow < Trailblazer::Operation
158
- include Representer
159
-
160
- representer do
161
- property :class
162
- property :id
163
- end
164
-
165
- def to_json(*)
166
- super(@params)
167
- end
168
-
169
- def model!(params)
170
- Song.new(1)
171
- end
172
- end
173
-
174
- it "allows to pass options to #to_json" do
175
- OptionsShow.present(include: [:id]).to_json.must_equal '{"id":1}'
176
- end
177
- end
178
- end
179
-
180
- class DifferentParseAndRenderingRepresenterTest < MiniTest::Spec
181
- Album = Struct.new(:title)
182
-
183
- # rendering
184
- class Create < Trailblazer::Operation
185
- extend Representer::DSL
186
- include Representer::Rendering # no Deserializer::Hash here or anything.
187
-
188
- contract do
189
- property :title
190
- end
191
-
192
- representer do
193
- property :title, as: :Title
194
- end
195
-
196
- def process(params)
197
- @model = Album.new
198
- validate(params) do
199
- contract.sync
200
- end
201
- end
202
- end
203
-
204
- it do
205
- Create.(title: "The Kids").to_json.must_equal %{{"Title":"The Kids"}}
206
- end
207
-
208
- # parsing
209
- class Update < Trailblazer::Operation
210
- extend Representer::DSL
211
- include Representer::Deserializer::Hash # no Rendering.
212
-
213
- representer do
214
- property :title, as: :Title
215
- end
216
-
217
- contract do
218
- property :title
219
- end
220
-
221
-
222
- def process(params)
223
- @model = Album.new
224
-
225
- validate(params) do
226
- contract.sync
227
- end
228
- end
229
-
230
- def to_json(*)
231
- %{{"title": "#{model.title}"}}
232
- end
233
- end
234
-
235
- it do
236
- Update.("Title" => "The Kids").to_json.must_equal %{{"title": "The Kids"}}
237
- end
238
- end
@@ -1,47 +0,0 @@
1
- require "test_helper"
2
-
3
- # TODO: rollback for composed operations: this is basically implemented already as every operation knows how to rollback.
4
- # however, this has to be run for composed operations.
5
- # we can also add Transaction and Lock for real uniqueness validators, etc.
6
- #
7
- # i am keen to try integrating https://github.com/collectiveidea/interactor organizers!
8
- module Trailblazer::Operation::Rollback
9
- def run
10
- begin
11
- super
12
- rescue
13
- rollback!(@params, $!)
14
- [false, self]
15
- end
16
- end
17
- end
18
-
19
- class RollbackTest < MiniTest::Spec
20
- class ExceptionalOperation < Trailblazer::Operation
21
- include Rollback
22
-
23
- def process(params)
24
- @_params = params
25
- raise # something happens.
26
- end
27
-
28
- attr_reader :_params, :_rollback_args
29
-
30
- def rollback!(params, exception)
31
- @_rollback_args = [params, exception]
32
- end
33
- end
34
-
35
- module Comparable
36
- def ==(other)
37
- self.class == other.class
38
- end
39
- end
40
-
41
- it do
42
- op = ExceptionalOperation.("amazing")
43
- op._params.must_equal "amazing"
44
-
45
- op._rollback_args.must_equal ["amazing", RuntimeError.new.extend(Comparable)]
46
- end
47
- end