trailblazer 2.0.2 → 2.0.3

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: 239150501ea57b15601c6e0a79ea03f383b0a0c8
4
- data.tar.gz: 8bae46c25cd0f90ca228020c9e0344ddfb0bcaea
3
+ metadata.gz: a1f0c47cf26109508aa2f58b38a69f0a4b5f9b07
4
+ data.tar.gz: 98cddf35489a1ea61a95e01237d459682d9ab3df
5
5
  SHA512:
6
- metadata.gz: a347bb971628f17168ba9d4ba826939da82d0a5bc536e837bbc97a413e64244526ac65de1be1c3da3e9054a5b2acd98e5481cca542aebca35c272e87af3e7629
7
- data.tar.gz: a9fd02ed273e2ab581a053b1b3772a1035cef6e2e8f7f9810f0d7d32112c41c335bbae1e928547d4601f66378f59ac30c42a69b2b8ec5f2d07843399867883fa
6
+ metadata.gz: 60ef0796bcfa0c12255595c47b514f4a0cbfbac1d90091172459cd4a23b7250d444bff6ecb90d74504c216bebca893bab5cfbe6643d9779ecddd0f7bfc093f2f
7
+ data.tar.gz: 212e3134780b3146cd25e713b5b83beadd4219f291a772e31cdcb766b745fd334ec765b01f70b085c9fa999ca0e062ae7cff88064a86b65139e73d257241f2d2
data/CHANGES.md CHANGED
@@ -1,3 +1,10 @@
1
+ # 2.0.3
2
+
3
+ * `Guard` now allows kw args for its option.
4
+ * Fix a bug where `Nested( ->{} )` wouldn't `_call` the nested operation and did too much work on re-nested the already nested params. Thanks to @eliranf for spotting this.
5
+ * Add `Nested(..., input: )` to dynamically decide the input to the nested operation. http://trailblazer.to/gems/operation/2.0/api.html#nested-input
6
+ * Add `Nested(..., output: )`: http://localhost:4000/gems/operation/2.0/api.html#nested-output
7
+
1
8
  # 2.0.2
2
9
 
3
10
  * Remove `uber` dependency as we use our own `Option::KW` now.
@@ -8,7 +8,7 @@ class Trailblazer::Operation
8
8
 
9
9
  module Guard
10
10
  def self.build(callable)
11
- value = Option.(callable) # Operation::Option
11
+ value = Option::KW.(callable) # Operation::Option
12
12
 
13
13
  # this gets wrapped in a Operation::Result object.
14
14
  ->(input, options) { Result.new( !!value.(input, options), {} ) }
@@ -1,32 +1,107 @@
1
1
  class Trailblazer::Operation
2
- def self.Nested(step)
3
- step = Nested.for(step)
2
+ def self.Nested(step, input:nil, output:nil)
3
+ step = Nested.for(step, input, output)
4
4
 
5
- [ step, {} ]
5
+ [ step, { name: "Nested(#{step})" } ]
6
6
  end
7
7
 
8
- module Nested
9
- def self.proc_for(step)
10
- if step.is_a?(Class) && step <= Trailblazer::Operation # interestingly, with < we get a weird nil exception. bug in Ruby?
11
- proc = ->(input, options) { step._call(*options.to_runtime_data) }
12
- else
13
- option = Option::KW.(step)
14
- proc = ->(input, options) { option.(input, options).(*options.to_runtime_data) }
8
+ # WARNING: this is experimental API, but it will end up with something like that.
9
+ module Element
10
+ # DISCUSS: add builders here.
11
+ def initialize(wrapped=nil)
12
+ @wrapped = wrapped
13
+ end
14
+
15
+ module Dynamic
16
+ def initialize(wrapped)
17
+ @wrapped = Option::KW.(wrapped)
15
18
  end
16
19
  end
20
+ end
17
21
 
22
+ module Nested
18
23
  # Please note that the instance_variable_get are here on purpose since the
19
24
  # superinternal API is not entirely decided, yet.
