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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 796e342235352bda7ad6efbfbb4917f016846ff5
4
- data.tar.gz: b7f53417cf1b74c08f8177a92f536e2990675dca
3
+ metadata.gz: c73755bd284523c496d97f90e637048d778a4233
4
+ data.tar.gz: 8c380418456b199836576b6d6cb78611d5e8ca73
5
5
  SHA512:
6
- metadata.gz: e8a5e6fbdb459b89c6dbf7c97d1771691d4b0ca0e259a27fb6318b977de11ba557b38db00f8816e0d671975ba9a5b58a180771e9d2ecad0d7a6d0d69e0f6bf5a
7
- data.tar.gz: f801d455bae0cafa1746c362d435e6ee39b23430836577c908b15904178abc42ba4a3af550ec763ae74328c7042f0bc4ac281bf5d2cf3e16d88ca43978d06bb9
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
- # gem "trailblazer-operation", path: "../operation"
24
- gem "pipetree"#, path: "../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[(https://github.com/trailblazer/trailblazer-operation) and can be used without our stack.
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
 
@@ -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"
@@ -20,7 +20,5 @@ class Trailblazer::Operation
20
20
  DSL = Uber::Builder::DSL
21
21
  end
22
22
 
23
- def self.Builder(*args, &block)
24
- [ Builder, args, block ]
25
- end
23
+ DSL.macro!(:Builder, Builder) # Operation::Builder()
26
24
  end
@@ -40,7 +40,5 @@ class Trailblazer::Operation
40
40
  end
41
41
  end
42
42
 
43
- def self.Callback(*args, &block)
44
- [ Callback, args, block ]
45
- end
43
+ DSL.macro!(:Callback, Callback) # Operation::Callback()
46
44
  end
@@ -6,41 +6,41 @@
6
6
  #
7
7
  # Needs Operation#model.
8
8
  # Needs #[], #[]= skill dependency.
9
- module Trailblazer::Operation::Contract
10
- module Build
11
- # bla build contract at runtime.
12
- def self.build_contract!(operation, options, name:"default", constant:nil, builder: nil)
13
- # TODO: we could probably clean this up a bit at some point.
14
- contract_class = constant || options["contract.#{name}.class"]
15
- model = operation["model"] # FIXME: model.default
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
- return operation["contract.#{name}"] = Uber::Option[builder].(operation, constant: contract_class, model: model) if builder
18
+ return operation["contract.#{name}"] = Uber::Option[builder].(operation, constant: contract_class, model: model) if builder
18
19
 
19
- operation["contract.#{name}"] = contract_class.new(model)
20
- end
20
+ operation["contract.#{name}"] = contract_class.new(model)
21
+ end
21
22
 
22
- def self.import!(operation, import, **args)
23
- import.(:>, ->(operation, options) { build_contract!(operation, options, **args) },
24
- name: "contract.build")
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
- def self.Build(*args, &block)
29
- [ Build, args, block ]
30
- end
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
- module DSL
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
- path, form_class = Trailblazer::DSL::Build.new.({ prefix: :contract, class: base, container: self }, name, constant, block)
40
+ self[path] = form_class
41
+ end
42
+ end # Contract
43
+ end
42
44
 
43
- self[path] = form_class
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(*args, &block)
21
- [ Guard, args, block ]
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
- if import.inheriting? # not sure how to do overrides!
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 Persist
3
- def self.import!(operation, import, options={})
4
- save_method = options[:method] || :save
5
- contract_name = options[:name] || "contract.default"
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
- import.(:&, ->(input, options) { options[contract_name].send(save_method) }, # TODO: test me.
8
- name: "persist.save")
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 policy-compatible object at runtime.
27
- def self.add!(operation, import, options)
28
- name = options[:name] || :default
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
- # configure class level.
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(*args, &block)
39
- [ Pundit, args, block ]
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
@@ -18,7 +18,5 @@ class Trailblazer::Operation
18
18
  # end
19
19
  end
20
20
 
21
- def self.Resolver(*args, &block)
22
- [ Resolver, args, block ]
23
- end
21
+ DSL.macro!(:Resolver, Resolver)
24
22
  end
@@ -1,50 +1,55 @@
1
- module Trailblazer::Operation::Contract
2
- # result.contract = {..}
3
- # result.contract.errors = {..}
4
- # Deviate to left track if optional key is not found in params.
5
- # Deviate to left if validation result falsey.
6
- module Validate
7
- def self.import!(operation, import, skip_extract:false, name: "default", representer:false, **args) # DISCUSS: should we introduce something like Validate::Deserializer?
8
- if representer
9
- skip_extract = true
10
- operation["representer.#{name}.class"] = representer
11
- end
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
- import.(:&, ->(input, options) { extract_params!(input, options, **args) },
14
- name: "validate.params.extract") unless skip_extract
14
+ params_path = "contract.#{name}.params" # extract_params! save extracted params here.
15
15
 
16
- # call the actual contract.validate(params)
17
- # DISCUSS: should we pass the representer here, or do that in #validate! i'm still mulling over what's the best, most generic approach.
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
- def self.extract_params!(operation, options, key:nil, **)
23
- # TODO: introduce nested pipes and pass composed input instead.
24
- options["params.validate"] = key ? options["params"][key] : options["params"]
25
- end
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
- def self.validate!(operation, options, name: nil, representer:false, from: "document", **)
28
- path = "contract.#{name}"
29
- contract = operation[path]
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
- # this is for 1.1-style compatibility and should be removed once we have Deserializer in place:
32
- operation["result.#{path}"] = result =
33
- if representer
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
- result.success?
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
- def self.Validate(*args, &block)
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)
@@ -1,3 +1,3 @@
1
1
  module Trailblazer
2
- VERSION = "2.0.0.beta1"
2
+ VERSION = "2.0.0.beta2"
3
3
  end
@@ -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.validate"] = options["params"][type]
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>] >} }
@@ -22,7 +22,7 @@ class DryContainerTest < Minitest::Spec
22
22
  self.| Model( Song, :new )
23
23
  self.| Contract::Build()
24
24
  self.| Contract::Validate()
25
- self.| Persist( method: :sync )
25
+ self.| Contract::Persist( method: :sync )
26
26
  end
27
27
  #:key end
28
28
 
@@ -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]} }
@@ -27,7 +27,7 @@ class DocsNestedOperationTest < Minitest::Spec
27
27
  class Update < Trailblazer::Operation
28
28
  step Nested( Edit )
29
29
  step Contract::Validate()
30
- step Persist( method: :sync )
30
+ step Contract::Persist( method: :sync )
31
31
  end
32
32
  #:update end
33
33
 
@@ -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
@@ -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
- #- Guard inheritance
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
 
@@ -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
- self.< ->(options) { options["outer-err"] = true }
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.
@@ -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,&validate.params.extract,&contract.validate,&callback.default]} }
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.validate gets set (TODO: change in 2.1)
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.validate"].must_equal({:title=>"SVG"}) }
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,&validate.params.extract,&contract.validate]} }
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
- self.~ Model :update # DISCUSS: do we need the ~ operator?
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,&validate.params.extract,&contract.validate,<PersistTest::Update:17,&persist.save,<PersistTest::Update:19]} }
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.beta1
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-07 00:00:00.000000000 Z
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!