trailblazer 2.0.2 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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