trailblazer-context 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  module Trailblazer
2
- class Context
3
- VERSION = "0.3.0"
2
+ module Context
3
+ VERSION = "0.5.0"
4
4
  end
5
5
  end
@@ -0,0 +1,32 @@
1
+ require 'test_helper'
2
+ require 'benchmark/ips'
3
+
4
+ BenchmarkRepresenter = Struct.new(:benchmark, :times_slower)
5
+
6
+ Minitest::Spec.class_eval do
7
+ def benchmark_ips(records, time: 1, warmup: 1)
8
+ base = records[:base]
9
+ target = records[:target]
10
+
11
+ benchmark = Benchmark.ips do |x|
12
+ x.config(time: time, warmup: warmup)
13
+
14
+ x.report(base[:label], &base[:block])
15
+ x.report(target[:label], &target[:block])
16
+
17
+ x.compare!
18
+ end
19
+
20
+ times_slower = benchmark.data[0][:ips] / benchmark.data[1][:ips]
21
+ BenchmarkRepresenter.new(benchmark, times_slower)
22
+ end
23
+
24
+ def assert_times_slower(result, threshold)
25
+ base = result.benchmark.data[0]
26
+ target = result.benchmark.data[1]
27
+
28
+ msg = "Expected #{target[:name]} to be slower by at most #{threshold} times than #{base[:name]}, but got #{result.times_slower}"
29
+
30
+ assert result.times_slower < threshold, msg
31
+ end
32
+ end
@@ -0,0 +1,89 @@
1
+ require_relative "benchmark_helper"
2
+
3
+ describe "Context::IndifferentAccess Performance" do
4
+ wrapped_options = { model: Object, policy: Hash, representer: String }
5
+ mutable_options = { write: String, read: Integer, delete: Float, merge: Symbol }
6
+ context_options = {
7
+ container_class: Trailblazer::Context::Container,
8
+ replica_class: Trailblazer::Context::Store::IndifferentAccess,
9
+ }
10
+
11
+ default_hash = Hash(**wrapped_options, **mutable_options)
12
+ indifferent_hash = Trailblazer::Context.build(wrapped_options, mutable_options, context_options)
13
+
14
+ it "initialize" do
15
+ result = benchmark_ips(
16
+ base: { label: :initialize_default_hash, block: ->{
17
+ Hash(**wrapped_options, **mutable_options)
18
+ }},
19
+ target: { label: :initialize_indifferent_hash, block: ->{
20
+ Trailblazer::Context.build(wrapped_options, mutable_options, context_options)
21
+ }},
22
+ )
23
+
24
+ assert_times_slower result, 3
25
+ end
26
+
27
+ it "read" do
28
+ result = benchmark_ips(
29
+ base: { label: :read_from_default_hash, block: ->{ default_hash[:read] } },
30
+ target: { label: :read_from_indifferent_hash, block: ->{ indifferent_hash[:read] } },
31
+ )
32
+
33
+ assert_times_slower result, 1.4
34
+ end
35
+
36
+ it "unknown read" do
37
+ result = benchmark_ips(
38
+ base: { label: :unknown_read_from_default_hash, block: ->{ default_hash[:unknown] } },
39
+ target: { label: :unknown_read_from_indifferent_hash, block: ->{ indifferent_hash[:unknown] } },
40
+ )
41
+
42
+ assert_times_slower result, 3.5
43
+ end
44
+
45
+ it "write" do
46
+ result = benchmark_ips(
47
+ base: { label: :write_to_default_hash, block: ->{ default_hash[:write] = "" } },
48
+ target: { label: :write_to_indifferent_hash, block: ->{ indifferent_hash[:write] = "SKU-1" } },
49
+ )
50
+
51
+ assert_times_slower result, 2.3
52
+ end
53
+
54
+ it "delete" do
55
+ result = benchmark_ips(
56
+ base: { label: :delete_from_default_hash, block: ->{ default_hash.delete(:delete) } },
57
+ target: { label: :delete_from_indifferent_hash, block: ->{ indifferent_hash.delete(:delete) } },
58
+ )
59
+
60
+ assert_times_slower result, 2.4
61
+ end
62
+
63
+ it "merge" do
64
+ result = benchmark_ips(
65
+ base: { label: :merge_from_default_hash, block: ->{ default_hash.merge(merge: :object_id) } },
66
+ target: { label: :merge_from_indifferent_hash, block: ->{ indifferent_hash.merge(merge: :object_id) } },
67
+ )
68
+
69
+ assert_times_slower result, 5.55
70
+ end
71
+
72
+ it "to_hash" do
73
+ result = benchmark_ips(
74
+ base: { label: :default_to_hash, block: ->{ default_hash.to_hash } },
75
+ target: { label: :indifferent_to_hash, block: ->{ indifferent_hash.to_hash } },
76
+ )
77
+
78
+ assert_times_slower result, 1.3
79
+ end
80
+
81
+ it "decompose" do
82
+ result = benchmark_ips(
83
+ base: { label: :dup_default_hash, block: ->{ default_hash.to_hash } },
84
+ target: { label: :decompose, block: ->{ indifferent_hash.decompose } },
85
+ )
86
+
87
+ assert_times_slower result, 1.55
88
+ end
89
+ end
@@ -0,0 +1,73 @@
1
+ require_relative "benchmark_helper"
2
+
3
+ describe "Context::Aliasing Performance" do
4
+ wrapped_options = { model: Object, policy: Hash, representer: String }
5
+ mutable_options = { write: String, read: Integer, delete: Float, merge: Symbol }
6
+
7
+ context_options = {
8
+ container_class: Trailblazer::Context::Container::WithAliases,
9
+ replica_class: Trailblazer::Context::Store::IndifferentAccess,
10
+ aliases: { read: :reader }
11
+ }
12
+
13
+ default_hash = Hash(**wrapped_options, **mutable_options)
14
+ aliased_hash = Trailblazer::Context.build(wrapped_options, mutable_options, context_options)
15
+
16
+ it "initialize" do
17
+ result = benchmark_ips(
18
+ base: { label: :initialize_default_hash, block: ->{
19
+ Hash(**wrapped_options, **mutable_options)
20
+ }},
21
+ target: { label: :initialize_aliased_hash, block: ->{
22
+ Trailblazer::Context.build(wrapped_options, mutable_options, context_options)
23
+ }},
24
+ )
25
+
26
+ assert_times_slower result, 8
27
+ end
28
+
29
+ it "write" do
30
+ result = benchmark_ips(
31
+ base: { label: :write_to_default_hash, block: ->{ default_hash[:write] = "" } },
32
+ target: { label: :write_to_aliased_hash, block: ->{ aliased_hash[:write] = "" } },
33
+ )
34
+
35
+ assert_times_slower result, 3.66
36
+ end
37
+
38
+ it "read" do
39
+ result = benchmark_ips(
40
+ base: { label: :read_from_default_hash, block: ->{ default_hash[:read] } },
41
+ target: { label: :read_from_aliased_hash, block: ->{ aliased_hash[:reader] } },
42
+ )
43
+
44
+ assert_times_slower result, 1.5
45
+ end
46
+
47
+ it "delete" do
48
+ result = benchmark_ips(
49
+ base: { label: :delete_from_default_hash, block: ->{ default_hash.delete(:delete) } },
50
+ target: { label: :delete_from_aliased_hash, block: ->{ aliased_hash.delete(:delete) } },
51
+ )
52
+
53
+ assert_times_slower result, 4
54
+ end
55
+
56
+ it "merge" do
57
+ result = benchmark_ips(
58
+ base: { label: :merge_from_default_hash, block: ->{ default_hash.merge(merge: :object_id) } },
59
+ target: { label: :merge_from_aliased_hash, block: ->{ aliased_hash.merge(merge: :object_id) } },
60
+ )
61
+
62
+ assert_times_slower result, 8.5
63
+ end
64
+
65
+ it "to_hash" do
66
+ result = benchmark_ips(
67
+ base: { label: :default_to_hash, block: ->{ default_hash.to_hash } },
68
+ target: { label: :aliased_to_hash, block: ->{ aliased_hash.to_hash } },
69
+ )
70
+
71
+ assert_times_slower result, 1.5
72
+ end
73
+ end
data/test/context_test.rb CHANGED
@@ -19,6 +19,7 @@ class ArgsTest < Minitest::Spec
19
19
 
