trailblazer 2.0.7 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) 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 -10
  7. data/CHANGES.md +83 -1
  8. data/COMM-LICENSE +46 -75
  9. data/CONTRIBUTING.md +179 -0
  10. data/Gemfile +0 -27
  11. data/{LICENSE.txt → LICENSE} +4 -4
  12. data/README.md +39 -138
  13. data/Rakefile +2 -19
  14. data/lib/trailblazer.rb +3 -17
  15. data/lib/trailblazer/version.rb +3 -1
  16. data/test/test_helper.rb +12 -3
  17. data/trailblazer.gemspec +10 -14
  18. metadata +22 -147
  19. data/doc/Trb-The-Stack.png +0 -0
  20. data/doc/operation-2017.png +0 -0
  21. data/doc/trb.jpg +0 -0
  22. data/lib/trailblazer/dsl.rb +0 -47
  23. data/lib/trailblazer/operation/auto_inject.rb +0 -47
  24. data/lib/trailblazer/operation/callback.rb +0 -35
  25. data/lib/trailblazer/operation/contract.rb +0 -46
  26. data/lib/trailblazer/operation/guard.rb +0 -18
  27. data/lib/trailblazer/operation/model.rb +0 -60
  28. data/lib/trailblazer/operation/module.rb +0 -29
  29. data/lib/trailblazer/operation/nested.rb +0 -113
  30. data/lib/trailblazer/operation/persist.rb +0 -10
  31. data/lib/trailblazer/operation/policy.rb +0 -35
  32. data/lib/trailblazer/operation/procedural/contract.rb +0 -15
  33. data/lib/trailblazer/operation/procedural/validate.rb +0 -22
  34. data/lib/trailblazer/operation/pundit.rb +0 -38
  35. data/lib/trailblazer/operation/representer.rb +0 -31
  36. data/lib/trailblazer/operation/rescue.rb +0 -21
  37. data/lib/trailblazer/operation/test.rb +0 -17
  38. data/lib/trailblazer/operation/validate.rb +0 -68
  39. data/lib/trailblazer/operation/wrap.rb +0 -25
  40. data/test/docs/auto_inject_test.rb +0 -30
  41. data/test/docs/contract_test.rb +0 -525
  42. data/test/docs/dry_test.rb +0 -31
  43. data/test/docs/fast_test.rb +0 -164
  44. data/test/docs/guard_test.rb +0 -169
  45. data/test/docs/macro_test.rb +0 -36
  46. data/test/docs/model_test.rb +0 -75
  47. data/test/docs/nested_test.rb +0 -334
  48. data/test/docs/operation_test.rb +0 -408
  49. data/test/docs/policy_test.rb +0 -2
  50. data/test/docs/pundit_test.rb +0 -133
  51. data/test/docs/representer_test.rb +0 -268
  52. data/test/docs/rescue_test.rb +0 -154
  53. data/test/docs/wrap_test.rb +0 -183
  54. data/test/gemfiles/Gemfile.ruby-1.9 +0 -3
  55. data/test/gemfiles/Gemfile.ruby-2.0 +0 -12
  56. data/test/gemfiles/Gemfile.ruby-2.3 +0 -12
  57. data/test/module_test.rb +0 -100
  58. data/test/operation/callback_test.rb +0 -70
  59. data/test/operation/contract_test.rb +0 -420
  60. data/test/operation/dsl/callback_test.rb +0 -106
  61. data/test/operation/dsl/contract_test.rb +0 -294
  62. data/test/operation/dsl/representer_test.rb +0 -169
  63. data/test/operation/model_test.rb +0 -60
  64. data/test/operation/params_test.rb +0 -36
  65. data/test/operation/persist_test.rb +0 -44
  66. data/test/operation/pipedream_test.rb +0 -59
  67. data/test/operation/pipetree_test.rb +0 -104
  68. data/test/operation/present_test.rb +0 -24
  69. data/test/operation/pundit_test.rb +0 -104
  70. data/test/operation/representer_test.rb +0 -254
  71. data/test/operation/resolver_test.rb +0 -47
  72. data/test/operation_test.rb +0 -143
