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,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
@@ -1,54 +0,0 @@
1
- require "test_helper"
2
-
3
- class ModelTest < Minitest::Spec
4
- Song = Struct.new(:id) do
5
- def self.find(id); new(id) end
6
- def self.find_by(id:nil); id.nil? ? nil : new(id) end
7
- end
8
-
9
- #---
10
- # use Model semantics, no customizations.
11
- class Create < Trailblazer::Operation
12
- step Model( Song, :new )
13
- end
14
-
15
- # :new new.
16
- it { Create.(params: {})[:model].inspect.must_equal %{#<struct ModelTest::Song id=nil>} }
17
-
18
- class Update < Create
19
- step Model( Song, :find ), override: true
20
- end
21
-
22
- #---
23
- #- inheritance
24
-
25
- # :find it
26
- it { Update.(params: { id: 1 })[:model].inspect.must_equal %{#<struct ModelTest::Song id=1>} }
27
-
28
- # inherited inspect is ok
29
- it { Trailblazer::Operation::Inspect.(Update).must_equal %{[>model.build]} }
30
-
31
- #---
32
- # :find_by, exceptionless.
33
- class Find < Trailblazer::Operation
34
- step Model Song, :find_by
35
- step :process
36
-
37
- def process(options, **); options["x"] = true end
38
- end
39
-
40
- # can't find model.
41
- #- result object, model
42
- it do
43
- Find.(params: {id: nil})["result.model"].failure?.must_equal true
44
- Find.(params: {id: nil})["x"].must_be_nil
45
- Find.(params: {id: nil}).failure?.must_equal true
46
- end
47
-
48
- #- result object, model
49
- it do
50
- Find.(params: {id: 9})["result.model"].success?.must_equal true
51
- Find.(params: {id: 9})["x"].must_equal true
52
- Find.(params: {id: 9})[:model].inspect.must_equal %{#<struct ModelTest::Song id=9>}
53
- end
54
- end
@@ -1,51 +0,0 @@
1
- require "test_helper"
2
-
3
- class PersistTest < Minitest::Spec
4
- Song = Struct.new(:title, :saved) do
5
- def save; title=="Fail!" ? false : self.saved = true; end
6
- end
7
-
8
- class Create < Trailblazer::Operation
9
- class Form < Reform::Form
10
- property :title
11
- end
12
-
13
- class Fail1
14
- def self.call(options, **); options["1. fail"] = "Validate" end
15
- end
16
-
17
- class Fail2
18
- def self.call(options, **); options["2. fail"] = "Persist" end
19
- end
20
-
21
- step Model( Song, :new )
22
- step Contract::Build( constant: Form )
23
- step Contract::Validate()
24
- fail Fail1
25
- step Contract::Persist()
26
- fail Fail2
27
- end
28
-
29
- it { Create.(params: {title: "In Recital"})[:model].title.must_equal "In Recital" }
30
- it { Create.(params: {title: "In Recital"})[:model].saved.must_equal true }
31
- # failure
32
- it do
33
- result = Create.(params: {title: "Fail!"})
34
- result[:model].saved.must_be_nil
35
- result[:model].title.must_equal "Fail!"
36
- result["2. fail"].must_equal "Persist"
37
- result.success?.must_equal false
38
- end
39
-
40
- #---
41
- #- inheritance
42
- class Update < Create
43
- end
44
-
45
- it { Operation::Inspect.( Update ).must_equal %{[>model.build,>contract.build,>contract.default.validate,<<PersistTest::Create::Fail1,>persist.save,<<PersistTest::Create::Fail2]} }
46
-
47
- #---
48
- it do
49
- skip "show how save! could be applied and how we could rescue and deviate to left track"
50
- end
51
- end
@@ -1,106 +0,0 @@
1
- require "test_helper"
2
- require "trailblazer/operation/policy"
3
-
4
- class PolicyTest < Minitest::Spec
5
- Song = Struct.new(:id) do
6
- def self.find(id); new(id) end
7
- end
8
-
9
- class Auth
10
- def initialize(user, model); @user, @model = user, model end
11
- def only_user?; @user == Module && @model.nil? end
12
- def user_object?; @user == Object end
13
- def user_and_model?; @user == Module && @model.class == Song end
14
- def inspect; "<Auth: user:#{@user.inspect}, model:#{@model.inspect}>" end
15
- end
16
-
17
- #---
18
- # Instance-level: Only policy, no model
19
- class Create < Trailblazer::Operation
20
- step Policy::Pundit( Auth, :only_user? )
21
- step :process
22
-
23
- def process(options, **)
24
- options["process"] = true
25
- end
26
- end
27
-
28
- # successful.
29
- it do
30
- result = Create.(params: {}, current_user: Module)
31
- result["process"].must_equal true
32
- #- result object, policy
33
- result["result.policy.default"].success?.must_equal true
34
- result["result.policy.default"]["message"].must_be_nil
35
- # result[:valid].must_be_nil
36
- result["policy.default"].inspect.must_equal %{<Auth: user:Module, model:nil>}
37
- end
38
- # breach.
39
- it do
40
- result = Create.(params: {}, current_user: nil)
41
- result["process"].must_be_nil
42
- #- result object, policy
43
- result["result.policy.default"].success?.must_equal false
44
- result["result.policy.default"]["message"].must_equal "Breach"
45
- end
46
- # inject different policy.Condition it { Create.(params: {}, current_user: Object, "policy.default.eval" => Trailblazer::Operation::Policy::Pundit::Condition.new(Auth, :user_object?))["process"].must_equal true }
47
- it { Create.(params: {}, current_user: Module, "policy.default.eval" => Trailblazer::Operation::Policy::Pundit::Condition.new(Auth, :user_object?))["process"].must_be_nil }
48
-
49
-
50
- #---
51
- # inheritance, adding Model
52
- class Show < Create
53
- step Model( Song, :new ), before: "policy.default.eval"
54
- end
55
-
56
- it { Trailblazer::Operation::Inspect.(Show).must_equal %{[>model.build,>policy.default.eval,>process]} }
57
-
58
- # invalid because user AND model.
59
- it do
60
- result = Show.(params: {}, current_user: Module)
61
- result["process"].must_be_nil
62
- result[:model].inspect.must_equal %{#<struct PolicyTest::Song id=nil>}
63
- # result["policy"].inspect.must_equal %{#<struct PolicyTest::Song id=nil>}
64
- end
65
-
66
- # valid because new policy.
67
- it do
68
- # puts Show["pipetree"].inspect
69
- result = Show.(params: {}, current_user: Module, "policy.default.eval" => Trailblazer::Operation::Policy::Pundit::Condition.new(Auth, :user_and_model?))
70
- result["process"].must_equal true
71
- result[:model].inspect.must_equal %{#<struct PolicyTest::Song id=nil>}
72
- result["policy.default"].inspect.must_equal %{<Auth: user:Module, model:#<struct PolicyTest::Song id=nil>>}
73
- end
74
-
75
- ##--
76
- # TOOOODOOO: Policy and Model before Build ("External" or almost Resolver)
77
- class Edit < Trailblazer::Operation
78
- step Model Song, :find
79
- step Policy::Pundit( Auth, :user_and_model? )
80
- step :process
81
-
82
- def process(options, **)
83
- options["process"] = true
84
- end
85
- end
86
-
87
- # successful.
88
- it do
89
- result = Edit.(params: { id: 1 }, current_user: Module)
90
- result["process"].must_equal true
91
- result[:model].inspect.must_equal %{#<struct PolicyTest::Song id=1>}
92
- result["result.policy.default"].success?.must_equal true
93
- result["result.policy.default"]["message"].must_be_nil
94
- # result[:valid].must_be_nil
95
- result["policy.default"].inspect.must_equal %{<Auth: user:Module, model:#<struct PolicyTest::Song id=1>>}
96
- end
97
-
98
- # breach.
99
- it do
100
- result = Edit.(params: { id: 4 }, current_user: nil)
101
- result[:model].inspect.must_equal %{#<struct PolicyTest::Song id=4>}
102
- result["process"].must_be_nil
103
- result["result.policy.default"].success?.must_equal false
104
- result["result.policy.default"]["message"].must_equal "Breach"
105
- end
106
- end
@@ -1,254 +0,0 @@
1
- # require "test_helper"
2
- # require "representable/json"
3
-
4
- # class RepresenterTest < MiniTest::Spec
5
- # Album = Struct.new(:title, :artist)
6
- # Artist = Struct.new(:name)
7
-
8
- # class Create < Trailblazer::Operation
9
- # include Contract
10
- # include Representer
11
- # include Representer::InferFromContract
12
- # attr_reader :model # FIXME: all we want is #model.
13
-
14
- # contract do
15
- # property :title
16
- # validates :title, presence: true
17
- # property :artist, populate_if_empty: Artist do
18
- # property :name
19
- # validates :name, presence: true
20
- # end
21
- # end
22
-
23
- # def call(params)
24
- # self["model"] = Album.new # NO artist!!!
25
- # validate(params[:album], model: self["model"])
26
- # self
27
- # end
28
- # end
29
-
30
-
31
- # # Infers representer from contract, no customization.
32
- # class Show < Create
33
- # def call(params)
34
- # self["model"] = Album.new("After The War", Artist.new("Gary Moore"))
35
- # self
36
- # end
37
- # end
38
-
39
-
40
- # # Infers representer, adds hypermedia.
41
- # require "roar/json/hal"
42
- # class HypermediaCreate < Create
43
- # representer do
44
- # include Roar::JSON::HAL
45
-
46
- # link(:self) { "//album/#{represented.title}" }
47
- # end
48
- # end
49
-
50
- # class HypermediaShow < HypermediaCreate
51
- # def call(params)
52
- # self["model"] = Album.new("After The War", Artist.new("Gary Moore"))
53
- # self
54
- # end
55
- # end
56
-
57
-
58
- # # rendering
59
- # # generic contract -> representer
60
- # it { Show.().to_json.must_equal %{{"title":"After The War","artist":{"name":"Gary Moore"}}} }
61
-
62
- # # contract -> representer with hypermedia
63
- # it do
64
- # HypermediaShow.().to_json.must_equal %{{"title":"After The War","artist":{"name":"Gary Moore"},"_links":{"self":{"href":"//album/After The War"}}}}
65
- # end
66
-
67
-
68
- # # parsing
69
- # it do
70
- # op = Create.(album: %{{"title":"Run For Cover","artist":{"name":"Gary Moore"}}})
71
- # op.contract.title.must_equal "Run For Cover"
72
- # op.contract.artist.name.must_equal "Gary Moore"
73
- # end
74
-
75
- # it do
76
- # op = HypermediaCreate.(album: %{{"title":"After The War","artist":{"name":"Gary Moore"},"_links":{"self":{"href":"//album/After The War"}}}})
77
- # op.contract.title.must_equal "After The War"
78
- # op.contract.artist.name.must_equal "Gary Moore"
79
- # end
80
-
81
-
82
-
83
-
84
-
85
- # # explicit representer set with ::representer_class=.
86
- # require "roar/decorator"
87
- # class JsonApiCreate < Trailblazer::Operation
88
- # include Contract
89
- # include Representer
90
- # attr_reader :model
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
-
101
- # # FIXME: this won't inherit, of course.
102
- # # self["representer.class"] = AlbumRepresenter
103
- # representer AlbumRepresenter
104
-
105
- # def call(params)
106
- # self["model"] = Album.new # NO artist!!!
107
- # validate(params[:album], model: self["model"])
108
- # self
109
- # end
110
- # end
111
-
112
- # class JsonApiShow < JsonApiCreate
113
- # def call(params)
114
- # self["model"] = Album.new("After The War", Artist.new("Gary Moore"))
115
- # self
116
- # end
117
- # end
118
-
119
- # # render.
120
- # it do
121
- # JsonApiShow.().to_json.must_equal %{{"title":"After The War"}}
122
- # end
123
-
124
- # # parse.
125
- # it do
126
- # JsonApiCreate.(album: %{{"title":"Run For Cover"}}).contract.title.must_equal "Run For Cover"
127
- # end
128
- # end
129
-
130
- # class InternalRepresenterAPITest < MiniTest::Spec
131
- # Song = Struct.new(:id)
132
-
133
- # describe "#represented" do
134
- # class Show < Trailblazer::Operation
135
- # include Contract
136
- # include Representer, Model
137
- # model Song, :create
138
-
139
- # representer do
140
- # property :class
141
- # end
142
-
143
- # def call(*)
144
- # self
145
- # end
146
-
147
- # def model # FIXME.
148
- # self["model"]
149
- # end
150
- # end
151
-
152
- # it "uses #model as represented, per default" do
153
- # Show.({}).to_json.must_equal '{"class":"InternalRepresenterAPITest::Song"}'
154
- # end
155
-
156
- # class ShowContract < Show
157
- # def represented
158
- # "Object"
159
- # end
160
- # end
161
-
162
- # it "can be overriden to use the contract" do
163
- # ShowContract.({}).to_json.must_equal %{{"class":"String"}}
164
- # end
165
- # end
166
-
167
- # describe "#to_json" do
168
- # class OptionsShow < Trailblazer::Operation
169
- # include Representer
170
-
171
- # representer do
172
- # property :class
173
- # property :id
174
- # end
175
-
176
- # def to_json(*)
177
- # super(self["params"])
178
- # end
179
-
180
- # include Model::Builder
181
- # def model!(*)
182
- # Song.new(1)
183
- # end
184
- # end
185
-
186
- # it "allows to pass options to #to_json" do
187
- # OptionsShow.(include: [:id]).to_json.must_equal %{{"id":1}}
188
- # end
189
- # end
190
- # end
191
-
192
- # class DifferentParseAndRenderingRepresenterTest < MiniTest::Spec
193
- # Album = Struct.new(:title)
194
-
195
- # # rendering
196
- # class Create < Trailblazer::Operation
197
- # include Contract
198
- # extend Representer::DSL
199
- # include Representer::Rendering # no Deserializer::Hash here or anything.
200
-
201
- # contract do
202
- # property :title
203
- # end
204
-
205
- # representer do
206
- # property :title, as: :Title
207
- # end
208
-
209
- # def call(params)
210
- # self["model"] = Album.new
211
- # validate(params) do
212
- # contract.sync
213
- # end
214
- # self
215
- # end
216
- # end
217
-
218
- # it do
219
- # Create.(title: "The Kids").to_json.must_equal %{{"Title":"The Kids"}}
220
- # end
221
-
222
- # # parsing
223
- # class Update < Trailblazer::Operation
224
- # include Contract
225
- # extend Representer::DSL
226
- # include Representer::Deserializer::Hash # no Rendering.
227
-
228
- # representer do
229
- # property :title, as: :Title
230
- # end
231
-
232
- # contract do
233
- # property :title
234
- # end
235
-
236
- # def call(params)
237
- # self["model"] = Album.new
238
-
239
- # validate(params) do
240
- # contract.sync
241
- # end
242
-
243
- # self
244
- # end
245
-
246
- # def to_json(*)
247
- # %{{"title": "#{self["model"].title}"}}
248
- # end
249
- # end
250
-
251
- # it do
252
- # Update.("Title" => "The Kids").to_json.must_equal %{{"title": "The Kids"}}
253
- # end
254
- # end