20
20
  # it { }
21
21
  _(immutable.inspect).must_equal %({:repository=>\"User\"})
22
+ _(ctx.inspect).must_equal %{#<Trailblazer::Context::Container wrapped_options={:repository=>\"User\"} mutable_options={:model=>Module, :contract=>Integer}>}
22
23
  end
23
24
 
24
25
  it "allows false/nil values" do
@@ -51,24 +52,36 @@ class ArgsTest < Minitest::Spec
51
52
 
52
53
  merged = ctx.merge(current_user: Module)
53
54
 
55
+ _(merged.class).must_equal(Trailblazer::Context::Container)
54
56
  _(merged.to_hash).must_equal(repository: "User", current_user: Module)
55
57
  _(ctx.to_hash).must_equal(repository: "User")
56
58
  end
57
59
  end
58
60
 
59
- #-
61
+ describe "Enumerable behaviour" do
62
+ it { _(ctx.each.to_a).must_equal [[:repository, "User"]] }
63
+ it { _(ctx.find{ |k, _| k == :repository }).must_equal [:repository, "User"] }
64
+ it { _(ctx.inject([]){ |r, (k, _)| r << k}).must_equal [:repository] }
65
+ end
66
+
67
+ #- #decompose
60
68
  it do
61
69
  immutable = {repository: "User", model: Module, current_user: Class}
70
+ mutable = {error: RuntimeError}
62
71
 
63
- Trailblazer::Context(immutable) do |_original, mutable|
64
- mutable
65
- end
72
+ _([immutable, mutable]).must_equal Trailblazer::Context(immutable, mutable).decompose
66
73
  end
67
74
  end
68
75
 
69
76
  class ContextWithIndifferentAccessTest < Minitest::Spec
70
77
  it do
71
- flow_options = {}
78
+ flow_options = {
79
+ context_options: {
80
+ container_class: Trailblazer::Context::Container,
81
+ replica_class: Trailblazer::Context::Store::IndifferentAccess
82
+ }
83
+ }
84
+
72
85
  circuit_options = {}
73
86
 
74
87
  immutable = {model: Object, "policy" => Hash}
@@ -108,6 +121,12 @@ class ContextWithIndifferentAccessTest < Minitest::Spec
108
121
  _(ctx["contract.default"]).must_equal Module
109
122
  _(ctx[:"contract.default"]).must_equal Module
110
123
 
124
+ # delete
125
+ ctx[:model] = Object
126
+ ctx.delete 'model'
127
+
128
+ _(ctx.key?(:model)).must_equal false
129
+ _(ctx.key?("model")).must_equal false
111
130
 
112
131
  ctx3 = ctx.merge("result" => false)
113
132
 
@@ -120,12 +139,24 @@ class ContextWithIndifferentAccessTest < Minitest::Spec
120
139
  end
121
140
 
122
141
  it "Aliasable" do
123
- flow_options = {context_alias: {"contract.default" => :contract, "result.default"=>:result, "trace.stack" => :stack}}
142
+ flow_options = {
143
+ context_options: {
144
+ container_class: Trailblazer::Context::Container::WithAliases,
145
+ replica_class: Trailblazer::Context::Store::IndifferentAccess,
146
+ aliases: { "contract.default" => :contract, "result.default"=>:result, "trace.stack" => :stack }
147
+ }
148
+ }
149
+
124
150
  circuit_options = {}
125
151
 
126
152
  immutable = {model: Object, "policy" => Hash}
127
153
 
128
154
  ctx = Trailblazer::Context.for_circuit(immutable, {}, [immutable, flow_options], **circuit_options)
155
+ _(ctx.class).must_equal(Trailblazer::Context::Container::WithAliases)
156
+
157
+ _(ctx.inspect).must_equal %{#<Trailblazer::Context::Container::WithAliases wrapped_options={:model=>Object, \"policy\"=>Hash} mutable_options={} aliases={\"contract.default\"=>:contract, \"result.default\"=>:result, \"trace.stack\"=>:stack}>}
158
+
159
+ _(ctx.to_hash).must_equal(:model=>Object, :policy=>Hash)
129
160
 
130
161
  _(ctx[:model]).must_equal Object
131
162
  _(ctx["model"]).must_equal Object
@@ -141,14 +172,24 @@ class ContextWithIndifferentAccessTest < Minitest::Spec
141
172
  assert_nil ctx["result"]
142
173
 
143
174
  _(ctx[:contract]).must_equal Module
175
+ _(ctx['contract']).must_equal Module
144
176
 
145
177
  assert_nil ctx[:stack]
178
+ assert_nil ctx['stack']
146
179
 
147
180
  # Set an aliased property via setter
148
181
  ctx["trace.stack"] = Object
149
182
  _(ctx[:stack]).must_equal Object
183
+ _(ctx["stack"]).must_equal Object
150
184
  _(ctx["trace.stack"]).must_equal Object
151
185
 
186
+ # Set an aliased property with merge
187
+ ctx["trace.stack"] = String
188
+ merged = ctx.merge(stack: Integer)
189
+
190
+ _(merged.class).must_equal(Trailblazer::Context::Container::WithAliases)
191
+ _(merged.to_hash).must_equal(:model=>Object, :policy=>Hash, :contract=>Module, :"contract.default"=>Module, :stack=>Integer, :"trace.stack"=>Integer)
192
+
152
193
  # key?
153
194
  _(ctx.key?("____contract.default")).must_equal false
154
195
  _(ctx.key?("contract.default")).must_equal true
@@ -159,8 +200,19 @@ class ContextWithIndifferentAccessTest < Minitest::Spec
159
200
  _(ctx.key?("trace.stack")).must_equal true
160
201
  _(ctx.key?(:"trace.stack")).must_equal true
161
202
 
203
+ # delete
204
+ ctx[:result] = Object
205
+ ctx.delete :result
206
+
207
+ _(ctx.key?(:result)).must_equal false
208
+ _(ctx.key?("result")).must_equal false
209
+
210
+ _(ctx.key?(:"result.default")).must_equal false
211
+ _(ctx.key?("result.default")).must_equal false
212
+
213
+
162
214
  # to_hash
163
- _(ctx.to_hash).must_equal(:model=>Object, :policy=>Hash, :"contract.default"=>Module, :"trace.stack"=>Object, :contract=>Module, :stack=>Object)
215
+ _(ctx.to_hash).must_equal(:model=>Object, :policy=>Hash, :contract=>Module, :"contract.default"=>Module, :stack=>String, :"trace.stack"=>String)
164
216
 
165
217
  # context in context
166
218
  ctx2 = Trailblazer::Context.for_circuit(ctx, {}, [ctx, flow_options], **circuit_options)
@@ -188,11 +240,58 @@ class ContextWithIndifferentAccessTest < Minitest::Spec
188
240
  # todo: TEST flow_options={context_class: SomethingElse}
189
241
  end
190
242
 
191
- it ".build provides default args" do
192
- immutable = {model: Object, "policy.default" => Hash}
243
+ it ".build accepts custom container class" do
244
+ MyContainer = Class.new(Trailblazer::Context::Container) do
245
+ def inspect
246
+ %{#<MyContainer wrapped=#{@wrapped_options} mutable=#{@mutable_options}>}
247
+ end
248
+ end
249
+
250
+ immutable = { model: Object }
251
+ options = { container_class: MyContainer, replica_class: Trailblazer::Context::Store::IndifferentAccess }
252
+
253
+ ctx = Trailblazer::Context.build(immutable, {}, **options)
254
+ _(ctx.class).must_equal(MyContainer)
255
+ _(ctx.inspect).must_equal("#<MyContainer wrapped=#{immutable} mutable={}>")
193
256
 
194
- # {Aliasing#initialize}
195
- ctx = Trailblazer::Context::IndifferentAccess.new(immutable, {}, context_alias: {"policy.default" => :policy})
257
+ _(ctx.to_hash).must_equal({ model: Object })
258
+
259
+ ctx[:integer] = Integer
260
+ _(ctx.to_hash).must_equal({ model: Object, integer: Integer })
261
+
262
+ ctx2 = ctx.merge(float: Float)
263
+ _(ctx2.class).must_equal(MyContainer)
264
+
265
+ _(ctx2.to_hash).must_equal({ model: Object, integer: Integer, float: Float })
266
+ end
267
+
268
+ it ".build accepts custom replica class (For example, To opt out from indifferent access)" do
269
+ MyReplica = Class.new(Hash) do
270
+ def initialize(*containers)
271
+ containers.each do |container|
272
+ container.each{ |key, value| self[key] = value }
273
+ end
274
+ end
275
+ end
276
+
277
+ immutable = { model: Object }
278
+ options = { container_class: Trailblazer::Context::Container, replica_class: MyReplica }
279
+
280
+ ctx = Trailblazer::Context.build(immutable, {}, **options)
281
+ ctx[:integer] = Integer
282
+
283
+ _(ctx[:integer]).must_equal(Integer)
284
+ _(ctx['integer']).must_be_nil
285
+ end
286
+
287
+ it "Context() provides default args" do
288
+ immutable = {model: Object, "policy.default" => Hash}
289
+ options = {
290
+ container_class: Trailblazer::Context::Container::WithAliases,
291
+ aliases: { "policy.default" => :policy }
292
+ }
293
+
294
+ ctx = Trailblazer::Context(immutable, {}, options)
196
295
 
197
296
  _(ctx[:model]).must_equal Object
198
297
  _(ctx["model"]).must_equal Object
@@ -206,6 +305,19 @@ class ContextWithIndifferentAccessTest < Minitest::Spec
206
305
  _(ctx2[:policy]).must_equal Hash
207
306
  _(ctx2[:result]).must_equal :success
208
307
  end
308
+
309
+ it "Context() throws RuntimeError if aliases are passed but container_class doesn't support it" do
310
+ immutable = {model: Object, "policy.default" => Hash}
311
+ options = {
312
+ aliases: { "policy.default" => :policy }
313
+ }
314
+
315
+ exception = assert_raises Trailblazer::Context::Container::UseWithAliases do
316
+ Trailblazer::Context(immutable, {}, options)
317
+ end
318
+
319
+ _(exception.message).must_equal %{Pass `Trailblazer::Context::Container::WithAliases` as `container_class` while defining `aliases`}
320
+ end
209
321
  end
210
322
 
211
323
  # TODO: test overriding Context.implementation.
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
 
11
11
  spec.summary = "Argument-specific data structures for Trailblazer."
12
12
  spec.description = "Argument-specific data structures for Trailblazer such as Context, Option and ContainerChain."
13
- spec.homepage = "http://trailblazer.to/gems/workflow"
13
+ spec.homepage = "https://trailblazer.to/"
14
14
  spec.licenses = ["MIT"]
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
@@ -20,6 +20,8 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.require_paths = ["lib"]
22
22
 
23
+ spec.add_dependency "hashie", ">= 3.0.0"
24
+
23
25
  spec.add_development_dependency "bundler"
24
26
  spec.add_development_dependency "minitest"
25
27
  spec.add_development_dependency "rake"
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trailblazer-context
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Sutterer
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-29 00:00:00.000000000 Z
11
+ date: 2021-04-10 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: hashie
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 3.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 3.0.0
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: bundler
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -60,8 +74,8 @@ executables: []
60
74
  extensions: []
61
75
  extra_rdoc_files: []
62
76
  files:
77
+ - ".github/workflows/ci.yml"
63
78
  - ".gitignore"
64
- - ".travis.yml"
65
79
  - CHANGES.md
66
80
  - Gemfile
67
81
  - LICENSE
@@ -70,19 +84,21 @@ files:
70
84
  - lib/trailblazer-context.rb
71
85
  - lib/trailblazer/container_chain.rb
72
86
  - lib/trailblazer/context.rb
73
- - lib/trailblazer/context/aliasing.rb
74
- - lib/trailblazer/context/indifferent_access.rb
87
+ - lib/trailblazer/context/container.rb
88
+ - lib/trailblazer/context/container/with_aliases.rb
89
+ - lib/trailblazer/context/store/indifferent_access.rb
75
90
  - lib/trailblazer/context/version.rb
76
- - lib/trailblazer/option.rb
91
+ - test/benchmark/benchmark_helper.rb
92
+ - test/benchmark/indifferent_access_test.rb
93
+ - test/benchmark/indifferent_access_with_aliasing_test.rb
77
94
  - test/context_test.rb
78
- - test/option_test.rb
79
95
  - test/test_helper.rb
80
96
  - trailblazer-context.gemspec
81
- homepage: http://trailblazer.to/gems/workflow
97
+ homepage: https://trailblazer.to/
82
98
  licenses:
83
99
  - MIT
84
100
  metadata: {}
85
- post_install_message:
101
+ post_install_message:
86
102
  rdoc_options: []
87
103
  require_paths:
88
104
  - lib
@@ -97,11 +113,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
113
  - !ruby/object:Gem::Version
98
114
  version: '0'
99
115
  requirements: []
100
- rubygems_version: 3.0.3
101
- signing_key:
116
+ rubygems_version: 3.0.8
117
+ signing_key:
102
118
  specification_version: 4
103
119
  summary: Argument-specific data structures for Trailblazer.
104
120
  test_files:
121
+ - test/benchmark/benchmark_helper.rb
122
+ - test/benchmark/indifferent_access_test.rb
123
+ - test/benchmark/indifferent_access_with_aliasing_test.rb
105
124
  - test/context_test.rb
106
- - test/option_test.rb
107
125
  - test/test_helper.rb