@@ -1,106 +0,0 @@
1
- require "test_helper"
2
-
3
- class DslCallbackTest < MiniTest::Spec
4
- module SongProcess
5
- def _invocations
6
- self["x"] ||= []
7
- end
8
-
9
- def self.included(includer)
10
- includer.extend Trailblazer::Operation::Contract::DSL
11
- includer.contract do
12
- property :title
13
- end
14
- includer.| Trailblazer::Operation::Model[OpenStruct, :new]
15
- includer.| Trailblazer::Operation::Contract::Build[includer["contract.default.class"]]
16
- includer.| Trailblazer::Operation::Contract::Validate[]
17
- includer.| Trailblazer::Operation::Callback[:default]
18
- end
19
- end
20
-
21
- describe "inheritance across operations" do
22
- class Operation < Trailblazer::Operation
23
- extend Callback::DSL
24
- include SongProcess
25
-
26
- callback do
27
- on_change :default!
28
- end
29
-
30
- class Admin < self
31
- callback do
32
- on_change :admin_default!
33
- end
34
-
35
- callback(:after_save) { on_change :after_save! }
36
-
37
- def admin_default!(*); _invocations << :admin_default!; end
38
- def after_save!(*); _invocations << :after_save!; end
39
-
40
- step Trailblazer::Operation::Callback[:after_save]
41
- end
42
-
43
- def default!(*); _invocations << :default!; end
44
- end
45
-
46
- it { Operation.({"title"=> "Love-less"})["x"].must_equal([:default!]) }
47
- it { Operation::Admin.({"title"=> "Love-less"})["x"].must_equal([:default!, :admin_default!, :after_save!]) }
48
- end
49
-
50
- describe "Op.callback :after_save, AfterSaveCallback" do
51
- class AfterSaveCallback < Disposable::Callback::Group
52
- on_change :after_save!
53
-
54
- def after_save!(twin, options)
55
- options[:operation]._invocations << :after_save!
56
- end
57
- end
58
-
59
- class OpWithExternalCallback < Trailblazer::Operation
60
- include SongProcess
61
- extend Callback::DSL
62
- callback :after_save, AfterSaveCallback
63
-
64
- step Callback[:after_save]
65
- end
66
-
67
- it { OpWithExternalCallback.("title"=>"Thunder Rising").must_equal([:after_save!]) }
68
- end
69
-
70
- describe "Op.callback :after_save, AfterSaveCallback do .. end" do
71
- class DefaultCallback < Disposable::Callback::Group
72
- on_change :default!
73
-
74
- def default!(twin, options)
75
- options[:operation]._invocations << :default!
76
- end
77
- end
78
-
79
- class OpUsingCallback < Trailblazer::Operation
80
- extend Callback::DSL
81
- include SongProcess
82
- callback :default, DefaultCallback
83
- end
84
-
85
- class OpExtendingCallback < Trailblazer::Operation
86
- extend Callback::DSL
87
- include SongProcess
88
- callback :default, DefaultCallback do
89
- on_change :after_save!
90
-
91
- def default!(twin, options)
92
- options[:operation]._invocations << :extended_default!
93
- end
94
-
95
- def after_save!(twin, options)
96
- options[:operation]._invocations << :after_save!
97
- end
98
- end
99
- end
100
-
101
- # this operation copies DefaultCallback and shouldn't run #after_save!.
102
- it { OpUsingCallback.(title: "Thunder Rising")["x"].must_equal([:default!]) }
103
- # this operation copies DefaultCallback, extends it and runs #after_save!.
104
- it { OpExtendingCallback.(title: "Thunder Rising")["x"].must_equal([:extended_default!, :after_save!]) }
105
- end
106
- end
@@ -1,294 +0,0 @@
1
- require "test_helper"
2
- require "trailblazer/operation/contract"
3
-
4
- # contract Constant # new
5
- # contract Constant, inherit: true # extend existing
6
- # contract do end # extend existing || new
7
- # contract Constant do .. end # new, extend new
8
-
9
- class DslContractTest < MiniTest::Spec
10
- module Call
11
- def call(params)
12
- validate(params, model: model=OpenStruct.new) { contract.sync }
13
- model
14
- end
15
-
16
- def self.included(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
- # includer.> ->(op, *) { op["x"] = [] }
22
- end
23
- end
24
-
25
- # ---
26
- # Operation::["contract.default.class"]
27
- # Operation::["contract.default.class"]=
28
- class Create < Trailblazer::Operation
29
- include Contract
30
- self["contract.default.class"] = String
31
- end
32
-
33
- # reader method.
34
- # no subclassing.
35
- it { Create["contract.default.class"].must_equal String }
36
-
37
- class CreateOrFind < Create
38
- end
39
-
40
- # no inheritance with setter.
41
- it { CreateOrFind["contract.default.class"].must_equal nil }
42
-
43
- # ---
44
- # Op::contract Constant
45
- class Update < Trailblazer::Operation
46
- class IdContract < Reform::Form
47
- property :id
48
- end
49
-
50
- extend Contract::DSL
51
- contract IdContract
52
-
53
- include Call
54
- end
55
-
56
- # UT: subclasses contract.
57
- it { Update["contract.default.class"].superclass.must_equal Update::IdContract }
58
- # IT: only knows `id`.
59
- it { Update.(id: 1, title: "Coaster")["model"].inspect.must_equal %{#<OpenStruct id=1>} }
60
-
61
- # Op::contract with inheritance
62
- # no ::contract call.
63
- class Upgrade < Update
64
- end
65
-
66
- # UT: subclasses contract but doesn't share with parent.
67
- it { Upgrade["contract.default.class"].superclass.must_equal Update::IdContract }
68
- it { Upgrade["contract.default.class"].wont_equal Update["contract.default.class"] }
69
- # IT: only knows `id`.
70
- it { Upgrade.(id: 1, title: "Coaster")["model"].inspect.must_equal %{#<OpenStruct id=1>} }
71
-
72
- # ::contract B overrides old A contract.
73
- # this makes sure when calling contract(Constant), the old class gets wiped and is replaced with the new constant.
74
- class Upsert < Update
75
- class TitleContract < Reform::Form
76
- property :title
77
- end
78
-
79
- contract TitleContract
80
- end
81
-
82
- # UT: subclasses contract.
83
- it { Upsert["contract.default.class"].superclass.must_equal Upsert::TitleContract }
84
- # IT: only knows `title`.
85
- it { Upsert.(id: 1, title: "Coaster")["model"].inspect.must_equal %{#<OpenStruct title="Coaster">} }
86
-
87
- # ::contract B do ..end overrides and extends new.
88
- # using a constant will wipe out the existing class.
89
- class Upside < Update
90
- contract Upsert::TitleContract do
91
- property :id
92
- end
93
- end
94
-
95
- # UT: subclasses contract.
96
- it { Upside["contract.default.class"].superclass.must_equal Upsert::TitleContract }
97
- # IT: only knows `title`.
98
- it { Upside.(id: 1, title: "Coaster")["model"].inspect.must_equal %{#<OpenStruct title="Coaster", id=1>} }
99
-
100
-
101
-
102
- #---
103
- # contract do .. end
104
- # (with block)
105
- class Delete < Trailblazer::Operation
106
- include Call
107
- extend Contract::DSL
108
- contract do
109
- property :title
110
- end
111
- end
112
-
113
- # UT: contract path is "contract.default.class"
114
- it { Delete["contract.default.class"].definitions.keys.must_equal ["title"] }
115
- # IT: knows `title`.
116
- it { Delete.(id: 1, title: "Coaster")["model"].inspect.must_equal %{#<OpenStruct title=\"Coaster\">} }
117
-
118
- class Wipe < Trailblazer::Operation
119
- extend Contract::DSL
120
-
121
- self["x"] = contract do end
122
- end
123
- # UT: ::contract returns form class
124
- it { Wipe["x"].superclass.must_equal Reform::Form }
125
-
126
- # subsequent calls merge.
127
- class Remove < Trailblazer::Operation
128
- extend Contract::DSL
129
- include Call
130
-
131
- contract do
132
- property :title
133
- end
134
-
135
- contract do
136
- property :id
137
- end
138
- end
139
-
140
- # IT: knows `title` and `id`, since contracts get merged.
141
- it { Remove.(id: 1, title: "Coaster")["model"].inspect.must_equal %{#<OpenStruct title=\"Coaster\", id=1>} }
142
-
143
-
144
-
145
-
146
-
147
-
148
- # Operation::["contract.default.class"]
149
- # Operation::["contract.default.class"]=
150
- describe %{Operation::["contract.default.class"]} do
151
-
152
- class Update2 < Trailblazer::Operation
153
- self["contract.default.class"] = String
154
- end
155
-
156
- it { Update2["contract.default.class"].must_equal String }
157
- end
158
-
159
- describe "inheritance across operations" do
160
- # inheritance
161
- class Operation < Trailblazer::Operation
162
- extend Contract::DSL
163
- contract do
164
- property :title
165
- property :band
166
- end
167
-
168
- class JSON < self
169
- contract do # inherit Contract
170
- property :genre, validates: {presence: true}
171
- property :band, virtual: true
172
- end
173
- end
174
-
175
- class XML < self
176
- end
177
- end
178
-
179
- # inherits subclassed Contract.
180
- it { Operation["contract.default.class"].wont_equal Operation::JSON["contract.default.class"] }
181
- it { Operation::XML["contract.default.class"].superclass.must_equal Reform::Form }
182
-
183
- it do
184
- form = Operation["contract.default.class"].new(OpenStruct.new)
185
- form.validate({})#.must_equal true
186
- form.errors.to_s.must_equal "{}"
187
-
188
- form = Operation::JSON["contract.default.class"].new(OpenStruct.new)
189
- form.validate({})#.must_equal true
190
- form.errors.to_s.must_equal "{:genre=>[\"can't be blank\"]}"
191
- end
192
-
193
- # allows overriding options
194
- it do
195
- form = Operation::JSON["contract.default.class"].new(song = OpenStruct.new)
196
- form.validate({genre: "Punkrock", band: "Osker"}).must_equal true
197
- form.sync
198
-
199
- song.genre.must_equal "Punkrock"
200
- song.band.must_equal nil
201
- end
202
- end
203
-
204
- describe "Op.contract CommentForm" do
205
- class SongForm < Reform::Form
206
- property :songTitle, validates: {presence: true}
207
- end
208
-
209
- class OpWithExternalContract < Trailblazer::Operation
210
- extend Contract::DSL
211
- contract SongForm
212
- include Call
213
- end
214
-
215
- it { OpWithExternalContract.("songTitle"=> "Monsterparty")["contract.default"].songTitle.must_equal "Monsterparty" }
216
- end
217
-
218
- describe "Op.contract CommentForm do .. end" do
219
- class DifferentSongForm < Reform::Form
220
- property :songTitle, validates: {presence: true}
221
- end
222
-
223
- class OpNotExtendingContract < Trailblazer::Operation
224
- extend Contract::DSL
225
- contract DifferentSongForm
226
- include Call
227
- end
228
-
229
- class OpExtendingContract < Trailblazer::Operation
230
- extend Contract::DSL
231
- contract DifferentSongForm do
232
- property :genre
233
- end
234
- include Call
235
- end
236
-
237
- # this operation copies DifferentSongForm and shouldn't have `genre`.
238
- it do
239
- contract = OpNotExtendingContract.("songTitle"=>"Monsterparty", "genre"=>"Punk")["contract.default"]
240
- contract.songTitle.must_equal "Monsterparty"
241
- assert_raises(NoMethodError) { contract.genre }
242
- end
243
-
244
- # this operation copies DifferentSongForm and extends it with the property `genre`.
245
- it do
246
- contract = OpExtendingContract.("songTitle"=>"Monsterparty", "genre"=>"Punk")["contract.default"]
247
- contract.songTitle.must_equal "Monsterparty"
248
- contract.genre.must_equal "Punk"
249
- end
250
-
251
- # of course, the original contract wasn't modified, either.
252
- it do
253
- assert_raises(NoMethodError) { DifferentSongForm.new(OpenStruct.new).genre }
254
- end
255
- end
256
-
257
- describe "Op.contract :name, Form" do
258
- class Follow < Trailblazer::Operation
259
- ParamsForm = Class.new
260
-
261
- extend Contract::DSL
262
- contract :params, ParamsForm
263
- end
264
-
265
- it { Follow["contract.params.class"].superclass.must_equal Follow::ParamsForm }
266
- end
267
-
268
- describe "Op.contract :name do..end" do
269
- class Unfollow < Trailblazer::Operation
270
- extend Contract::DSL
271
- contract :params do
272
- property :title
273
- end
274
- end
275
-
276
- it { Unfollow["contract.params.class"].superclass.must_equal Reform::Form }
277
- end
278
-
279
- # multiple ::contract calls.
280
- describe "multiple ::contract calls" do
281
- class Star < Trailblazer::Operation
282
- extend Contract::DSL
283
- contract do
284
- property :title
285
- end
286
-
287
- contract do
288
- property :id
289
- end
290
- end
291
-
292
- it { Star["contract.default.class"].definitions.keys.must_equal ["title", "id"] }
293
- end
294
- end
@@ -1,169 +0,0 @@
1
- require "test_helper"
2
- require "representable/json"
3
- require "trailblazer/operation/representer"
4
- require "trailblazer/operation/contract"
5
-
6
- class DslRepresenterTest < MiniTest::Spec
7
- module SongProcess
8
- def process(params)
9
- self["model"] = OpenStruct.new(params)
10
- end
11
-
12
- def represented
13
- self["model"]
14
- end
15
- end
16
-
17
- describe "inheritance across operations" do
18
- class Operation < Trailblazer::Operation
19
- include Representer
20
- include SongProcess
21
-
22
- representer do
23
- property :title
24
- end
25
-
26
- class JSON < self
27
- representer do
28
- property :band
29
- end
30
- end
31
- end
32
-
33
- it { Operation.(title: "Nothing To Lose", band: "Gary Moore").to_json.must_equal %{{"title":"Nothing To Lose"}} }
34
- # only the subclass must have the `band` field, even though it's set in the original operation.
35
- it { Operation::JSON.(title: "Nothing To Lose", band: "Gary Moore").to_json.must_equal %{{"title":"Nothing To Lose","band":"Gary Moore"}} }
36
- end
37
-
38
- describe "Op.representer CommentRepresenter" do
39
- class SongRepresenter < Representable::Decorator
40
- include Representable::JSON
41
- property :songTitle
42
- end
43
-
44
- class OpWithExternalRepresenter < Trailblazer::Operation
45
- include Representer
46
- include SongProcess
47
- representer SongRepresenter
48
- end
49
-
50
- it { OpWithExternalRepresenter.("songTitle"=>"Listen To Your Heartbeat").to_json.must_equal %{{"songTitle":"Listen To Your Heartbeat"}} }
51
- end
52
-
53
- # name for representer
54
- describe "1) Op.representer :parse, Representer" do
55
- class Op1 < Trailblazer::Operation
56
- include Representer
57
- representer :parse, String
58
- end
59
-
60
- it { Op1["representer.parse.class"].superclass.must_equal String }
61
- it { Op1.({})["representer.parse.class"].superclass.must_equal String }
62
- end
63
-
64
- # name for default representer
65
- describe "2) Op.representer Representer" do
66
- class Op2 < Trailblazer::Operation
67
- include Representer
68
- representer String
69
- def call(*); self; end
70
- end
71
-
72
- it { Op2["representer.default.class"].superclass.must_equal String }
73
- it { Op2.({})["representer.default.class"].superclass.must_equal String }
74
- it { Op2.({}, "representer.default.class" => Integer)["representer.default.class"].must_equal Integer }
75
- end
76
-
77
-
78
-
79
- describe "Op.representer CommentRepresenter do .. end" do
80
- class HitRepresenter < Representable::Decorator
81
- include Representable::JSON
82
- property :title
83
- end
84
-
85
- class OpNotExtendingRepresenter < Trailblazer::Operation
86
- include Representer
87
- include SongProcess
88
- representer HitRepresenter
89
- end
90
-
91
- class OpExtendingRepresenter < Trailblazer::Operation
92
- include Representer
93
- include SongProcess
94
- representer HitRepresenter do
95
- property :genre
96
- end
97
- end
98
-
99
- # this operation copies HitRepresenter and shouldn't have `genre`.
100
- it do
101
- OpNotExtendingRepresenter.("title"=>"Monsterparty", "genre"=>"Punk").to_json.must_equal %{{"title":"Monsterparty"}}
102
- end
103
-
104
- # # this operation copies HitRepresenter and extends it with the property `genre`.
105
- it do
106
- OpExtendingRepresenter.("title"=>"Monsterparty", "genre"=>"Punk").to_json.must_equal %{{"title":"Monsterparty","genre":"Punk"}}
107
- end
108
-
109
- # # of course, the original representer wasn't modified, either.
110
- it do
111
- HitRepresenter.new(OpenStruct.new(title: "Monsterparty", genre: "Punk")).to_json.must_equal %{{"title":"Monsterparty"}}
112
- end
113
- end
114
-
115
- describe "Op.representer (inferring)" do
116
- class ContractOperation < Trailblazer::Operation
117
- include Representer
118
- include Representer::InferFromContract
119
- include SongProcess
120
- include Contract::Explicit
121
-
122
- contract do
123
- property :songTitle
124
- end
125
- end
126
-
127
- class ContractOperation2 < Trailblazer::Operation
128
- include Representer
129
- include SongProcess
130
- include Contract::Explicit
131
- include Representer::InferFromContract
132
- contract ContractOperation["contract.default.class"]
133
-
134
- representer do
135
- property :genre
136
- end
137
- end
138
-
139
- it { ContractOperation.("songTitle"=>"Monsterparty", "genre"=>"Punk").to_json.must_equal %{{"songTitle":"Monsterparty"}} }
140
- # this representer block extends the inferred from contract.
141
- it { ContractOperation2.("songTitle"=>"Monsterparty", "genre"=>"Punk").to_json.must_equal %{{"songTitle":"Monsterparty","genre":"Punk"}} }
142
- end
143
-
144
- describe "Op.representer_class" do
145
- class PlayRepresenter < Representable::Decorator
146
- include Representable::JSON
147
- property :title
148
- end
149
-
150
- class OpSettingRepresenter < Trailblazer::Operation
151
- include Representer
152
- include SongProcess
153
- self["representer.default.class"] = PlayRepresenter
154
- end
155
-
156
- class OpExtendRepresenter < Trailblazer::Operation
157
- include Representer
158
- include SongProcess
159
- self["representer.default.class"] = PlayRepresenter
160
- representer do
161
- property :genre
162
- end
163
- end
164
-
165
- # both operations produce the same as the representer is shared, not copied.
166
- it { skip; OpSettingRepresenter.("title"=>"Monsterparty", "genre"=>"Punk").to_json.must_equal %{{"title":"Monsterparty","genre":"Punk"}} }
167
- it { OpExtendRepresenter.("title"=>"Monsterparty", "genre"=>"Punk").to_json.must_equal %{{"title":"Monsterparty","genre":"Punk"}} }
168
- end
169
- end