20
- def self.for(step)
21
- proc = proc_for(step)
25
+ def self.for(step, input, output) # DISCUSS: use builders here?
26
+ invoker = Caller::Dynamic.new(step)
27
+ invoker = Caller.new(step) if step.is_a?(Class) && step <= Trailblazer::Operation # interestingly, with < we get a weird nil exception. bug in Ruby?
28
+
29
+ options_for_nested = Options.new
30
+ options_for_nested = Options::Dynamic.new(input) if input
31
+
32
+ options_for_composer = Options::Output.new
33
+ options_for_composer = Options::Output::Dynamic.new(output) if output
34
+
35
+ # This lambda is the strut added on the track, executed at runtime.
36
+ ->(operation, options) do
37
+ result = invoker.(operation, options, options_for_nested.(operation, options)) # TODO: what about containers?
22
38
 
23
- ->(input, options) do
24
- result = proc.(input, options) # TODO: what about containers?
39
+ options_for_composer.(operation, options, result).each { |k,v| options[k] = v }
25
40
 
26
- result.instance_variable_get(:@data).to_mutable_data.each { |k,v| options[k] = v }
27
41
  result.success? # DISCUSS: what if we could simply return the result object here?
28
42
  end
29
43
  end
44
+
45
+ # Is executed at runtime and calls the nested operation.
46
+ class Caller
47
+ include Element
48
+
49
+ def call(input, options, options_for_nested)
50
+ call_nested(nested(input, options), options_for_nested)
51
+ end
52
+
53
+ private
54
+ def call_nested(operation, options)
55
+ operation._call(options)
56
+ end
57
+
58
+ def nested(*); @wrapped end
59
+
60
+ class Dynamic < Caller
61
+ include Element::Dynamic
62
+
63
+ def nested(input, options)
64
+ @wrapped.(input, options)
65
+ end
66
+ end
67
+ end
68
+
69
+ class Options
70
+ include Element
71
+
72
+ # Per default, only runtime data for nested operation.
73
+ def call(input, options)
74
+ options.to_runtime_data[0]
75
+ end
76
+
77
+ class Dynamic
78
+ include Element::Dynamic
79
+
80
+ def call(operation, options)
81
+ @wrapped.(operation, options, runtime_data: options.to_runtime_data[0], mutable_data: options.to_mutable_data )
82
+ end
83
+ end
84
+
85
+ class Output
86
+ include Element
87
+
88
+ def call(input, options, result)
89
+ mutable_data_for(result).each { |k,v| options[k] = v }
90
+ end
91
+
92
+ def mutable_data_for(result)
93
+ result.instance_variable_get(:@data).to_mutable_data
94
+ end
95
+
96
+ class Dynamic < Output
97
+ include Element::Dynamic
98
+
99
+ def call(input, options, result)
100
+ @wrapped.(input, options, mutable_data: mutable_data_for(result))
101
+ end
102
+ end
103
+ end
104
+ end
30
105
  end
31
106
  end
32
107
 
@@ -1,3 +1,3 @@
1
1
  module Trailblazer
2
- VERSION = "2.0.2"
2
+ VERSION = "2.0.3"
3
3
  end
@@ -3,11 +3,21 @@ require "test_helper"
3
3
  #--
4
4
  # with proc
5
5
  class DocsGuardProcTest < Minitest::Spec
6
+ # test without KWs, only options.
7
+ class Update < Trailblazer::Operation
8
+ step Policy::Guard( ->(options) { options["params"][:pass] } )
9
+ step ->(options, *) { options["x"] = true }
10
+ end
11
+
12
+ it { Update.(pass: false)["x"].must_equal nil }
13
+ it { Update.(pass: true)["x"].must_equal true }
14
+ # TODO: test excp when current_user not available
15
+
6
16
  #:proc
7
17
  class Create < Trailblazer::Operation
8
- step Policy::Guard( ->(options) { options["params"][:pass] } )
9
- step :process
18
+ step Policy::Guard( ->(options, params:, **) { params[:pass] } )
10
19
  #~pipeonly
20
+ step :process
11
21
 
12
22
  def process(*)
13
23
  self["x"] = true
@@ -29,7 +39,7 @@ class DocsGuardProcTest < Minitest::Spec
29
39
  #---
30
40
  #- Guard inheritance
31
41
  class New < Create
32
- step Policy::Guard( ->(options) { options["current_user"] } ), override: true
42
+ step Policy::Guard( ->(options, current_user:, **) { current_user } ), override: true
33
43
  end
34
44
 
35
45
  it { New["pipetree"].inspect.must_equal %{[>operation.new,>policy.default.eval,>process]} }
