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.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.rubocop-https---raw-githubusercontent-com-trailblazer-meta-master-rubocop-yml +101 -0
- data/.rubocop.yml +20 -0
- data/.rubocop_todo.yml +556 -0
- data/.travis.yml +6 -10
- data/CHANGES.md +83 -1
- data/COMM-LICENSE +46 -75
- data/CONTRIBUTING.md +179 -0
- data/Gemfile +0 -27
- data/{LICENSE.txt → LICENSE} +4 -4
- data/README.md +39 -138
- data/Rakefile +2 -19
- data/lib/trailblazer.rb +3 -17
- data/lib/trailblazer/version.rb +3 -1
- data/test/test_helper.rb +12 -3
- data/trailblazer.gemspec +10 -14
- metadata +22 -147
- data/doc/Trb-The-Stack.png +0 -0
- data/doc/operation-2017.png +0 -0
- data/doc/trb.jpg +0 -0
- data/lib/trailblazer/dsl.rb +0 -47
- data/lib/trailblazer/operation/auto_inject.rb +0 -47
- data/lib/trailblazer/operation/callback.rb +0 -35
- data/lib/trailblazer/operation/contract.rb +0 -46
- data/lib/trailblazer/operation/guard.rb +0 -18
- data/lib/trailblazer/operation/model.rb +0 -60
- data/lib/trailblazer/operation/module.rb +0 -29
- data/lib/trailblazer/operation/nested.rb +0 -113
- data/lib/trailblazer/operation/persist.rb +0 -10
- data/lib/trailblazer/operation/policy.rb +0 -35
- data/lib/trailblazer/operation/procedural/contract.rb +0 -15
- data/lib/trailblazer/operation/procedural/validate.rb +0 -22
- data/lib/trailblazer/operation/pundit.rb +0 -38
- data/lib/trailblazer/operation/representer.rb +0 -31
- data/lib/trailblazer/operation/rescue.rb +0 -21
- data/lib/trailblazer/operation/test.rb +0 -17
- data/lib/trailblazer/operation/validate.rb +0 -68
- data/lib/trailblazer/operation/wrap.rb +0 -25
- data/test/docs/auto_inject_test.rb +0 -30
- data/test/docs/contract_test.rb +0 -525
- data/test/docs/dry_test.rb +0 -31
- data/test/docs/fast_test.rb +0 -164
- data/test/docs/guard_test.rb +0 -169
- data/test/docs/macro_test.rb +0 -36
- data/test/docs/model_test.rb +0 -75
- data/test/docs/nested_test.rb +0 -334
- data/test/docs/operation_test.rb +0 -408
- data/test/docs/policy_test.rb +0 -2
- data/test/docs/pundit_test.rb +0 -133
- data/test/docs/representer_test.rb +0 -268
- data/test/docs/rescue_test.rb +0 -154
- data/test/docs/wrap_test.rb +0 -183
- data/test/gemfiles/Gemfile.ruby-1.9 +0 -3
- data/test/gemfiles/Gemfile.ruby-2.0 +0 -12
- data/test/gemfiles/Gemfile.ruby-2.3 +0 -12
- data/test/module_test.rb +0 -100
- data/test/operation/callback_test.rb +0 -70
- data/test/operation/contract_test.rb +0 -420
- data/test/operation/dsl/callback_test.rb +0 -106
- data/test/operation/dsl/contract_test.rb +0 -294
- data/test/operation/dsl/representer_test.rb +0 -169
- data/test/operation/model_test.rb +0 -60
- data/test/operation/params_test.rb +0 -36
- data/test/operation/persist_test.rb +0 -44
- data/test/operation/pipedream_test.rb +0 -59
- data/test/operation/pipetree_test.rb +0 -104
- data/test/operation/present_test.rb +0 -24
- data/test/operation/pundit_test.rb +0 -104
- data/test/operation/representer_test.rb +0 -254
- data/test/operation/resolver_test.rb +0 -47
- data/test/operation_test.rb +0 -143
data/test/docs/wrap_test.rb
DELETED
@@ -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,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"
|
data/test/module_test.rb
DELETED
@@ -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!
|