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