@@ -42,8 +52,8 @@ class DocsGuardTest < Minitest::Spec
42
52
  class MyGuard
43
53
  include Uber::Callable
44
54
 
45
- def call(options)
46
- options["params"][:pass]
55
+ def call(options, params:, **)
56
+ params[:pass]
47
57
  end
48
58
  end
49
59
  #:callable end
@@ -51,8 +61,8 @@ class DocsGuardTest < Minitest::Spec
51
61
  #:callable-op
52
62
  class Create < Trailblazer::Operation
53
63
  step Policy::Guard( MyGuard.new )
54
- step :process
55
64
  #~pipe-only
65
+ step :process
56
66
  def process(*); self[:x] = true; end
57
67
  #~pipe-only end
58
68
  end
@@ -68,11 +78,14 @@ class DocsGuardMethodTest < Minitest::Spec
68
78
  #:method
69
79
  class Create < Trailblazer::Operation
70
80
  step Policy::Guard( :pass? )
81
+
82
+ def pass?(options, params:, **)
83
+ params[:pass]
84
+ end
85
+ #~pipe-onlyy
71
86
  step :process
72
- #~pipe-only
73
- def pass?(options, *); options["params"][:pass] end
74
87
  def process(*); self["x"] = true; end
75
- #~pipe-only end
88
+ #~pipe-onlyy end
76
89
  end
77
90
  #:method end
78
91
 
@@ -85,12 +98,12 @@ end
85
98
  class DocsGuardNamedTest < Minitest::Spec
86
99
  #:name
87
100
  class Create < Trailblazer::Operation
88
- step Policy::Guard( ->(options) { options["current_user"] }, name: :user )
101
+ step Policy::Guard( ->(options, current_user:, **) { current_user }, name: :user )
89
102
  # ...
90
103
  end
91
104
  #:name end
92
105
 
93
- it { Create.()["result.policy.user"].success?.must_equal false }
106
+ it { Create.({}, "current_user" => nil )["result.policy.user"].success?.must_equal false }
94
107
  it { Create.({}, "current_user" => Module)["result.policy.user"].success?.must_equal true }
95
108
 
96
109
  it {
@@ -101,30 +114,12 @@ class DocsGuardNamedTest < Minitest::Spec
101
114
  }
102
115
  end
103
116
 
104
- #---
105
- # class-level guard
106
- class DocsGuardClassLevelTest < Minitest::Spec
107
- #:class-level
108
- class Create < Trailblazer::Operation
109
- step Policy::Guard( ->(options) { options["current_user"] == Module } ),
110
- before: "operation.new"
111
- #~pipe--only
112
- step ->(options) { options["x"] = true }
113
- #~pipe--only end
114
- end
115
- #:class-level end
116
-
117
- it { Create.(); Create["result.policy"].must_equal nil }
118
- it { Create.({}, "current_user" => Module)["x"].must_equal true }
119
- it { Create.({} )["x"].must_equal nil }
120
- end
121
-
122
117
  #---
123
118
  # dependency injection
124
119
  class DocsGuardInjectionTest < Minitest::Spec
125
120
  #:di-op
126
121
  class Create < Trailblazer::Operation
127
- step Policy::Guard( ->(options) { options["current_user"] == Module } )
122
+ step Policy::Guard( ->(options, current_user:, **) { current_user == Module } )
128
123
  end
129
124
  #:di-op end
130
125
 
@@ -140,6 +135,35 @@ class DocsGuardInjectionTest < Minitest::Spec
140
135
  result.inspect("").must_equal %{<Result:false [nil] >} }
141
136
  end
142
137
 
138
+ #---
139
+ # missing current_user throws exception
140
+ class DocsGuardMissingKeywordTest < Minitest::Spec
141
+ class Create < Trailblazer::Operation
142
+ step Policy::Guard( ->(options, current_user:, **) { current_user == Module } )
143
+ end
144
+
145
+ it { assert_raises(ArgumentError) { Create.() } }
146
+ it { Create.({}, "current_user" => Module).success?.must_equal true }
147
+ end
143
148
 
