trailblazer 2.0.7 → 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 (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,183 +0,0 @@
1
- require "test_helper"
2
-
3
- class WrapTest < Minitest::Spec
4
- Song = Struct.new(:id, :title) do
5
- def self.find(id)
6
- id.nil? ? raise : new(id)
7
- end
8
- end
9
-
10
- class Create < Trailblazer::Operation
11
- class MyContract < Reform::Form
12
- property :title
13
- end
14
-
15
- step Wrap ->(options, *, &block) {
16
- begin
17
- block.call
18
- rescue => exception
19
- options["result.model.find"] = "argh! because #{exception.class}"
20
- false
21
- end } {
22
- step Model( Song, :find )
23
- step Contract::Build( constant: MyContract )
24
- }
25
- step Contract::Validate()
26
- step Contract::Persist( method: :sync )
27
- end
28
-
29
- it { Create.( id: 1, title: "Prodigal Son" )["contract.default"].model.inspect.must_equal %{#<struct WrapTest::Song id=1, title="Prodigal Son">} }
30
- it { Create.( id: nil ).inspect("result.model.find").must_equal %{<Result:false [\"argh! because RuntimeError\"] >} }
31
-
32
- #-
33
- # Wrap return
34
- class WrapReturnTest < Minitest::Spec
35
- class Create < Trailblazer::Operation
36
- step Wrap ->(options, *, &block) { options["yield?"] ? block.call : false } {
37
- step ->(options) { options["x"] = true }
38
- success :noop!
39
- # ...
40
- }
41
-
42
- def noop!(options)
43
- end
44
- end
45
-
46
- it { Create.().inspect("x").must_equal %{<Result:false [nil] >} }
47
- # returns falsey means deviate to left.
48
- it { Create.({}, "yield?" => true).inspect("x").must_equal %{<Result:true [true] >} }
49
- end
50
-
51
- class WrapWithCallableTest < Minitest::Spec
52
- class MyWrapper
53
- extend Uber::Callable
54
-
55
- def self.call(options, *, &block)
56
- options["yield?"] ? yield : false
57
- end
58
- end
59
-
60
- class Create < Trailblazer::Operation
61
- step Wrap( MyWrapper ) {
62
- step ->(options) { options["x"] = true }
63
- # ...
64
- }
65
- end
66
-
67
- it { Create.().inspect("x").must_equal %{<Result:false [nil] >} }
68
- # returns falsey means deviate to left.
69
- it { Create.({}, "yield?" => true).inspect("x").must_equal %{<Result:true [true] >} }
70
- end
71
-
72
- #-
73
- # arguments for Wrap
74
- class Update < Trailblazer::Operation
75
- step Wrap ->(options, operation, pipe, &block) { operation["yield?"] ? block.call : false } {
76
- step ->(options) { options["x"] = true }
77
- }
78
- end
79
-
80
- it { Update.().inspect("x").must_equal %{<Result:false [nil] >} }
81
- it { Update.({}, "yield?" => true).inspect("x").must_equal %{<Result:true [true] >} }
82
-
83
- class WrapExampleProcTest < Minitest::Spec
84
- module Sequel
85
- def self.transaction
86
- yield
87
- end
88
- end
89
-
90
- module MyNotifier
91
- def self.mail; true; end
92
- end
93
-
94
- #:sequel-transaction
95
- class Create < Trailblazer::Operation
96
- #~wrap-only
97
- class MyContract < Reform::Form
98
- property :title
99
- end
100
-
101
- #~wrap-only end
102
- step Wrap ->(*, &block) { Sequel.transaction do block.call end } {
103
- step Model( Song, :new )
104
- #~wrap-only
105
- step Contract::Build( constant: MyContract )
106
- step Contract::Validate( )
107
- step Contract::Persist( method: :sync )
108
- #~wrap-only end
109
- }
110
- failure :error! # handle all kinds of errors.
111
- #~wrap-only
112
- step :notify!
113
-
114
- def error!(options)
115
- # handle errors after the wrap
116
- end
117
-
118
- def notify!(options)
119
- MyNotifier.mail
120
- end
121
- #~wrap-only end
122
- end
123
- #:sequel-transaction end
124
-
125
- it { Create.( title: "Pie" ).inspect("model", "x", "err").must_equal %{<Result:true [#<struct WrapTest::Song id=nil, title=\"Pie\">, nil, nil] >} }
126
- end
127
-
128
- class WrapExampleCallableTest < Minitest::Spec
129
- module Sequel
130
- def self.transaction
131
- yield
132
- end
133
- end
134
-
135
- module MyNotifier
136
- def self.mail; true; end
137
- end
138
-
139
- #:callable-t
140
- class MyTransaction
141
- extend Uber::Callable
142
-
143
- def self.call(options, *)
144
- Sequel.transaction { yield } # yield runs the nested pipe.
145
- # return value decides about left or right track!
146
- end
147
- end
148
- #:callable-t end
149
- #:sequel-transaction-callable
150
- class Create < Trailblazer::Operation
151
- #~wrap-onlyy
152
- class MyContract < Reform::Form
153
- property :title
154
- end
155
-
156
- #~wrap-onlyy end
157
- step Wrap( MyTransaction ) {
158
- step Model( Song, :new )
159
- #~wrap-onlyy
160
- step Contract::Build( constant: MyContract )
161
- step Contract::Validate( )
162
- step Contract::Persist( method: :sync )
163
- #~wrap-onlyy end
164
- }
165
- failure :error! # handle all kinds of errors.
166
- #~wrap-onlyy
167
- step :notify!
168
-
169
- def error!(options)
170
- # handle errors after the wrap
171
- end
172
-
173
- def notify!(options)
174
- MyNotifier.mail # send emails, because success...
175
- end
176
- #~wrap-onlyy end
177
- end
178
- #:sequel-transaction-callable end
179
-
180
- it { Create.( title: "Pie" ).inspect("model", "x", "err").must_equal %{<Result:true [#<struct WrapTest::Song id=nil, title=\"Pie\">, nil, nil] >} }
181
- end
182
- end
183
-
@@ -1,3 +0,0 @@
1
- source 'https://rubygems.org'
2
- gemspec path: "../../"
3
-
@@ -1,12 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in trailblazer.gemspec
4
- gemspec path: "../../"
5
-
6
- gem "multi_json"
7
- gem "trailblazer-operation"
8
- gem "minitest-line"
9
- gem "benchmark-ips"
10
- gem "activesupport", "~> 4.2.0"
11
- gem "reform-rails"
12
- gem "dry-validation"
@@ -1,12 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in trailblazer.gemspec
4
- gemspec
5
-
6
- gem "multi_json"
7
-
8
- gem "dry-auto_inject"
9
- gem "dry-matcher"
10
-
11
- gem "minitest-line"
12
- gem "benchmark-ips"
@@ -1,100 +0,0 @@
1
- require 'test_helper'
2
- require "trailblazer/operation/module"
3
- require "trailblazer/operation/callback"
4
- require "trailblazer/operation/contract"
5
-
6
- class OperationModuleTest < MiniTest::Spec
7
- Song = Struct.new(:name, :artist)
8
- Artist = Struct.new(:id, :full_name)
9
-
10
- class Create < Trailblazer::Operation
11
- include Trailblazer::Operation::Callback
12
- include Contract::Explicit
13
-
14
- contract do
15
- property :name
16
- property :artist, populate_if_empty: Artist do
17
- property :id
18
- end
19
- end
20
-
21
- callback do
22
- on_change :notify_me!
23
- end
24
-
25
- attr_reader :model
26
- def call(params)
27
- self["model"] = Song.new
28
-
29
- validate(params, model: self["model"]) do
30
- contract.sync
31
-
32
- dispatch!
33
- end
34
-
35
- self
36
- end
37
-
38
- def dispatched
39
- self["dispatched"] ||= []
40
- end
41
-
42
- private
43
- def notify_me!(*)
44
- dispatched << :notify_me!
45
- end
46
- end
47
-
48
-
49
- module SignedIn
50
- include Trailblazer::Operation::Module
51
-
52
- contract do
53
- property :artist, inherit: true do
54
- property :full_name
55
-
56
- puts definitions.inspect
57
- end
58
- end
59
-
60
- callback do
61
- on_change :notify_you!
62
- end
63
-
64
- def notify_you!(*)
65
- dispatched << :notify_you!
66
- end
67
- end
68
-
69
-
70
- class Update < Create
71
- callback do
72
- on_change :notify_them!
73
- end
74
-
75
- include SignedIn
76
-
77
- def notify_them!(*)
78
- dispatched << :notify_them!
79
- end
80
- end
81
-
82
-
83
- it do
84
- op = Create.({name: "Feelings", artist: {id: 1, full_name: "The Offspring"}})
85
-
86
- op["dispatched"].must_equal [:notify_me!]
87
- op["model"].name.must_equal "Feelings"
88
- op["model"].artist.id.must_equal 1
89
- op["model"].artist.full_name.must_equal nil # property not declared.
90
- end
91
-
92
- it do
93
- op = Update.({name: "Feelings", artist: {id: 1, full_name: "The Offspring"}})
94
-
95
- op["dispatched"].must_equal [:notify_me!, :notify_them!, :notify_you!]
96
- op["model"].name.must_equal "Feelings"
97
- op["model"].artist.id.must_equal 1
98
- op["model"].artist.full_name.must_equal "The Offspring" # property declared via Module.
99
- end
100
- end
@@ -1,70 +0,0 @@
1
- require 'test_helper'
2
-
3
- # callbacks are tested in Disposable::Callback::Group.
4
- class OperationCallbackTest < MiniTest::Spec
5
- Song = Struct.new(:name)
6
-
7
- #---
8
- # with contract and disposable semantics
9
- class Create < Trailblazer::Operation
10
- extend Contract::DSL
11
-
12
- contract do
13
- property :name
14
- end
15
-
16
- step Model( Song, :new )
17
- step Contract::Build()
18
- step Contract::Validate()
19
- step Callback( :default )
20
-
21
-
22
- extend Callback::DSL
23
-
24
- callback do
25
- on_change :notify_me!
26
- on_change :notify_you!
27
- end
28
-
29
-
30
- # TODO: always dispatch, pass params.
31
-
32
- def dispatched
33
- self["dispatched"] ||= []
34
- end
35
-
36
- private
37
- def notify_me!(*)
38
- dispatched << :notify_me!
39
- end
40
-
41
- def notify_you!(*)
42
- dispatched << :notify_you!
43
- end
44
- end
45
-
46
-
47
- class Update < Create
48
- # TODO: allow skipping groups.
49
- # skip_dispatch :notify_me!
50
-
51
- callback do
52
- remove! :on_change, :notify_me!
53
- end
54
- end
55
-
56
- #---
57
- #- inheritance
58
- it { Update["pipetree"].inspect.must_equal %{[>operation.new,>model.build,>contract.build,>contract.default.validate,>callback.default]} }
59
-
60
-
61
- it "invokes all callbacks" do
62
- res = Create.({"name"=>"Keep On Running"})
63
- res["dispatched"].must_equal [:notify_me!, :notify_you!]
64
- end
65
-
66
- it "does not invoke removed callbacks" do
67
- res = Update.({"name"=>"Keep On Running"})
68
- res["dispatched"].must_equal [:notify_you!]
69
- end
70
- end
@@ -1,420 +0,0 @@
1
- require "test_helper"
2
-
3
- class ContractExtractMacroTest < Minitest::Spec
4
- class Create < Trailblazer::Operation
5
- step Contract::Validate::Extract( key: "song", params_path: "x" )
6
- end
7
-
8
- it { Create["pipetree"].inspect.must_equal %{[>operation.new,>x]} }
9
- it { Create.({}).inspect("x").must_equal %{<Result:false [nil] >} }
10
- it { Create.({ "song" => Object }).inspect("x").must_equal %{<Result:true [Object] >} }
11
- end
12
-
13
-
14
-
15
- require "dry/validation"
16
-
17
- class DryValidationTest < Minitest::Spec
18
- class Create < Trailblazer::Operation
19
- extend Contract::DSL
20
-
21
- contract "params", (Dry::Validation.Schema do
22
- required(:id).filled
23
- end)
24
- # self["contract.params"] = Dry::Validation.Schema do
25
- # required(:id).filled
26
- # end
27
-
28
- step :process
29
-
30
- include Procedural::Validate
31
-
32
- def process(options)
33
- validate(options["params"], contract: self["contract.params"], path: "contract.params") { |f| puts f.inspect }
34
- end
35
- end
36
-
37
- #- result object, contract
38
- # success
39
- it { Create.(id: 1)["result.contract.params"].success?.must_equal true }
40
- it { Create.(id: 1)["result.contract.params"].errors.must_equal({}) }
41
- # failure
42
- it { Create.(id: nil)["result.contract.params"].success?.must_equal false }
43
- it { Create.(id: nil)["result.contract.params"].errors.must_equal({:id=>["must be filled"]}) }
44
-
45
- #---
46
- # with Contract::Validate, but before op even gets instantiated.
47
- class Update < Trailblazer::Operation #["contract"]
48
- extend Contract::DSL
49
-
50
- contract "params", (Dry::Validation.Schema do
51
- required(:id).filled
52
- end)
53
-
54
- step ->(options) { options["contract.params"].(options["params"]).success? }, before: "operation.new"
55
- end
56
-
57
- it { Update.( id: 1 ).success?.must_equal true }
58
- it { Update.( ).success?.must_equal false }
59
- end
60
-
61
- class ContractTest < Minitest::Spec
62
- Song = Struct.new(:title)
63
- # # generic form for testing.
64
- # class Form
65
- # def initialize(model, options={})
66
- # @inspect = "#{self.class}: #{model} #{options.inspect}"
67
- # end
68
-
69
- # def validate
70
- # @inspect
71
- # end
72
- # end
73
-
74
- #---
75
- # contract do..end (without constant)
76
- #- explicit validate call
77
- describe "contract do .. end" do
78
- class Index < Trailblazer::Operation
79
- extend Contract::DSL
80
-
81
- contract do
82
- property :title
83
- end
84
-
85
- success ->(options) { options["model"] = Song.new }
86
- # step Model( Song, :new )
87
- step Contract::Build()
88
- step :process
89
-
90
- include Procedural::Validate
91
- # TODO: get model automatically in validate!
92
-
93
- def process(options)
94
- # validate(params, model: Song.new) { |f| self["x"] = f.to_nested_hash }
95
- validate(options["params"]) { |f| self["x"] = f.to_nested_hash }
96
- end
97
- end
98
-
99
- # will create a Reform::Form for us.
100
- it { Index.(title: "Falling Down")["x"].must_equal({"title"=>"Falling Down"}) }
101
- end
102
-
103
- # # TODO: in all step tests.
104
- # describe "dependency injection" do
105
- # class Delete < Trailblazer::Operation
106
- # include Contract::Step
107
- # end
108
-
109
- # class Follow < Trailblazer::Operation
110
- # include Contract::Step
111
- # end
112
-
113
- # # inject contract instance via constructor.
114
- # it { Delete.({}, "contract" => "contract/instance")["contract"].must_equal "contract/instance" }
115
- # # inject contract class.
116
- # it { Follow.({}, "contract.default.class" => Form)["contract"].class.must_equal Form }
117
- # end
118
-
119
-
120
- # # contract(model, [admin: true]).validate
121
- # class Create < Trailblazer::Operation
122
- # include Test::ReturnProcess
123
- # include Contract::Explicit
124
-
125
- # def call(options:false)
126
- # return contract(model: Object, options: { admin: true }).validate if options
127
- # contract(model: Object).validate
128
- # end
129
- # end
130
-
131
- # # inject class, pass in model and options when constructing.
132
- # # contract(model)
133
- # it { Create.({}, "contract.default.class" => Form).must_equal "ContractTest::Form: Object {}" }
134
- # # contract(model, options)
135
- # it { Create.({ options: true }, "contract.default.class" => Form).must_equal "ContractTest::Form: Object {:admin=>true}" }
136
-
137
- # # ::contract Form
138
- # # contract(model).validate
139
- # class Update < Trailblazer::Operation
140
- # include Test::ReturnProcess
141
- # include Contract::Explicit
142
-
143
- # = Form
144
-
145
- # def call(*)
146
- # contract.validate
147
- # end
148
-
149
- # include Model( :Builder
150
- # ) def model!(*)
151
- # Object
152
- # end
153
- # end
154
-
155
- # # use the class contract.
156
- # it { Update.().must_equal "ContractTest::Form: Object {}" }
157
- # # injected contract overrides class.
158
- # it { Update.({}, "contract.default.class" => Injected = Class.new(Form)).must_equal "ContractTest::Injected: Object {}" }
159
-
160
- # # passing Constant into #contract
161
- # # contract(Object.new, { title: "Bad Feeling" }, Contract)
162
- # class Operation < Trailblazer::Operation
163
- # include Contract::Explicit
164
-
165
- # class Contract < Reform::Form
166
- # property :title, virtual: true
167
- # end
168
-
169
- # def process(params)
170
- # contract(model: Object.new, options: { title: "Bad Feeling" }, contract_class: Contract)
171
-
172
- # validate(params)
173
- # end
174
- # end
175
-
176
- # # allow using #contract to inject model, options and class.
177
- # it do
178
- # contract = Operation.(id: 1)["contract"]
179
- # contract.title.must_equal "Bad Feeling"
180
- # contract.must_be_instance_of Operation::Contract
181
- # end
182
-
183
- # # allow using #contract before #validate.
184
- # class Upsert < Trailblazer::Operation
185
- # include Contract::Explicit
186
-
187
- # contract do
188
- # property :id
189
- # property :title
190
- # property :length
191
- # end
192
-
193
- # def process(params)
194
- # self["model"] = Struct.new(:id, :title, :length).new
195
- # contract.id = 1
196
- # validate(params) { contract.length = 3 }
197
- # end
198
- # end
199
-
200
- # it do
201
- # contract = Upsert.(title: "Beethoven")["contract"]
202
- # contract.id.must_equal 1
203
- # contract.title.must_equal "Beethoven"
204
- # contract.length.must_equal 3
205
- # end
206
- # end
207
-
208
- #---
209
- #- validate
210
- class ValidateTest < Minitest::Spec
211
- class Create < Trailblazer::Operation
212
- extend Contract::DSL
213
- contract do
214
- property :title
215
- validates :title, presence: true
216
- end
217
-
218
-
219
- include Procedural::Validate
220
- def process(options)
221
- if validate(options["params"])
222
- self["x"] = "works!"
223
- true
224
- else
225
- self["x"] = "try again"
226
- false
227
- end
228
- end
229
-
230
- step Model( Song, :new ) # FIXME.
231
- step Contract::Build()
232
- step :process
233
- end
234
-
235
- # validate returns the #validate result
236
- it do
237
- Create.(title: nil)["x"].must_equal "try again"
238
- Create.(title: nil).success?.must_equal false
239
- end
240
- it { Create.(title: "SVG")["x"].must_equal "works!" }
241
- it { Create.(title: "SVG").success?.must_equal true }
242
-
243
- # result object from validation.
244
- #- result object, contract
245
- it { Create.(title: 1)["result.contract.default"].success?.must_equal true }
246
- it { Create.(title: 1)["result.contract.default"].errors.messages.must_equal({}) } # FIXME: change API with Fran.
247
- it { Create.(title: nil)["result.contract.default"].success?.must_equal false }
248
- it { Create.(title: nil)["result.contract.default"].errors.messages.must_equal({:title=>["can't be blank"]}) } # FIXME: change API with Fran.
249
- # #---
250
- # # validate with block returns result.
251
- # class Update < Trailblazer::Operation
252
- # include Contract::Explicit
253
- # contract Form
254
-
255
- # def process(params)
256
- # self["x"] = validate(params) { }
257
- # end
258
- # end
259
-
260
- # it { Update.(false)["x"].must_equal false}
261
- # it { Update.(true)["x"]. must_equal true}
262
-
263
- #---
264
- # Contract::Validate[]
265
- class Update < Trailblazer::Operation
266
- extend Contract::DSL
267
- contract do
268
- property :title
269
- validates :title, presence: true
270
- end
271
-
272
- step Model( Song, :new ) # FIXME.
273
- step Contract::Build()
274
- step Contract::Validate() # generic validate call for you.
275
-
276
- # include Procedural::Validate
277
- ->(*) { validate(options["params"][:song]) } # <-- TODO
278
- end
279
-
280
- # success
281
- it do
282
- result = Update.(title: "SVG")
283
- result.success?.must_equal true
284
- result["result.contract.default"].success?.must_equal true
285
- result["result.contract.default"].errors.messages.must_equal({})
286
- end
287
-
288
- # failure
289
- it do
290
- result = Update.(title: nil)
291
- result.success?.must_equal false
292
- result["result.contract.default"].success?.must_equal false
293
- result["result.contract.default"].errors.messages.must_equal({:title=>["can't be blank"]})
294
- end
295
-
296
- #---
297
- # Contract::Validate[key: :song]
298
- class Upsert < Trailblazer::Operation
299
- extend Contract::DSL
300
- contract do
301
- property :title
302
- validates :title, presence: true
303
- end
304
-
305
- step Model( Song, :new ) # FIXME.
306
- step Contract::Build()
307
- step Contract::Validate( key: :song) # generic validate call for you.
308
- # ->(*) { validate(options["params"][:song]) } # <-- TODO
309
- step Contract::Persist( method: :sync )
310
- end
311
-
312
- # success
313
- it { Upsert.(song: { title: "SVG" }).success?.must_equal true }
314
- # failure
315
- it { Upsert.(song: { title: nil }).success?.must_equal false }
316
- # key not found
317
- it { Upsert.().success?.must_equal false }
318
-
319
- #---
320
- # contract.default.params gets set (TODO: change in 2.1)
321
- it { Upsert.(song: { title: "SVG" })["params"].must_equal({:song=>{:title=>"SVG"}}) }
322
- it { Upsert.(song: { title: "SVG" })["contract.default.params"].must_equal({:title=>"SVG"}) }
323
-
324
- #---
325
- #- inheritance
326
- class New < Upsert
327
- end
328
-
329
- it { New["pipetree"].inspect.must_equal %{[>operation.new,>model.build,>contract.build,>contract.default.validate,>persist.save]} }
330
-
331
- #- overwriting Validate
332
- class NewHit < Upsert
333
- step Contract::Validate( key: :hit ), override: true
334
- end
335
-
336
- it { NewHit["pipetree"].inspect.must_equal %{[>operation.new,>model.build,>contract.build,>contract.default.validate,>persist.save]} }
337
- it { NewHit.(:hit => { title: "Hooray For Me" }).inspect("model").must_equal %{<Result:true [#<struct ContractTest::Song title=\"Hooray For Me\">] >} }
338
- end
339
-
340
- # #---
341
- # # allow using #contract to inject model and arguments.
342
- # class OperationContractWithOptionsTest < Minitest::Spec
343
- # # contract(model, title: "Bad Feeling")
344
- # class Operation < Trailblazer::Operation
345
- # include Contract::Explicit
346
- # contract do
347
- # property :id
348
- # property :title, virtual: true
349
- # end
350
-
351
- # def process(params)
352
- # model = Struct.new(:id).new
353
-
354
- # contract(model: model, options: { title: "Bad Feeling" })
355
-
356
- # validate(params)
357
- # end
358
- # end
359
-
360
- # it do
361
- # op = Operation.(id: 1)
362
- # op["contract"].id.must_equal 1
363
- # op["contract"].title.must_equal "Bad Feeling"
364
- # end
365
-
366
- # # contract({ song: song, album: album }, title: "Medicine Balls")
367
- # class CompositionOperation < Trailblazer::Operation
368
- # include Contract::Explicit
369
- # contract do
370
- # include Reform::Form::Composition
371
- # property :song_id, on: :song
372
- # property :album_name, on: :album
373
- # property :title, virtual: true
374
- # end
375
-
376
- # def process(params)
377
- # song = Struct.new(:song_id).new(1)
378
- # album = Struct.new(:album_name).new("Forever Malcom Young")
379
-
380
- # contract(model: { song: song, album: album }, options: { title: "Medicine Balls" })
381
-
382
- # validate(params)
383
- # end
384
- # end
385
-
386
- # it do
387
- # contract = CompositionOperation.({})["contract"]
388
- # contract.song_id.must_equal 1
389
- # contract.album_name.must_equal "Forever Malcom Young"
390
- # contract.title.must_equal "Medicine Balls"
391
- # end
392
-
393
- # # validate(params, { song: song, album: album }, title: "Medicine Balls")
394
- # class CompositionValidateOperation < Trailblazer::Operation
395
- # include Contract::Explicit
396
- # contract do
397
- # include Reform::Form::Composition
398
- # property :song_id, on: :song
399
- # property :album_name, on: :album
400
- # property :title, virtual: true
401
- # end
402
-
403
- # def process(params)
404
- # song = Struct.new(:song_id).new(1)
405
- # album = Struct.new(:album_name).new("Forever Malcom Young")
406
-
407
- # validate(params, model: { song: song, album: album }, options: { title: "Medicine Balls" })
408
- # end
409
- # end
410
-
411
- # it do
412
- # contract = CompositionValidateOperation.({})["contract"]
413
- # contract.song_id.must_equal 1
414
- # contract.album_name.must_equal "Forever Malcom Young"
415
- # contract.title.must_equal "Medicine Balls"
416
- # end
417
- end
418
-
419
- # TODO: full stack test with validate, process, save, etc.
420
- # with model!