trailblazer-context 0.1.2 → 0.3.1
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 +5 -5
- data/.gitignore +2 -0
- data/.travis.yml +12 -0
- data/CHANGES.md +31 -0
- data/Gemfile +2 -2
- data/LICENSE +1 -1
- data/Rakefile +8 -2
- data/lib/trailblazer-context.rb +1 -0
- data/lib/trailblazer/container_chain.rb +5 -3
- data/lib/trailblazer/context.rb +15 -61
- data/lib/trailblazer/context/container.rb +100 -0
- data/lib/trailblazer/context/container/with_aliases.rb +102 -0
- data/lib/trailblazer/context/store/indifferent_access.rb +36 -0
- data/lib/trailblazer/context/version.rb +2 -2
- data/lib/trailblazer/option.rb +18 -19
- data/test/benchmark/benchmark_helper.rb +32 -0
- data/test/benchmark/indifferent_access_test.rb +89 -0
- data/test/benchmark/indifferent_access_with_aliasing_test.rb +73 -0
- data/test/context_test.rb +323 -0
- data/test/option_test.rb +186 -0
- data/test/test_helper.rb +3 -0
- data/trailblazer-context.gemspec +14 -11
- metadata +49 -20
data/lib/trailblazer/option.rb
CHANGED
@@ -1,22 +1,5 @@
|
|
1
1
|
module Trailblazer
|
2
|
-
# @note This might go to trailblazer-args along with `Context` at some point.
|
3
|
-
def self.Option(proc)
|
4
|
-
Option.build(Option, proc)
|
5
|
-
end
|
6
|
-
|
7
2
|
class Option
|
8
|
-
# Generic builder for a callable "option".
|
9
|
-
# @param call_implementation [Class, Module] implements the process of calling the proc
|
10
|
-
# while passing arguments/options to it in a specific style (e.g. kw args, step interface).
|
11
|
-
# @return [Proc] when called, this proc will evaluate its option (at run-time).
|
12
|
-
def self.build(call_implementation, proc)
|
13
|
-
if proc.is_a? Symbol
|
14
|
-
->(*args, &block) { call_implementation.evaluate_method(proc, *args, &block) }
|
15
|
-
else
|
16
|
-
->(*args, &block) { call_implementation.evaluate_callable(proc, *args, &block) }
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
3
|
# A call implementation invoking `proc.(*args)` and plainly forwarding all arguments.
|
21
4
|
# Override this for your own step strategy (see KW#call!).
|
22
5
|
# @private
|
@@ -27,16 +10,28 @@ module Trailblazer
|
|
27
10
|
# Note that both #evaluate_callable and #evaluate_method drop most of the args.
|
28
11
|
# If you need those, override this class.
|
29
12
|
# @private
|
30
|
-
def self.evaluate_callable(proc, *args,
|
13
|
+
def self.evaluate_callable(proc, *args, **, &block)
|
31
14
|
call!(proc, *args, &block)
|
32
15
|
end
|
33
16
|
|
34
17
|
# Make the context's instance method a "lambda" and reuse #call!.
|
35
18
|
# @private
|
36
|
-
def self.evaluate_method(proc, *args, exec_context:raise("No :exec_context given."),
|
19
|
+
def self.evaluate_method(proc, *args, exec_context: raise("No :exec_context given."), **, &block)
|
37
20
|
call!(exec_context.method(proc), *args, &block)
|
38
21
|
end
|
39
22
|
|
23
|
+
# Generic builder for a callable "option".
|
24
|
+
# @param call_implementation [Class, Module] implements the process of calling the proc
|
25
|
+
# while passing arguments/options to it in a specific style (e.g. kw args, step interface).
|
26
|
+
# @return [Proc] when called, this proc will evaluate its option (at run-time).
|
27
|
+
def self.build(call_implementation, proc)
|
28
|
+
if proc.is_a? Symbol
|
29
|
+
->(*args, &block) { call_implementation.evaluate_method(proc, *args, &block) }
|
30
|
+
else
|
31
|
+
->(*args, &block) { call_implementation.evaluate_callable(proc, *args, &block) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
40
35
|
# Returns a {Proc} that, when called, invokes the `proc` argument with keyword arguments.
|
41
36
|
# This is known as "step (call) interface".
|
42
37
|
#
|
@@ -75,4 +70,8 @@ module Trailblazer
|
|
75
70
|
end
|
76
71
|
end
|
77
72
|
end
|
73
|
+
# @note This might go to trailblazer-args along with `Context` at some point.
|
74
|
+
def self.Option(proc)
|
75
|
+
Option.build(Option, proc)
|
76
|
+
end
|
78
77
|
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
|
@@ -0,0 +1,323 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "trailblazer/container_chain"
|
3
|
+
|
4
|
+
class ArgsTest < Minitest::Spec
|
5
|
+
let(:immutable) { {repository: "User"} }
|
6
|
+
|
7
|
+
let(:ctx) { Trailblazer::Context(immutable) }
|
8
|
+
|
9
|
+
it do
|
10
|
+
ctx = Trailblazer::Context(immutable)
|
11
|
+
|
12
|
+
# it { }
|
13
|
+
#-
|
14
|
+
# options[] and options[]=
|
15
|
+
ctx[:model] = Module
|
16
|
+
ctx[:contract] = Integer
|
17
|
+
_(ctx[:model]) .must_equal Module
|
18
|
+
_(ctx[:contract]).must_equal Integer
|
19
|
+
|
20
|
+
# it { }
|
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}>}
|
23
|
+
end
|
24
|
+
|
25
|
+
it "allows false/nil values" do
|
26
|
+
ctx["x"] = false
|
27
|
+
_(ctx["x"]).must_equal false
|
28
|
+
|
29
|
+
ctx["x"] = nil
|
30
|
+
assert_nil ctx["x"]
|
31
|
+
end
|
32
|
+
|
33
|
+
#- #to_hash
|
34
|
+
it do
|
35
|
+
ctx = Trailblazer::Context(immutable)
|
36
|
+
|
37
|
+
# it { }
|
38
|
+
_(ctx.to_hash).must_equal(repository: "User")
|
39
|
+
|
40
|
+
# last added has precedence.
|
41
|
+
# only symbol keys.
|
42
|
+
# it { }
|
43
|
+
ctx[:a] = Symbol
|
44
|
+
ctx["a"] = String
|
45
|
+
|
46
|
+
_(ctx.to_hash).must_equal(repository: "User", a: String)
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#merge" do
|
50
|
+
it do
|
51
|
+
ctx = Trailblazer::Context(immutable)
|
52
|
+
|
53
|
+
merged = ctx.merge(current_user: Module)
|
54
|
+
|
55
|
+
_(merged.class).must_equal(Trailblazer::Context::Container)
|
56
|
+
_(merged.to_hash).must_equal(repository: "User", current_user: Module)
|
57
|
+
_(ctx.to_hash).must_equal(repository: "User")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
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
|
68
|
+
it do
|
69
|
+
immutable = {repository: "User", model: Module, current_user: Class}
|
70
|
+
mutable = {error: RuntimeError}
|
71
|
+
|
72
|
+
_([immutable, mutable]).must_equal Trailblazer::Context(immutable, mutable).decompose
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class ContextWithIndifferentAccessTest < Minitest::Spec
|
77
|
+
it do
|
78
|
+
flow_options = {
|
79
|
+
context_options: {
|
80
|
+
container_class: Trailblazer::Context::Container,
|
81
|
+
replica_class: Trailblazer::Context::Store::IndifferentAccess
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
85
|
+
circuit_options = {}
|
86
|
+
|
87
|
+
immutable = {model: Object, "policy" => Hash}
|
88
|
+
|
89
|
+
ctx = Trailblazer::Context.for_circuit(immutable, {}, [immutable, flow_options], **circuit_options)
|
90
|
+
|
91
|
+
_(ctx[:model]).must_equal Object
|
92
|
+
_(ctx["model"]).must_equal Object
|
93
|
+
_(ctx[:policy]).must_equal Hash
|
94
|
+
_(ctx["policy"]).must_equal Hash
|
95
|
+
|
96
|
+
ctx["contract.default"] = Module
|
97
|
+
_(ctx["contract.default"]).must_equal Module
|
98
|
+
_(ctx[:"contract.default"]).must_equal Module
|
99
|
+
|
100
|
+
# key?
|
101
|
+
_(ctx.key?("____contract.default")).must_equal false
|
102
|
+
_(ctx.key?("contract.default")).must_equal true
|
103
|
+
_(ctx.key?(:"contract.default")).must_equal true
|
104
|
+
|
105
|
+
# context in context
|
106
|
+
ctx2 = Trailblazer::Context.for_circuit(ctx, {}, [ctx, flow_options], **circuit_options)
|
107
|
+
|
108
|
+
_(ctx2[:model]).must_equal Object
|
109
|
+
_(ctx2["model"]).must_equal Object
|
110
|
+
|
111
|
+
ctx2["contract.default"] = Class
|
112
|
+
_(ctx2["contract.default"]).must_equal Class
|
113
|
+
_(ctx2[:"contract.default"]).must_equal Class
|
114
|
+
|
115
|
+
# key?
|
116
|
+
_(ctx2.key?("contract.default")).must_equal true
|
117
|
+
_(ctx2.key?(:"contract.default")).must_equal true
|
118
|
+
_(ctx2.key?("model")).must_equal true
|
119
|
+
|
120
|
+
# wrapped ctx doesn't change
|
121
|
+
_(ctx["contract.default"]).must_equal Module
|
122
|
+
_(ctx[:"contract.default"]).must_equal Module
|
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
|
130
|
+
|
131
|
+
ctx3 = ctx.merge("result" => false)
|
132
|
+
|
133
|
+
_(ctx3["contract.default"]).must_equal Module
|
134
|
+
_(ctx3[:"contract.default"]).must_equal Module
|
135
|
+
_(ctx3["result"]).must_equal false
|
136
|
+
_(ctx3[:result]).must_equal false
|
137
|
+
_(ctx3.key?("result")).must_equal true
|
138
|
+
_(ctx3.key?(:result)).must_equal true
|
139
|
+
end
|
140
|
+
|
141
|
+
it "Aliasable" do
|
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
|
+
|
150
|
+
circuit_options = {}
|
151
|
+
|
152
|
+
immutable = {model: Object, "policy" => Hash}
|
153
|
+
|
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)
|
160
|
+
|
161
|
+
_(ctx[:model]).must_equal Object
|
162
|
+
_(ctx["model"]).must_equal Object
|
163
|
+
_(ctx[:policy]).must_equal Hash
|
164
|
+
_(ctx["policy"]).must_equal Hash
|
165
|
+
|
166
|
+
ctx["contract.default"] = Module
|
167
|
+
_(ctx["contract.default"]).must_equal Module
|
168
|
+
_(ctx[:"contract.default"]).must_equal Module
|
169
|
+
|
170
|
+
# alias
|
171
|
+
assert_nil ctx[:result]
|
172
|
+
assert_nil ctx["result"]
|
173
|
+
|
174
|
+
_(ctx[:contract]).must_equal Module
|
175
|
+
_(ctx['contract']).must_equal Module
|
176
|
+
|
177
|
+
assert_nil ctx[:stack]
|
178
|
+
assert_nil ctx['stack']
|
179
|
+
|
180
|
+
# Set an aliased property via setter
|
181
|
+
ctx["trace.stack"] = Object
|
182
|
+
_(ctx[:stack]).must_equal Object
|
183
|
+
_(ctx["stack"]).must_equal Object
|
184
|
+
_(ctx["trace.stack"]).must_equal Object
|
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
|
+
|
193
|
+
# key?
|
194
|
+
_(ctx.key?("____contract.default")).must_equal false
|
195
|
+
_(ctx.key?("contract.default")).must_equal true
|
196
|
+
_(ctx.key?(:"contract.default")).must_equal true
|
197
|
+
_(ctx.key?(:contract)).must_equal true
|
198
|
+
_(ctx.key?(:result)).must_equal false
|
199
|
+
_(ctx.key?(:stack)).must_equal true
|
200
|
+
_(ctx.key?("trace.stack")).must_equal true
|
201
|
+
_(ctx.key?(:"trace.stack")).must_equal true
|
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
|
+
|
214
|
+
# to_hash
|
215
|
+
_(ctx.to_hash).must_equal(:model=>Object, :policy=>Hash, :contract=>Module, :"contract.default"=>Module, :stack=>String, :"trace.stack"=>String)
|
216
|
+
|
217
|
+
# context in context
|
218
|
+
ctx2 = Trailblazer::Context.for_circuit(ctx, {}, [ctx, flow_options], **circuit_options)
|
219
|
+
|
220
|
+
_(ctx2.key?("____contract.default")).must_equal false
|
221
|
+
_(ctx2.key?("contract.default")).must_equal true
|
222
|
+
_(ctx2.key?(:"contract.default")).must_equal true
|
223
|
+
_(ctx2.key?(:contract)).must_equal true
|
224
|
+
_(ctx2.key?(:result)).must_equal false
|
225
|
+
_(ctx2.key?("result.default")).must_equal false
|
226
|
+
_(ctx2.key?(:stack)).must_equal true
|
227
|
+
_(ctx2.key?("trace.stack")).must_equal true
|
228
|
+
_(ctx2.key?(:"trace.stack")).must_equal true
|
229
|
+
|
230
|
+
# Set aliased in new context via setter
|
231
|
+
ctx2["result.default"] = Class
|
232
|
+
|
233
|
+
_(ctx2[:result]).must_equal Class
|
234
|
+
_(ctx2[:"result.default"]).must_equal Class
|
235
|
+
|
236
|
+
_(ctx2.key?("result.default")).must_equal true
|
237
|
+
_(ctx2.key?(:"result.default")).must_equal true
|
238
|
+
_(ctx2.key?(:result)).must_equal true
|
239
|
+
|
240
|
+
# todo: TEST flow_options={context_class: SomethingElse}
|
241
|
+
end
|
242
|
+
|
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={}>")
|
256
|
+
|
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)
|
295
|
+
|
296
|
+
_(ctx[:model]).must_equal Object
|
297
|
+
_(ctx["model"]).must_equal Object
|
298
|
+
_(ctx[:policy]).must_equal Hash
|
299
|
+
|
300
|
+
ctx2 = ctx.merge(result: :success)
|
301
|
+
|
302
|
+
|
303
|
+
_(ctx2[:model]).must_equal Object
|
304
|
+
_(ctx2["model"]).must_equal Object
|
305
|
+
_(ctx2[:policy]).must_equal Hash
|
306
|
+
_(ctx2[:result]).must_equal :success
|
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
|
321
|
+
end
|
322
|
+
|
323
|
+
# TODO: test overriding Context.implementation.
|