144
- # TODO:
145
- #policy.default
149
+ #---
150
+ # before:
151
+ class DocsGuardPositionTest < Minitest::Spec
152
+ #:before
153
+ class Create < Trailblazer::Operation
154
+ step :model!
155
+ step Policy::Guard( :authorize! ),
156
+ before: :model!
157
+ end
158
+ #:before end
159
+
160
+ it { Create["pipetree"].inspect.must_equal %{[>operation.new,>policy.default.eval,>model!]} }
161
+ it do
162
+ #:before-pipe
163
+ puts Create["pipetree"].inspect(style: :rows) #=>
164
+ # 0 ========================>operation.new
165
+ # 1 ==================>policy.default.eval
166
+ # 2 ===============================>model!
167
+ #:before-pipe end
168
+ end
169
+ end
@@ -71,6 +71,7 @@ class DocsNestedOperationTest < Minitest::Spec
71
71
  Update.(id: 2).inspect("model").must_equal %{<Result:false [nil] >}
72
72
  end
73
73
 
74
+ #---
74
75
  #- shared data
75
76
  class B < Trailblazer::Operation
76
77
  success ->(options) { options["can.B.see.it?"] = options["this.should.not.be.visible.in.B"] }
@@ -82,7 +83,7 @@ class DocsNestedOperationTest < Minitest::Spec
82
83
  self["A.class.data"] = true
83
84
 
84
85
  success ->(options) { options["this.should.not.be.visible.in.B"] = true }
85
- step Nested B
86
+ step Nested( B )
86
87
  end
87
88
 
88
89
  # mutual data from A doesn't bleed into B.
@@ -97,6 +98,103 @@ class DocsNestedOperationTest < Minitest::Spec
97
98
  # cr_result = Create.({}, "result" => result)
98
99
  # puts cr_result["model"]
99
100
  # puts cr_result["contract.default"]
