trailblazer 2.0.0.beta1 → 2.0.0.beta2
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 +4 -4
- data/CHANGES.md +7 -0
- data/Gemfile +2 -2
- data/README.md +1 -1
- data/lib/trailblazer.rb +0 -1
- data/lib/trailblazer/operation/builder.rb +1 -3
- data/lib/trailblazer/operation/callback.rb +1 -3
- data/lib/trailblazer/operation/contract.rb +29 -29
- data/lib/trailblazer/operation/guard.rb +9 -4
- data/lib/trailblazer/operation/model.rb +4 -7
- data/lib/trailblazer/operation/persist.rb +9 -7
- data/lib/trailblazer/operation/policy.rb +13 -7
- data/lib/trailblazer/operation/pundit.rb +9 -4
- data/lib/trailblazer/operation/resolver.rb +1 -3
- data/lib/trailblazer/operation/validate.rb +45 -40
- data/lib/trailblazer/operation/wrap.rb +1 -2
- data/lib/trailblazer/version.rb +1 -1
- data/test/docs/contract_test.rb +15 -13
- data/test/docs/dry_test.rb +1 -1
- data/test/docs/guard_test.rb +1 -0
- data/test/docs/nested_test.rb +1 -1
- data/test/docs/operation_test.rb +46 -0
- data/test/docs/pundit_test.rb +25 -2
- data/test/docs/representer_test.rb +5 -5
- data/test/docs/rescue_test.rb +4 -4
- data/test/docs/wrap_test.rb +3 -3
- data/test/operation/callback_test.rb +1 -1
- data/test/operation/contract_test.rb +12 -3
- data/test/operation/dsl/contract_test.rb +1 -1
- data/test/operation/model_test.rb +1 -1
- data/test/operation/persist_test.rb +2 -2
- metadata +4 -4
- data/lib/trailblazer/operation/controller.rb +0 -90
- data/lib/trailblazer/operation/present.rb +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c73755bd284523c496d97f90e637048d778a4233
|
4
|
+
data.tar.gz: 8c380418456b199836576b6d6cb78611d5e8ca73
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8aee8a0a4ccfa11d016504d269e7f9248e58696ae508c15031b5f59a0e35f647a68d940f1244b150d5b05a8533e9dab05c47bbe11a1d07ae79a315d949a2ff61
|
7
|
+
data.tar.gz: 7d6086b907f98b3d53bf152d1a72ef47d71bdbb5482ec38f6096cf682bc3e44f1faa83a35b30c7700e79936181ade80838d08190981d6355a9dc62e4a2993a61
|
data/CHANGES.md
CHANGED
@@ -102,6 +102,13 @@ You can now inject the following objects via `::call`:
|
|
102
102
|
|
103
103
|
* You can't call `Create.().contract` anymore. The contract instance(s) are available through the `Result` object.
|
104
104
|
|
105
|
+
# 2.0.0.beta2
|
106
|
+
|
107
|
+
* Removed `Operation::Controller`.
|
108
|
+
* Renamed `Persist` to `Contract::Persist`.
|
109
|
+
* Simplify inheritance by introducing `Operation::override`.
|
110
|
+
* `Contract` paths are now consistent.
|
111
|
+
|
105
112
|
# 2.0.0.beta1
|
106
113
|
|
107
114
|
* Still undefined `self.~`.
|
data/Gemfile
CHANGED
@@ -20,8 +20,8 @@ gem "dry-matcher"
|
|
20
20
|
gem "dry-validation"
|
21
21
|
|
22
22
|
|
23
|
-
|
24
|
-
gem "pipetree"
|
23
|
+
gem "trailblazer-operation"#, path: "../operation"
|
24
|
+
# gem "pipetree", path: "../pipetree"
|
25
25
|
|
26
26
|
gem "minitest-line"
|
27
27
|
|
data/README.md
CHANGED
@@ -127,7 +127,7 @@ Again, the controller only dispatchs to the operation and handles successful/inv
|
|
127
127
|
|
128
128
|
Operations encapsulate business logic and are the heart of a Trailblazer architecture.
|
129
129
|
|
130
|
-
The bare bones operation without any Trailblazery is implemented in [the `trailblazer-operation` gem
|
130
|
+
The bare bones operation without any Trailblazery is implemented in [the `trailblazer-operation` gem](https://github.com/trailblazer/trailblazer-operation) and can be used without our stack.
|
131
131
|
|
132
132
|
Operations don't know about HTTP or the environment. You could use an operation in Rails, Hanami, or Roda, it wouldn't know.
|
133
133
|
|
data/lib/trailblazer.rb
CHANGED
@@ -9,7 +9,6 @@ require "trailblazer/operation/model"
|
|
9
9
|
require "trailblazer/operation/contract"
|
10
10
|
require "trailblazer/operation/validate"
|
11
11
|
require "trailblazer/operation/representer"
|
12
|
-
require "trailblazer/operation/present"
|
13
12
|
require "trailblazer/operation/policy"
|
14
13
|
require "trailblazer/operation/pundit"
|
15
14
|
require "trailblazer/operation/guard"
|
@@ -6,41 +6,41 @@
|
|
6
6
|
#
|
7
7
|
# Needs Operation#model.
|
8
8
|
# Needs #[], #[]= skill dependency.
|
9
|
-
|
10
|
-
module
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
9
|
+
class Trailblazer::Operation
|
10
|
+
module Contract
|
11
|
+
module Build
|
12
|
+
# bla build contract at runtime.
|
13
|
+
def self.build_contract!(operation, options, name:"default", constant:nil, builder: nil)
|
14
|
+
# TODO: we could probably clean this up a bit at some point.
|
15
|
+
contract_class = constant || options["contract.#{name}.class"]
|
16
|
+
model = operation["model"] # FIXME: model.default
|
16
17
|
|
17
|
-
|
18
|
+
return operation["contract.#{name}"] = Uber::Option[builder].(operation, constant: contract_class, model: model) if builder
|
18
19
|
|
19
|
-
|
20
|
-
|
20
|
+
operation["contract.#{name}"] = contract_class.new(model)
|
21
|
+
end
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
|
23
|
+
def self.import!(operation, import, **args)
|
24
|
+
import.(:>, ->(operation, options) { build_contract!(operation, options, **args) },
|
25
|
+
name: "contract.build")
|
26
|
+
end
|
25
27
|
end
|
26
|
-
end
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
29
|
+
module DSL
|
30
|
+
# This is the class level DSL method.
|
31
|
+
# Op.contract #=> returns contract class
|
32
|
+
# Op.contract do .. end # defines contract
|
33
|
+
# Op.contract CommentForm # copies (and subclasses) external contract.
|
34
|
+
# Op.contract CommentForm do .. end # copies and extends contract.
|
35
|
+
def contract(name=:default, constant=nil, base: Reform::Form, &block)
|
36
|
+
heritage.record(:contract, name, constant, &block)
|
31
37
|
|
32
|
-
|
33
|
-
# This is the class level DSL method.
|
34
|
-
# Op.contract #=> returns contract class
|
35
|
-
# Op.contract do .. end # defines contract
|
36
|
-
# Op.contract CommentForm # copies (and subclasses) external contract.
|
37
|
-
# Op.contract CommentForm do .. end # copies and extends contract.
|
38
|
-
def contract(name=:default, constant=nil, base: Reform::Form, &block)
|
39
|
-
heritage.record(:contract, name, constant, &block)
|
38
|
+
path, form_class = Trailblazer::DSL::Build.new.({ prefix: :contract, class: base, container: self }, name, constant, block)
|
40
39
|
|
41
|
-
|
40
|
+
self[path] = form_class
|
41
|
+
end
|
42
|
+
end # Contract
|
43
|
+
end
|
42
44
|
|
43
|
-
|
44
|
-
end
|
45
|
-
end # Contract
|
45
|
+
DSL.macro!(:Build, Contract::Build, Contract.singleton_class)
|
46
46
|
end
|
@@ -4,8 +4,8 @@ require "uber/option"
|
|
4
4
|
class Trailblazer::Operation
|
5
5
|
module Policy
|
6
6
|
module Guard
|
7
|
-
def self.import!(operation, import, user_proc, options={})
|
8
|
-
Policy.add!(operation, import, options) { Guard.build(user_proc) }
|
7
|
+
def self.import!(operation, import, user_proc, options={}, insert_options={})
|
8
|
+
Policy.add!(operation, import, options, insert_options) { Guard.build(user_proc) }
|
9
9
|
end
|
10
10
|
|
11
11
|
def self.build(callable)
|
@@ -17,8 +17,13 @@ class Trailblazer::Operation
|
|
17
17
|
end
|
18
18
|
end # Guard
|
19
19
|
|
20
|
-
def self.Guard(
|
21
|
-
|
20
|
+
def self.Guard(proc, name: :default, &block)
|
21
|
+
options = {
|
22
|
+
name: name,
|
23
|
+
path: "policy.#{name}.eval",
|
24
|
+
}
|
25
|
+
|
26
|
+
[Guard, [proc, options], block]
|
22
27
|
end
|
23
28
|
end
|
24
29
|
end
|
@@ -3,16 +3,13 @@ class Trailblazer::Operation
|
|
3
3
|
Step = ->(operation, options) { options["model"] = operation.model!(options["params"]) }
|
4
4
|
|
5
5
|
def self.import!(operation, import, model_class, action=nil)
|
6
|
-
|
7
|
-
# FIXME: prototyping inheritance. should we handle that here?
|
8
|
-
return operation["model.action"] = model_class
|
9
|
-
end
|
10
|
-
|
11
|
-
import.(:&, Step, name: "model.build")
|
12
|
-
|
6
|
+
# configure
|
13
7
|
operation["model.class"] = model_class
|
14
8
|
operation["model.action"] = action
|
15
9
|
|
10
|
+
# add
|
11
|
+
import.(:&, Step, name: "model.build")
|
12
|
+
|
16
13
|
operation.send :include, BuildMethods
|
17
14
|
end
|
18
15
|
|
@@ -1,13 +1,15 @@
|
|
1
1
|
class Trailblazer::Operation
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
2
|
+
module Contract
|
3
|
+
module Persist
|
4
|
+
def self.import!(operation, import, options={})
|
5
|
+
save_method = options[:method] || :save
|
6
|
+
contract_name = options[:name] || "contract.default"
|
6
7
|
|
7
|
-
|
8
|
-
|
8
|
+
import.(:&, ->(input, options) { options[contract_name].send(save_method) }, # TODO: test me.
|
9
|
+
name: "persist.save")
|
10
|
+
end
|
9
11
|
end
|
10
12
|
end
|
11
13
|
|
12
|
-
DSL.macro!(:Persist, Persist)
|
14
|
+
DSL.macro!(:Persist, Contract::Persist, Contract.singleton_class) # Contract::Persist()
|
13
15
|
end
|
@@ -2,7 +2,6 @@ class Trailblazer::Operation
|
|
2
2
|
module Policy
|
3
3
|
# Step: This generically `call`s a policy and then pushes its result to `options`.
|
4
4
|
# You can use any callable object as a policy with this step.
|
5
|
-
# :private:
|
6
5
|
class Eval
|
7
6
|
include Uber::Callable
|
8
7
|
|
@@ -23,17 +22,24 @@ class Trailblazer::Operation
|
|
23
22
|
end
|
24
23
|
end
|
25
24
|
|
26
|
-
# Adds the `yield` result to the pipe and treats it like a
|
27
|
-
|
28
|
-
|
25
|
+
# Adds the `yield` result to the pipe and treats it like a
|
26
|
+
# policy-compatible object at runtime.
|
27
|
+
def self.add!(operation, import, options, insert_options, &block)
|
28
|
+
name = options[:name]
|
29
|
+
path = options[:path]
|
29
30
|
|
30
|
-
|
31
|
-
operation[path = "policy.#{name}.eval"] = yield
|
31
|
+
configure!(operation, import, options, &block)
|
32
32
|
|
33
33
|
# add step.
|
34
34
|
import.(:&, Eval.new( name: name, path: path ),
|
35
|
-
name: path
|
35
|
+
insert_options.merge(name: path)
|
36
36
|
)
|
37
37
|
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def self.configure!(operation, import, options)
|
41
|
+
# configure class level.
|
42
|
+
operation[ options[:path] ] = yield
|
43
|
+
end
|
38
44
|
end
|
39
45
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
class Trailblazer::Operation
|
2
2
|
module Policy
|
3
3
|
module Pundit
|
4
|
-
def self.import!(operation, import, policy_class, action, options={})
|
5
|
-
Policy.add!(operation, import, options) { Pundit.build(policy_class, action) }
|
4
|
+
def self.import!(operation, import, policy_class, action, options={}, insert_options={})
|
5
|
+
Policy.add!(operation, import, options, insert_options) { Pundit.build(policy_class, action) }
|
6
6
|
end
|
7
7
|
|
8
8
|
def self.build(*args, &block)
|
@@ -35,8 +35,13 @@ class Trailblazer::Operation
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
def self.Pundit(
|
39
|
-
|
38
|
+
def self.Pundit(policy, condition, name: :default, &block)
|
39
|
+
options = {
|
40
|
+
name: name,
|
41
|
+
path: "policy.#{name}.eval",
|
42
|
+
}
|
43
|
+
|
44
|
+
[Pundit, [policy, condition, options], block]
|
40
45
|
end
|
41
46
|
end
|
42
47
|
end
|
@@ -1,50 +1,55 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
1
|
+
class Trailblazer::Operation
|
2
|
+
module Contract
|
3
|
+
# result.contract = {..}
|
4
|
+
# result.contract.errors = {..}
|
5
|
+
# Deviate to left track if optional key is not found in params.
|
6
|
+
# Deviate to left if validation result falsey.
|
7
|
+
module Validate
|
8
|
+
def self.import!(operation, import, skip_extract:false, name: "default", representer:false, key: nil) # DISCUSS: should we introduce something like Validate::Deserializer?
|
9
|
+
if representer
|
10
|
+
skip_extract = true
|
11
|
+
operation["representer.#{name}.class"] = representer
|
12
|
+
end
|
12
13
|
|
13
|
-
|
14
|
-
name: "validate.params.extract") unless skip_extract
|
14
|
+
params_path = "contract.#{name}.params" # extract_params! save extracted params here.
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
import.(:&, ->(operation, options) { validate!(operation, options, name: name, representer: options["representer.#{name}.class"], **args) },
|
19
|
-
name: "contract.validate")
|
20
|
-
end
|
16
|
+
import.(:&, ->(input, options) { extract_params!(input, options, key: key, path: params_path) },
|
17
|
+
name: params_path) unless skip_extract
|
21
18
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
19
|
+
# call the actual contract.validate(params)
|
20
|
+
# DISCUSS: should we pass the representer here, or do that in #validate! i'm still mulling over what's the best, most generic approach.
|
21
|
+
import.(:&, ->(operation, options) do
|
22
|
+
validate!(operation, options, name: name, representer: options["representer.#{name}.class"], key: key, params_path: params_path)
|
23
|
+
end,
|
24
|
+
name: "contract.#{name}.validate", # visible name of the pipe step.
|
25
|
+
)
|
26
|
+
end
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
28
|
+
def self.extract_params!(operation, options, key:nil, path:nil)
|
29
|
+
# TODO: introduce nested pipes and pass composed input instead.
|
30
|
+
options[path] = key ? options["params"][key] : options["params"]
|
31
|
+
end
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
# use "document" as the body and let the representer deserialize to the contract.
|
35
|
-
# this will be simplified once we have Deserializer.
|
36
|
-
# translates to contract.("{document: bla}") { MyRepresenter.new(contract).from_json .. }
|
37
|
-
contract.(options[from]) { |document| representer.new(contract).parse(document) }
|
38
|
-
else
|
39
|
-
# let Reform handle the deserialization.
|
40
|
-
contract.(options["params.validate"])
|
41
|
-
end
|
33
|
+
def self.validate!(operation, options, name: nil, representer:false, from: "document", params_path:nil, **)
|
34
|
+
path = "contract.#{name}"
|
35
|
+
contract = operation[path]
|
42
36
|
|
43
|
-
|
37
|
+
# this is for 1.1-style compatibility and should be removed once we have Deserializer in place:
|
38
|
+
operation["result.#{path}"] = result =
|
39
|
+
if representer
|
40
|
+
# use "document" as the body and let the representer deserialize to the contract.
|
41
|
+
# this will be simplified once we have Deserializer.
|
42
|
+
# translates to contract.("{document: bla}") { MyRepresenter.new(contract).from_json .. }
|
43
|
+
contract.(options[from]) { |document| representer.new(contract).parse(document) }
|
44
|
+
else
|
45
|
+
# let Reform handle the deserialization.
|
46
|
+
contract.(options[params_path])
|
47
|
+
end
|
48
|
+
|
49
|
+
result.success?
|
50
|
+
end
|
44
51
|
end
|
45
52
|
end
|
46
53
|
|
47
|
-
|
48
|
-
[ Validate, args, block ]
|
49
|
-
end
|
54
|
+
DSL.macro!(:Validate, Contract::Validate, Contract.singleton_class)
|
50
55
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
class Trailblazer::Operation
|
2
2
|
module Wrap
|
3
3
|
def self.import!(operation, import, wrap, _options={}, &block)
|
4
|
-
pipe_api = API.new(operation, pipe = ::Pipetree::Flow
|
4
|
+
pipe_api = API.new(operation, pipe = ::Pipetree::Flow.new)
|
5
5
|
|
6
6
|
# DISCUSS: don't instance_exec when |pipe| given?
|
7
7
|
# yield pipe_api # create the nested pipe.
|
@@ -32,6 +32,5 @@ class Trailblazer::Operation
|
|
32
32
|
DSL.macro!(:Wrap, Wrap)
|
33
33
|
end
|
34
34
|
|
35
|
-
|
36
35
|
# (options, *) => (options, operation, bla)
|
37
36
|
# (*, params:, **) => (options, operation, bla, options)
|
data/lib/trailblazer/version.rb
CHANGED
data/test/docs/contract_test.rb
CHANGED
@@ -24,7 +24,7 @@ class DocsContractOverviewTest < Minitest::Spec
|
|
24
24
|
step Model( Song, :new )
|
25
25
|
step Contract::Build()
|
26
26
|
step Contract::Validate()
|
27
|
-
step Persist( method: :sync )
|
27
|
+
step Contract::Persist( method: :sync )
|
28
28
|
#~contractonly end
|
29
29
|
end
|
30
30
|
#:overv-reform end
|
@@ -121,7 +121,7 @@ class DocsContractExplicitTest < Minitest::Spec
|
|
121
121
|
step Model( Song, :new )
|
122
122
|
step Contract::Build()
|
123
123
|
step Contract::Validate()
|
124
|
-
step Persist( method: :sync )
|
124
|
+
step Contract::Persist( method: :sync )
|
125
125
|
end
|
126
126
|
#:reform-inline-op end
|
127
127
|
end
|
@@ -141,11 +141,11 @@ class DocsContractKeyTest < Minitest::Spec
|
|
141
141
|
step Model( Song, :new )
|
142
142
|
step Contract::Build()
|
143
143
|
step Contract::Validate( key: "song" )
|
144
|
-
step Persist( method: :sync )
|
144
|
+
step Contract::Persist( method: :sync )
|
145
145
|
end
|
146
146
|
#:key end
|
147
147
|
|
148
|
-
it { Create.({}).inspect("model").must_equal %{<Result:false [#<struct DocsContractKeyTest::Song id=nil, title=nil
|
148
|
+
it { Create.({}).inspect("model", "result.contract.default.extract").must_equal %{<Result:false [#<struct DocsContractKeyTest::Song id=nil, title=nil>, nil] >} }
|
149
149
|
it { Create.({"song" => { title: "SVG" }}).inspect("model").must_equal %{<Result:true [#<struct DocsContractKeyTest::Song id=nil, title="SVG">] >} }
|
150
150
|
end
|
151
151
|
|
@@ -168,10 +168,10 @@ class DocsContractSeparateKeyTest < Minitest::Spec
|
|
168
168
|
step Contract::Build()
|
169
169
|
consider :extract_params!
|
170
170
|
step Contract::Validate( skip_extract: true )
|
171
|
-
step Persist( method: :sync )
|
171
|
+
step Contract::Persist( method: :sync )
|
172
172
|
|
173
173
|
def extract_params!(options)
|
174
|
-
options["params
|
174
|
+
options["contract.default.params"] = options["params"][type]
|
175
175
|
end
|
176
176
|
end
|
177
177
|
#:key-extr end
|
@@ -196,7 +196,7 @@ class ContractConstantTest < Minitest::Spec
|
|
196
196
|
step Model( Song, :new )
|
197
197
|
step Contract::Build( constant: MyContract )
|
198
198
|
step Contract::Validate()
|
199
|
-
step Persist( method: :sync )
|
199
|
+
step Contract::Persist( method: :sync )
|
200
200
|
end
|
201
201
|
#:constant end
|
202
202
|
|
@@ -217,7 +217,7 @@ class ContractNamedConstantTest < Minitest::Spec
|
|
217
217
|
step Model( Song, :new )
|
218
218
|
step Contract::Build( constant: MyContract, name: "form" )
|
219
219
|
step Contract::Validate( name: "form" )
|
220
|
-
step Persist( method: :sync, name: "contract.form" )
|
220
|
+
step Contract::Persist( method: :sync, name: "contract.form" )
|
221
221
|
end
|
222
222
|
#:constant-name end
|
223
223
|
|
@@ -241,7 +241,7 @@ class ContractInjectConstantTest < Minitest::Spec
|
|
241
241
|
step Model( Song, :new )
|
242
242
|
step Contract::Build()
|
243
243
|
step Contract::Validate()
|
244
|
-
step Persist( method: :sync )
|
244
|
+
step Contract::Persist( method: :sync )
|
245
245
|
end
|
246
246
|
#:di-constant end
|
247
247
|
|
@@ -285,11 +285,13 @@ class DryValidationContractTest < Minitest::Spec
|
|
285
285
|
step Model( Song, :new ) # create the op's main model.
|
286
286
|
step Contract::Build( name: "form" ) # create the Reform contract.
|
287
287
|
step Contract::Validate( name: "form" ) # validate the Reform contract.
|
288
|
-
step Persist( method: :sync, name: "contract.form" ) # persist the contract's data via the model.
|
288
|
+
step Contract::Persist( method: :sync, name: "contract.form" ) # persist the contract's data via the model.
|
289
289
|
#~form end
|
290
290
|
end
|
291
291
|
#:dry-schema end
|
292
292
|
|
293
|
+
puts "@@@@@ #{Create["pipetree"].inspect(style: :rows)}"
|
294
|
+
|
293
295
|
it { Create.({}).inspect("model", "result.contract.params").must_equal %{<Result:false [nil, #<Dry::Validation::Result output={} errors={:id=>[\"is missing\"]}>] >} }
|
294
296
|
it { Create.({ id: 1 }).inspect("model", "result.contract.params").must_equal %{<Result:false [#<struct DryValidationContractTest::Song id=nil, title=nil>, #<Dry::Validation::Result output={:id=>1} errors={}>] >} }
|
295
297
|
# it { Create.({ id: 1, title: "" }).inspect("model", "result.contract.form").must_equal %{<Result:false [#<struct DryValidationContractTest::Song id=nil, title=nil>] >} }
|
@@ -349,7 +351,7 @@ class DocContractBuilderTest < Minitest::Spec
|
|
349
351
|
step Model( Song, :new )
|
350
352
|
step Contract::Build( builder: :default_contract! )
|
351
353
|
step Contract::Validate()
|
352
|
-
step Persist( method: :sync )
|
354
|
+
step Contract::Persist( method: :sync )
|
353
355
|
|
354
356
|
def default_contract!(constant:, model:)
|
355
357
|
constant.new(model, current_user: self["current_user"])
|
@@ -377,7 +379,7 @@ class DocContractBuilderTest < Minitest::Spec
|
|
377
379
|
})
|
378
380
|
#:builder-proc end
|
379
381
|
step Contract::Validate()
|
380
|
-
step Persist( method: :sync )
|
382
|
+
step Contract::Persist( method: :sync )
|
381
383
|
end
|
382
384
|
|
383
385
|
it { Update.({}).inspect("model").must_equal %{<Result:false [#<struct DocContractBuilderTest::Song id=nil, title=nil>] >} }
|
@@ -397,7 +399,7 @@ class DocContractTest < Minitest::Spec
|
|
397
399
|
step Model( Song, :new )
|
398
400
|
step Contract::Build() # resolves to "contract.class.default" and is resolved at runtime.
|
399
401
|
step Contract::Validate()
|
400
|
-
step Persist( method: :sync )
|
402
|
+
step Contract::Persist( method: :sync )
|
401
403
|
end
|
402
404
|
|
403
405
|
it { Block.({}).inspect("model").must_equal %{<Result:true [#<struct DocContractTest::Song id=nil, title=nil>] >} }
|
data/test/docs/dry_test.rb
CHANGED
data/test/docs/guard_test.rb
CHANGED
@@ -29,6 +29,7 @@ class DocsGuardProcTest < Minitest::Spec
|
|
29
29
|
#---
|
30
30
|
#- Guard inheritance
|
31
31
|
class New < Create
|
32
|
+
override Policy::Guard( ->(options) { options["current_user"] } )
|
32
33
|
end
|
33
34
|
|
34
35
|
it { New["pipetree"].inspect.must_equal %{[>>operation.new,&policy.default.eval,>process]} }
|
data/test/docs/nested_test.rb
CHANGED
@@ -0,0 +1,46 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class DocsOperationExampleTest < Minitest::Spec
|
4
|
+
Song = Struct.new(:id, :title, :created_by) do
|
5
|
+
def save; true; end
|
6
|
+
end
|
7
|
+
#:op
|
8
|
+
|
9
|
+
class Song::Create < Trailblazer::Operation
|
10
|
+
extend Contract::DSL
|
11
|
+
|
12
|
+
contract do
|
13
|
+
property :title
|
14
|
+
validates :title, presence: true
|
15
|
+
end
|
16
|
+
|
17
|
+
step Model( Song, :new )
|
18
|
+
consider :assign_current_user!
|
19
|
+
step Contract::Build()
|
20
|
+
step Contract::Validate( )
|
21
|
+
failure :log_error!
|
22
|
+
step Contract::Persist( )
|
23
|
+
|
24
|
+
def log_error!(options)
|
25
|
+
# ..
|
26
|
+
end
|
27
|
+
|
28
|
+
def assign_current_user!(options)
|
29
|
+
options["model"].created_by =
|
30
|
+
options["current_user"]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
#:op end
|
34
|
+
|
35
|
+
it { Song::Create.({ }).inspect("model").must_equal %{<Result:false [#<struct DocsOperationExampleTest::Song id=nil, title=nil, created_by=nil>] >} }
|
36
|
+
it { Song::Create.({ title: "Nothin'" }, "current_user"=>Module).inspect("model").must_equal %{<Result:true [#<struct DocsOperationExampleTest::Song id=nil, title="Nothin'", created_by=Module>] >} }
|
37
|
+
end
|
38
|
+
|
39
|
+
class DndTest < Minitest::Spec
|
40
|
+
class Create < Trailblazer::Operation
|
41
|
+
consider :authorize!
|
42
|
+
failure :auth_err!
|
43
|
+
consider :save!
|
44
|
+
self.< Wrap
|
45
|
+
end
|
46
|
+
end
|
data/test/docs/pundit_test.rb
CHANGED
@@ -9,6 +9,10 @@ class MyPolicy
|
|
9
9
|
def create?
|
10
10
|
@user == Module && @model.id.nil?
|
11
11
|
end
|
12
|
+
|
13
|
+
def new?
|
14
|
+
@user == Class
|
15
|
+
end
|
12
16
|
end
|
13
17
|
#:policy end
|
14
18
|
|
@@ -38,12 +42,31 @@ class DocsPunditProcTest < Minitest::Spec
|
|
38
42
|
result["result.policy.default"]["policy"].is_a?(MyPolicy).must_equal true
|
39
43
|
end
|
40
44
|
|
41
|
-
|
42
|
-
#-
|
45
|
+
#---
|
46
|
+
#- override
|
43
47
|
class New < Create
|
48
|
+
override Policy::Pundit( MyPolicy, :new? )
|
44
49
|
end
|
45
50
|
|
46
51
|
it { New["pipetree"].inspect.must_equal %{[>>operation.new,&model.build,&policy.default.eval]} }
|
52
|
+
it { New.({}, "current_user" => Class ).inspect("model").must_equal %{<Result:true [#<struct DocsPunditProcTest::Song id=nil>] >} }
|
53
|
+
it { New.({}, "current_user" => nil ).inspect("model").must_equal %{<Result:false [#<struct DocsPunditProcTest::Song id=nil>] >} }
|
54
|
+
|
55
|
+
#---
|
56
|
+
#- override with :name
|
57
|
+
class Edit < Trailblazer::Operation
|
58
|
+
step Policy::Pundit( MyPolicy, :create?, name: "first" )
|
59
|
+
step Policy::Pundit( MyPolicy, :new?, name: "second" )
|
60
|
+
end
|
61
|
+
|
62
|
+
class Update < Edit
|
63
|
+
override Policy::Pundit( MyPolicy, :new?, name: "first" )
|
64
|
+
end
|
65
|
+
|
66
|
+
it { Edit["pipetree"].inspect.must_equal %{[>>operation.new,&policy.first.eval,&policy.second.eval]} }
|
67
|
+
it { Edit.({}, "current_user" => Class).inspect("model").must_equal %{<Result:false [nil] >} }
|
68
|
+
it { Update["pipetree"].inspect.must_equal %{[>>operation.new,&policy.first.eval,&policy.second.eval]} }
|
69
|
+
it { Update.({}, "current_user" => Class).inspect("model").must_equal %{<Result:true [nil] >} }
|
47
70
|
|
48
71
|
#---
|
49
72
|
# dependency injection
|
@@ -15,7 +15,7 @@ class DocsRepresenterInferTest < Minitest::Spec
|
|
15
15
|
step Model( Song, :new )
|
16
16
|
step Contract::Build( constant: MyContract )
|
17
17
|
step Contract::Validate( representer: Representer.infer(MyContract, format: Representable::JSON) )
|
18
|
-
step Persist( method: :sync )
|
18
|
+
step Contract::Persist( method: :sync )
|
19
19
|
end
|
20
20
|
#:infer end
|
21
21
|
|
@@ -44,7 +44,7 @@ class DocsRepresenterExplicitTest < Minitest::Spec
|
|
44
44
|
step Model( Song, :new )
|
45
45
|
step Contract::Build( constant: MyContract )
|
46
46
|
step Contract::Validate( representer: MyRepresenter ) # :representer
|
47
|
-
step Persist( method: :sync )
|
47
|
+
step Contract::Persist( method: :sync )
|
48
48
|
end
|
49
49
|
#:explicit-op end
|
50
50
|
|
@@ -108,7 +108,7 @@ class DocsRepresenterDITest < Minitest::Spec
|
|
108
108
|
step Model( Song, :new )
|
109
109
|
step Contract::Build( constant: MyContract )
|
110
110
|
step Contract::Validate()
|
111
|
-
step Persist( method: :sync )
|
111
|
+
step Contract::Persist( method: :sync )
|
112
112
|
end
|
113
113
|
|
114
114
|
let (:json) { MultiJson.dump(id: 1) }
|
@@ -136,7 +136,7 @@ class DocsRepresenterInlineTest < Minitest::Spec
|
|
136
136
|
step Model( Song, :new )
|
137
137
|
step Contract::Build( constant: MyContract )
|
138
138
|
step Contract::Validate( representer: self["representer.default.class"] )
|
139
|
-
step Persist( method: :sync )
|
139
|
+
step Contract::Persist( method: :sync )
|
140
140
|
end
|
141
141
|
#:inline end
|
142
142
|
|
@@ -231,7 +231,7 @@ class DocsRepresenterFullExampleTest < Minitest::Spec
|
|
231
231
|
step Model( Song, :new )
|
232
232
|
step Contract::Build()
|
233
233
|
step Contract::Validate( representer: self["representer.parse.class"] )
|
234
|
-
step Persist( method: :sync )
|
234
|
+
step Contract::Persist( method: :sync )
|
235
235
|
end
|
236
236
|
#:full end
|
237
237
|
|
data/test/docs/rescue_test.rb
CHANGED
@@ -20,7 +20,7 @@ class NestedRescueTest < Minitest::Spec
|
|
20
20
|
self.< ->(options) { options["inner-err"] = true }
|
21
21
|
}
|
22
22
|
step ->(options) { options["e"] = true }
|
23
|
-
|
23
|
+
failure ->(options) { options["outer-err"] = true }
|
24
24
|
end
|
25
25
|
|
26
26
|
it { NestedInsanity["pipetree"].inspect.must_equal %{[>>operation.new,&Rescue:10,>:22,<NestedRescueTest::NestedInsanity:23]} }
|
@@ -61,7 +61,7 @@ class RescueTest < Minitest::Spec
|
|
61
61
|
step Contract::Build( constant: MyContract )
|
62
62
|
}
|
63
63
|
step Contract::Validate()
|
64
|
-
step Persist( method: :sync )
|
64
|
+
step Contract::Persist( method: :sync )
|
65
65
|
end
|
66
66
|
#:simple end
|
67
67
|
|
@@ -82,7 +82,7 @@ class RescueTest < Minitest::Spec
|
|
82
82
|
step Contract::Build( constant: MyContract )
|
83
83
|
}
|
84
84
|
step Contract::Validate()
|
85
|
-
step Persist( method: :sync )
|
85
|
+
step Contract::Persist( method: :sync )
|
86
86
|
|
87
87
|
def rollback!(exception, options)
|
88
88
|
options["x"] = exception.class
|
@@ -122,7 +122,7 @@ class RescueTest < Minitest::Spec
|
|
122
122
|
step ->(options) { options["model"].lock! } # lock the model.
|
123
123
|
step Contract::Build( constant: MyContract )
|
124
124
|
step Contract::Validate( )
|
125
|
-
step Persist( method: :sync )
|
125
|
+
step Contract::Persist( method: :sync )
|
126
126
|
}
|
127
127
|
}
|
128
128
|
failure :error! # handle all kinds of errors.
|
data/test/docs/wrap_test.rb
CHANGED
@@ -23,7 +23,7 @@ class WrapTest < Minitest::Spec
|
|
23
23
|
step Contract::Build( constant: MyContract )
|
24
24
|
}
|
25
25
|
step Contract::Validate()
|
26
|
-
step Persist( method: :sync )
|
26
|
+
step Contract::Persist( method: :sync )
|
27
27
|
end
|
28
28
|
|
29
29
|
it { Create.( id: 1, title: "Prodigal Son" )["contract.default"].model.inspect.must_equal %{#<struct WrapTest::Song id=1, title="Prodigal Son">} }
|
@@ -100,7 +100,7 @@ class WrapTest < Minitest::Spec
|
|
100
100
|
#~wrap-only
|
101
101
|
step Contract::Build( constant: MyContract )
|
102
102
|
step Contract::Validate( )
|
103
|
-
step Persist( method: :sync )
|
103
|
+
step Contract::Persist( method: :sync )
|
104
104
|
#~wrap-only end
|
105
105
|
}
|
106
106
|
failure :error! # handle all kinds of errors.
|
@@ -150,7 +150,7 @@ class WrapTest < Minitest::Spec
|
|
150
150
|
#~wrap-onlyy
|
151
151
|
step Contract::Build( constant: MyContract )
|
152
152
|
step Contract::Validate( )
|
153
|
-
step Persist( method: :sync )
|
153
|
+
step Contract::Persist( method: :sync )
|
154
154
|
#~wrap-onlyy end
|
155
155
|
}
|
156
156
|
failure :error! # handle all kinds of errors.
|
@@ -55,7 +55,7 @@ class OperationCallbackTest < MiniTest::Spec
|
|
55
55
|
|
56
56
|
#---
|
57
57
|
#- inheritance
|
58
|
-
it { Update["pipetree"].inspect.must_equal %{[>>operation.new,&model.build,>contract.build,&
|
58
|
+
it { Update["pipetree"].inspect.must_equal %{[>>operation.new,&model.build,>contract.build,&contract.default.params,&contract.default.validate,&callback.default]} }
|
59
59
|
|
60
60
|
|
61
61
|
it "invokes all callbacks" do
|
@@ -295,6 +295,7 @@ class ValidateTest < Minitest::Spec
|
|
295
295
|
self.| Contract::Build()
|
296
296
|
self.| Contract::Validate( key: :song) # generic validate call for you.
|
297
297
|
# ->(*) { validate(options["params"][:song]) } # <-- TODO
|
298
|
+
step Contract::Persist( method: :sync )
|
298
299
|
end
|
299
300
|
|
300
301
|
# success
|
@@ -305,16 +306,24 @@ class ValidateTest < Minitest::Spec
|
|
305
306
|
it { Upsert.().success?.must_equal false }
|
306
307
|
|
307
308
|
#---
|
308
|
-
# params
|
309
|
+
# contract.default.params gets set (TODO: change in 2.1)
|
309
310
|
it { Upsert.(song: { title: "SVG" })["params"].must_equal({:song=>{:title=>"SVG"}}) }
|
310
|
-
it { Upsert.(song: { title: "SVG" })["params
|
311
|
+
it { Upsert.(song: { title: "SVG" })["contract.default.params"].must_equal({:title=>"SVG"}) }
|
311
312
|
|
312
313
|
#---
|
313
314
|
#- inheritance
|
314
315
|
class New < Upsert
|
315
316
|
end
|
316
317
|
|
317
|
-
it { New["pipetree"].inspect.must_equal %{[>>operation.new,&model.build,>contract.build,&
|
318
|
+
it { New["pipetree"].inspect.must_equal %{[>>operation.new,&model.build,>contract.build,&contract.default.params,&contract.default.validate,&persist.save]} }
|
319
|
+
|
320
|
+
#- overwriting Validate
|
321
|
+
class NewHit < Upsert
|
322
|
+
override Contract::Validate( key: :hit )
|
323
|
+
end
|
324
|
+
|
325
|
+
it { NewHit["pipetree"].inspect.must_equal %{[>>operation.new,&model.build,>contract.build,&contract.default.params,&contract.default.validate,&persist.save]} }
|
326
|
+
it { NewHit.(:hit => { title: "Hooray For Me" }).inspect("model").must_equal %{<Result:true [#<struct ContractTest::Song title=\"Hooray For Me\">] >} }
|
318
327
|
end
|
319
328
|
|
320
329
|
# #---
|
@@ -17,7 +17,7 @@ class DslContractTest < MiniTest::Spec
|
|
17
17
|
includer.| Trailblazer::Operation::Model( OpenStruct, :new )
|
18
18
|
includer.| Trailblazer::Operation::Contract::Build()
|
19
19
|
includer.| Trailblazer::Operation::Contract::Validate()
|
20
|
-
includer.| Trailblazer::Operation::Persist( save_method: :sync )
|
20
|
+
includer.| Trailblazer::Operation::Contract::Persist( save_method: :sync )
|
21
21
|
# includer.> ->(op, *) { op["x"] = [] }
|
22
22
|
end
|
23
23
|
end
|
@@ -16,7 +16,7 @@ class ModelTest < Minitest::Spec
|
|
16
16
|
it { Create.({})["model"].inspect.must_equal %{#<struct ModelTest::Song id=nil>} }
|
17
17
|
|
18
18
|
class Update < Create
|
19
|
-
|
19
|
+
override Model(Song, :update)
|
20
20
|
end
|
21
21
|
|
22
22
|
# :find it
|
@@ -15,7 +15,7 @@ class PersistTest < Minitest::Spec
|
|
15
15
|
self.| Contract::Build()
|
16
16
|
self.| Contract::Validate()
|
17
17
|
self.< ->(options) { options["1. fail"] = "Validate" }
|
18
|
-
self.| Persist()
|
18
|
+
self.| Contract::Persist()
|
19
19
|
self.< ->(options) { options["2. fail"] = "Persist" }
|
20
20
|
end
|
21
21
|
|
@@ -35,7 +35,7 @@ class PersistTest < Minitest::Spec
|
|
35
35
|
class Update < Create
|
36
36
|
end
|
37
37
|
|
38
|
-
it { Update["pipetree"].inspect.must_equal %{[>>operation.new,&model.build,>contract.build,&
|
38
|
+
it { Update["pipetree"].inspect.must_equal %{[>>operation.new,&model.build,>contract.build,&contract.default.params,&contract.default.validate,<PersistTest::Update:17,&persist.save,<PersistTest::Update:19]} }
|
39
39
|
|
40
40
|
#---
|
41
41
|
it do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trailblazer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.0.
|
4
|
+
version: 2.0.0.beta2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-12-
|
11
|
+
date: 2016-12-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: trailblazer-operation
|
@@ -189,7 +189,6 @@ files:
|
|
189
189
|
- lib/trailblazer/operation/builder.rb
|
190
190
|
- lib/trailblazer/operation/callback.rb
|
191
191
|
- lib/trailblazer/operation/contract.rb
|
192
|
-
- lib/trailblazer/operation/controller.rb
|
193
192
|
- lib/trailblazer/operation/guard.rb
|
194
193
|
- lib/trailblazer/operation/model.rb
|
195
194
|
- lib/trailblazer/operation/module.rb
|
@@ -197,7 +196,6 @@ files:
|
|
197
196
|
- lib/trailblazer/operation/params.rb
|
198
197
|
- lib/trailblazer/operation/persist.rb
|
199
198
|
- lib/trailblazer/operation/policy.rb
|
200
|
-
- lib/trailblazer/operation/present.rb
|
201
199
|
- lib/trailblazer/operation/procedural/contract.rb
|
202
200
|
- lib/trailblazer/operation/procedural/validate.rb
|
203
201
|
- lib/trailblazer/operation/pundit.rb
|
@@ -213,6 +211,7 @@ files:
|
|
213
211
|
- test/docs/dry_test.rb
|
214
212
|
- test/docs/guard_test.rb
|
215
213
|
- test/docs/nested_test.rb
|
214
|
+
- test/docs/operation_test.rb
|
216
215
|
- test/docs/policy_test.rb
|
217
216
|
- test/docs/pundit_test.rb
|
218
217
|
- test/docs/representer_test.rb
|
@@ -272,6 +271,7 @@ test_files:
|
|
272
271
|
- test/docs/dry_test.rb
|
273
272
|
- test/docs/guard_test.rb
|
274
273
|
- test/docs/nested_test.rb
|
274
|
+
- test/docs/operation_test.rb
|
275
275
|
- test/docs/policy_test.rb
|
276
276
|
- test/docs/pundit_test.rb
|
277
277
|
- test/docs/representer_test.rb
|
@@ -1,90 +0,0 @@
|
|
1
|
-
module Trailblazer::Operation::Controller
|
2
|
-
private
|
3
|
-
def form(operation_class, options={})
|
4
|
-
res, options = operation_for!(operation_class, options) { |params| { operation: operation_class.present(params) } }
|
5
|
-
res.contract.prepopulate!(options) # equals to @form.prepopulate!
|
6
|
-
|
7
|
-
res.contract
|
8
|
-
end
|
9
|
-
|
10
|
-
# Provides the operation instance, model and contract without running #process.
|
11
|
-
# Returns the operation.
|
12
|
-
def present(operation_class, options={})
|
13
|
-
res, options = operation_for!(operation_class, options.merge(skip_form: true)) { |params| { operation: operation_class.present(params) } }
|
14
|
-
res # FIXME.
|
15
|
-
end
|
16
|
-
|
17
|
-
def collection(*args)
|
18
|
-
res, op = operation!(*args)
|
19
|
-
@collection = op.model
|
20
|
-
op
|
21
|
-
end
|
22
|
-
|
23
|
-
def run(operation_class, options={}, &block)
|
24
|
-
res = operation_for!(operation_class, options) { |params| operation_class.(params) }
|
25
|
-
|
26
|
-
yield res if res[:valid] and block_given?
|
27
|
-
|
28
|
-
res # FIXME.
|
29
|
-
end
|
30
|
-
|
31
|
-
# The block passed to #respond is always run, regardless of the validity result.
|
32
|
-
def respond(operation_class, options={}, &block)
|
33
|
-
res, op = operation_for!(operation_class, options) { |params| operation_class.run(params) }
|
34
|
-
namespace = options.delete(:namespace) || []
|
35
|
-
|
36
|
-
return respond_with *namespace, op, options if not block_given?
|
37
|
-
respond_with *namespace, op, options, &Proc.new { |formats| block.call(op, formats) } if block_given?
|
38
|
-
end
|
39
|
-
|
40
|
-
private
|
41
|
-
def process_params!(params)
|
42
|
-
end
|
43
|
-
|
44
|
-
# Normalizes parameters and invokes the operation (including its builders).
|
45
|
-
def operation_for!(operation_class, options, &block)
|
46
|
-
params = options[:params] || self.params # TODO: test params: parameter properly in all 4 methods.
|
47
|
-
process_params!(params) # deprecate or rename to #setup_params!
|
48
|
-
|
49
|
-
res = Endpoint.new(operation_class, params, request, options).(&block)
|
50
|
-
setup_operation_instance_variables!(res, options)
|
51
|
-
|
52
|
-
[res, options.merge(params: params)]
|
53
|
-
end
|
54
|
-
|
55
|
-
def setup_operation_instance_variables!(result, options)
|
56
|
-
@operation = result # FIXME: remove!
|
57
|
-
@model = result["model"]
|
58
|
-
@form = result["contract"] unless options[:skip_form]
|
59
|
-
end
|
60
|
-
|
61
|
-
# Encapsulates HTTP-specific logic needed before running an operation.
|
62
|
-
# Right now, all this does is #document_body! which figures out whether or not to pass the request body
|
63
|
-
# into params, so the operation can use a representer to deserialize the original document.
|
64
|
-
# To be used in Hanami, Roda, Rails, etc.
|
65
|
-
class Endpoint
|
66
|
-
def initialize(operation_class, params, request, options)
|
67
|
-
@operation_class = operation_class
|
68
|
-
@params = params
|
69
|
-
@request = request
|
70
|
-
@is_document = options[:is_document]
|
71
|
-
end
|
72
|
-
|
73
|
-
def call
|
74
|
-
document_body! if @is_document
|
75
|
-
yield @params# Create.run(params)
|
76
|
-
end
|
77
|
-
|
78
|
-
private
|
79
|
-
attr_reader :params, :operation_class, :request
|
80
|
-
|
81
|
-
def document_body!
|
82
|
-
# this is what happens:
|
83
|
-
# respond_with Comment::Update::JSON.run(params.merge(comment: request.body.string))
|
84
|
-
concept_name = operation_class.model_class.to_s.underscore # this could be renamed to ::concept_class soon.
|
85
|
-
request_body = request.body.respond_to?(:string) ? request.body.string : request.body.read
|
86
|
-
|
87
|
-
params.merge!(concept_name => request_body)
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
class Trailblazer::Operation
|
2
|
-
module Present
|
3
|
-
def self.included(includer)
|
4
|
-
includer.extend PresentMethod
|
5
|
-
includer.& Stop, before: Call
|
6
|
-
end
|
7
|
-
|
8
|
-
module PresentMethod
|
9
|
-
def present(params={}, options={}, *args)
|
10
|
-
call(params, options.merge("present.stop?" => true), *args)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
# Stops the pipeline if "present.stop?" is set, which usually happens in Operation::present.
|
16
|
-
Present::Stop = ->(input, options) { ! options["present.stop?"] } # false returns Left.
|
17
|
-
end
|
18
|
-
|
19
|
-
# TODO: another stop for present without the contract!
|