trailblazer 2.0.0.beta1 → 2.0.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- 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!
|