101
+
102
+ #---
103
+ #- Nested( .., input: )
104
+ class C < Trailblazer::Operation
105
+ self["C.class.data"] = true
106
+
107
+ success ->(options) { options["this.should.not.be.visible.in.B"] = true }
108
+
109
+ step Nested( B, input: ->(options, runtime_data:, mutable_data:, **) {
110
+ runtime_data.merge( "this.should.not.be.visible.in.B" => mutable_data["this.should.not.be.visible.in.B"] )
111
+ } )
112
+ end
113
+
114
+ it { C.()["can.B.see.it?"].must_equal true }
115
+ it { C.()["this.should.not.be.visible.in.B"].must_equal true } # this IS visible since we use :input!
116
+ it { C.({}, "current_user" => Module)["can.B.see.current_user?"].must_equal Module }
117
+ it { C.()["can.B.see.A.class.data?"].must_equal nil }
118
+ end
119
+
120
+ class NestedInput < Minitest::Spec
121
+ #:input-multiply
122
+ class Multiplier < Trailblazer::Operation
123
+ step ->(options, x:, y:, **) { options["product"] = x*y }
124
+ end
125
+ #:input-multiply end
126
+
127
+ #:input-pi
128
+ class MultiplyByPi < Trailblazer::Operation
129
+ step ->(options) { options["pi_constant"] = 3.14159 }
130
+ step Nested( Multiplier, input: ->(options, mutable_data:, runtime_data:, **) do
131
+ { "y" => mutable_data["pi_constant"],
132
+ "x" => runtime_data["x"] }
133
+ end )
134
+ end
135
+ #:input-pi end
136
+
137
+ it { MultiplyByPi.({}, "x" => 9).inspect("product").must_equal %{<Result:true [28.27431] >} }
138
+
139
+ it do
140
+ #:input-result
141
+ result = MultiplyByPi.({}, "x" => 9)
142
+ result["product"] #=> [28.27431]
143
+ #:input-result end
144
+ end
145
+ end
146
+
147
+ class NestedInputCallable < Minitest::Spec
148
+ Multiplier = NestedInput::Multiplier
149
+
150
+ #:input-callable
151
+ class MyInput
152
+ extend Uber::Callable
153
+
154
+ def self.call(options, mutable_data:, runtime_data:, **)
155
+ {
156
+ "y" => mutable_data["pi_constant"],
157
+ "x" => runtime_data["x"]
158
+ }
159
+ end
160
+ end
161
+ #:input-callable end
162
+
163
+ #:input-callable-op
164
+ class MultiplyByPi < Trailblazer::Operation
165
+ step ->(options) { options["pi_constant"] = 3.14159 }
166
+ step Nested( Multiplier, input: MyInput )
167
+ end
168
+ #:input-callable-op end
169
+
170
+ it { MultiplyByPi.({}, "x" => 9).inspect("product").must_equal %{<Result:true [28.27431] >} }
171
+ end
172
+
173
+ #---
174
+ #- Nested( .., output: )
175
+ class NestedOutput < Minitest::Spec
176
+ Edit = DocsNestedOperationTest::Edit
177
+
178
+ #:output
179
+ class Update < Trailblazer::Operation
180
+ step Nested( Edit, output: ->(options, mutable_data:, **) do
181
+ {
182
+ "contract.my" => mutable_data["contract.default"],
183
+ "model" => mutable_data["model"]
184
+ }
185
+ end )
186
+ step Contract::Validate( name: "my" )
187
+ step Contract::Persist( method: :sync, name: "my" )
188
+ end
189
+ #:output end
190
+
191
+ it { Update.( id: 1, title: "Call It A Night" ).inspect("model", "contract.default").must_equal %{<Result:true [#<struct DocsNestedOperationTest::Song id=1, title=\"Call It A Night\">, nil] >} }
192
+
193
+ it do
194
+ result = Update.( id: 1, title: "Call It A Night" )
195
+
196
+ result["model"] #=> #<Song id=1 , title=\"Call It A Night\">
197
+ end
100
198
  end
101
199
 
102
200
  class NestedClassLevelTest < Minitest::Spec
@@ -107,7 +205,7 @@ class NestedClassLevelTest < Minitest::Spec
107
205
  end
108
206
 
109
207
  class Create < Trailblazer::Operation
110
- step Nested New
208
+ step Nested( New )
111
209
  step ->(options) { options["y"] = true }
112
210
  end
113
211
  #:class-level end
@@ -116,10 +214,13 @@ class NestedClassLevelTest < Minitest::Spec
116
214
  it { Create.(); Create["class"].must_equal nil }
117
215
  end
118
216
 
217
+ #---
218
+ # Nested( ->{} )
119
219
  class NestedWithCallableTest < Minitest::Spec
120
220
  Song = Struct.new(:id, :title)
121
221
 
122
222
  class X < Trailblazer::Operation
223
+ step ->(options, params:, **) { options["params.original"] = params }
123
224
  step ->(options) { options["x"] = true }
124
225
  end
125
226
 
@@ -132,7 +233,7 @@ class NestedWithCallableTest < Minitest::Spec
132
233
  step Nested( ->(options, *) { options["class"] } )
133
234
  end
134
235
 
135
- it { A.({}, "class" => X).inspect("x", "y", "z").must_equal "<Result:true [true, nil, true] >" }
236
+ it { A.({ a: 1 }, "class" => X).inspect("x", "y", "z", "params.original").must_equal "<Result:true [true, nil, true, {:a=>1}] >" }
136
237
  it { A.({}, "class" => Y).inspect("x", "y", "z").must_equal "<Result:true [nil, true, true] >" }
137
238
  # it { Create.({}).inspect("x", "y", "z").must_equal "<Result:true [nil, true, true] >" }
138
239
 
@@ -177,6 +278,7 @@ class NestedWithCallableTest < Minitest::Spec
177
278
  it { Create.({}, "current_user" => anonymous).inspect("x").must_equal %{<Result:true [true] >} }
178
279
  it { Create.({}, "current_user" => admin) .inspect("x").must_equal %{<Result:true [nil] >} }
179
280
 
281
+ #---
180
282
  #:method
181
283
  class Update < Trailblazer::Operation
182
284
  step Nested( :build! )
@@ -190,6 +292,7 @@ class NestedWithCallableTest < Minitest::Spec
190
292
  it { Update.({}, "current_user" => anonymous).inspect("x").must_equal %{<Result:true [true] >} }
191
293
  it { Update.({}, "current_user" => admin) .inspect("x").must_equal %{<Result:true [nil] >} }
192
294
 
295
+ #---
193
296
  #:callable-builder
194
297
  class MyBuilder
195
298
  extend Uber::Callable
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.2
4
+ version: 2.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Sutterer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-21 00:00:00.000000000 Z
11
+ date: 2017-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: trailblazer-operation