trailblazer-context 0.2.0 → 0.4.0
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 +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +10 -5
- data/CHANGES.md +40 -0
- data/Gemfile +0 -1
- data/LICENSE +1 -1
- data/Rakefile +5 -2
- data/lib/trailblazer-context.rb +1 -0
- data/lib/trailblazer/container_chain.rb +0 -2
- data/lib/trailblazer/context.rb +13 -84
- 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 +26 -57
- 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 +201 -91
- data/test/option_test.rb +34 -73
- data/test/test_helper.rb +0 -1
- data/trailblazer-context.gemspec +4 -3
- metadata +21 -17
- data/.rubocop-https---raw-githubusercontent-com-trailblazer-meta-master-rubocop-yml +0 -136
- data/.rubocop.yml +0 -40
- data/lib/trailblazer/context/aliasing.rb +0 -36
- data/lib/trailblazer/context/indifferent_access.rb +0 -35
@@ -0,0 +1,36 @@
|
|
1
|
+
require "hashie"
|
2
|
+
|
3
|
+
module Trailblazer
|
4
|
+
module Context
|
5
|
+
module Store
|
6
|
+
# Simple yet indifferently accessible hash store, used as replica in Context::Container.
|
7
|
+
# It maintains cache for multiple hashes (wrapped_options, mutable_options etc).
|
8
|
+
class IndifferentAccess < Hash
|
9
|
+
include Hashie::Extensions::IndifferentAccess
|
10
|
+
|
11
|
+
def initialize(hashes)
|
12
|
+
hashes.each do |hash|
|
13
|
+
hash.each do |key, value|
|
14
|
+
self[key] = value
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Override of Hashie::Extensions::IndifferentAccess#indifferent_value
|
20
|
+
# to not do deep indifferent access conversion.
|
21
|
+
# DISCUSS: Should we make this configurable ?
|
22
|
+
def indifferent_value(value)
|
23
|
+
value
|
24
|
+
end
|
25
|
+
|
26
|
+
# Override of Hashie::Extensions::IndifferentAccess#convert_key
|
27
|
+
# to store keys as Symbol by default instead of String.
|
28
|
+
# Why ? We need to pass `ctx` as keyword arguments most of the time.
|
29
|
+
def convert_key(key)
|
30
|
+
return key if Symbol === key
|
31
|
+
String === key ? key.to_sym : key
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/trailblazer/option.rb
CHANGED
@@ -1,78 +1,47 @@
|
|
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
|
23
|
-
def self.call!(proc, *args, &block)
|
24
|
-
|
6
|
+
def self.call!(proc, *args, keyword_arguments: {}, **, &block)
|
7
|
+
# {**keyword_arguments} gets removed automatically if it's an empty hash.
|
8
|
+
# DISCUSS: is this a good practice?
|
9
|
+
proc.(*args, **keyword_arguments, &block)
|
25
10
|
end
|
26
11
|
|
27
12
|
# Note that both #evaluate_callable and #evaluate_method drop most of the args.
|
28
13
|
# If you need those, override this class.
|
29
14
|
# @private
|
30
|
-
def self.evaluate_callable(proc, *args, **
|
31
|
-
call!(proc, *args, &block)
|
15
|
+
def self.evaluate_callable(proc, *args, **options, &block)
|
16
|
+
call!(proc, *args, **options, &block)
|
32
17
|
end
|
33
18
|
|
34
19
|
# Make the context's instance method a "lambda" and reuse #call!.
|
35
20
|
# @private
|
36
|
-
def self.evaluate_method(proc, *args, exec_context: raise("No :exec_context given."), **
|
37
|
-
call!(exec_context.method(proc), *args, &block)
|
21
|
+
def self.evaluate_method(proc, *args, exec_context: raise("No :exec_context given."), **options, &block)
|
22
|
+
call!(exec_context.method(proc), *args, **options, &block)
|
38
23
|
end
|
39
24
|
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
# Alternatively, you can pass a symbol and an `:exec_context`.
|
52
|
-
#
|
53
|
-
# my_proc = :some_method
|
54
|
-
# task = Trailblazer::Option::KW(my_proc)
|
55
|
-
#
|
56
|
-
# class A
|
57
|
-
# def some_method(options, **kws)
|
58
|
-
# options["i got called"] = true
|
59
|
-
# end
|
60
|
-
# end
|
61
|
-
#
|
62
|
-
# task.(options = {}, exec_context: A.new)
|
63
|
-
# options["i got called"] #=> true
|
64
|
-
def self.KW(proc)
|
65
|
-
Option.build(KW, proc)
|
25
|
+
# Generic builder for a callable "option".
|
26
|
+
# @param call_implementation [Class, Module] implements the process of calling the proc
|
27
|
+
# while passing arguments/options to it in a specific style (e.g. kw args, step interface).
|
28
|
+
# @return [Proc] when called, this proc will evaluate its option (at run-time).
|
29
|
+
def self.build(proc)
|
30
|
+
if proc.is_a? Symbol
|
31
|
+
->(*args, **kws, &block) { Option.evaluate_method(proc, *args, **kws, &block) }
|
32
|
+
else
|
33
|
+
->(*args, **kws, &block) {
|
34
|
+
Option.evaluate_callable(proc, *args, **kws, &block) }
|
35
|
+
end
|
66
36
|
end
|
67
37
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
# your_code.(options, **options)
|
72
|
-
# @private
|
73
|
-
def self.call!(proc, options, *)
|
74
|
-
proc.(options, **options.to_hash) # Step interface: (options, **)
|
75
|
-
end
|
38
|
+
def self.KW(proc)
|
39
|
+
raise "The `Option::KW()` method has been removed in trailblazer-context-0.4.
|
40
|
+
Please use `Option(task, keyword_arguments: {...})` instead. Check https://trailblazer.to/2.1/docs/trailblazer.html#trailblazer-context-option"
|
76
41
|
end
|
77
42
|
end
|
43
|
+
# @note This might go to trailblazer-args along with `Context` at some point.
|
44
|
+
def self.Option(proc)
|
45
|
+
Option.build(proc)
|
46
|
+
end
|
78
47
|
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
@@ -2,8 +2,6 @@ require "test_helper"
|
|
2
2
|
require "trailblazer/container_chain"
|
3
3
|
|
4
4
|
class ArgsTest < Minitest::Spec
|
5
|
-
Context = Trailblazer::Context
|
6
|
-
|
7
5
|
let(:immutable) { {repository: "User"} }
|
8
6
|
|
9
7
|
let(:ctx) { Trailblazer::Context(immutable) }
|
@@ -16,16 +14,17 @@ class ArgsTest < Minitest::Spec
|
|
16
14
|
# options[] and options[]=
|
17
15
|
ctx[:model] = Module
|
18
16
|
ctx[:contract] = Integer
|
19
|
-
ctx[:model] .must_equal Module
|
20
|
-
ctx[:contract].must_equal Integer
|
17
|
+
_(ctx[:model]) .must_equal Module
|
18
|
+
_(ctx[:contract]).must_equal Integer
|
21
19
|
|
22
20
|
# it { }
|
23
|
-
immutable.inspect.must_equal %({:repository=>\"User\"})
|
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}>}
|
24
23
|
end
|
25
24
|
|
26
25
|
it "allows false/nil values" do
|
27
26
|
ctx["x"] = false
|
28
|
-
ctx["x"].must_equal false
|
27
|
+
_(ctx["x"]).must_equal false
|
29
28
|
|
30
29
|
ctx["x"] = nil
|
31
30
|
assert_nil ctx["x"]
|
@@ -36,7 +35,7 @@ class ArgsTest < Minitest::Spec
|
|
36
35
|
ctx = Trailblazer::Context(immutable)
|
37
36
|
|
38
37
|
# it { }
|
39
|
-
ctx.to_hash.must_equal(repository: "User")
|
38
|
+
_(ctx.to_hash).must_equal(repository: "User")
|
40
39
|
|
41
40
|
# last added has precedence.
|
42
41
|
# only symbol keys.
|
@@ -44,7 +43,7 @@ class ArgsTest < Minitest::Spec
|
|
44
43
|
ctx[:a] = Symbol
|
45
44
|
ctx["a"] = String
|
46
45
|
|
47
|
-
ctx.to_hash.must_equal(repository: "User", a: String)
|
46
|
+
_(ctx.to_hash).must_equal(repository: "User", a: String)
|
48
47
|
end
|
49
48
|
|
50
49
|
describe "#merge" do
|
@@ -53,160 +52,271 @@ class ArgsTest < Minitest::Spec
|
|
53
52
|
|
54
53
|
merged = ctx.merge(current_user: Module)
|
55
54
|
|
56
|
-
merged.
|
57
|
-
|
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
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
-
|
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
|
62
68
|
it do
|
63
69
|
immutable = {repository: "User", model: Module, current_user: Class}
|
70
|
+
mutable = {error: RuntimeError}
|
64
71
|
|
65
|
-
Trailblazer::Context(immutable
|
66
|
-
mutable
|
67
|
-
end
|
72
|
+
_([immutable, mutable]).must_equal Trailblazer::Context(immutable, mutable).decompose
|
68
73
|
end
|
69
74
|
end
|
70
75
|
|
71
76
|
class ContextWithIndifferentAccessTest < Minitest::Spec
|
72
77
|
it do
|
73
|
-
flow_options = {
|
78
|
+
flow_options = {
|
79
|
+
context_options: {
|
80
|
+
container_class: Trailblazer::Context::Container,
|
81
|
+
replica_class: Trailblazer::Context::Store::IndifferentAccess
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
74
85
|
circuit_options = {}
|
75
86
|
|
76
87
|
immutable = {model: Object, "policy" => Hash}
|
77
88
|
|
78
|
-
ctx = Trailblazer::Context.for_circuit(immutable, {}, [immutable, flow_options], circuit_options)
|
89
|
+
ctx = Trailblazer::Context.for_circuit(immutable, {}, [immutable, flow_options], **circuit_options)
|
79
90
|
|
80
|
-
ctx[:model].must_equal Object
|
81
|
-
ctx["model"].must_equal Object
|
82
|
-
ctx[:policy].must_equal Hash
|
83
|
-
ctx["policy"].must_equal Hash
|
91
|
+
_(ctx[:model]).must_equal Object
|
92
|
+
_(ctx["model"]).must_equal Object
|
93
|
+
_(ctx[:policy]).must_equal Hash
|
94
|
+
_(ctx["policy"]).must_equal Hash
|
84
95
|
|
85
96
|
ctx["contract.default"] = Module
|
86
|
-
ctx["contract.default"].must_equal Module
|
87
|
-
ctx[:"contract.default"].must_equal Module
|
97
|
+
_(ctx["contract.default"]).must_equal Module
|
98
|
+
_(ctx[:"contract.default"]).must_equal Module
|
88
99
|
|
89
100
|
# key?
|
90
|
-
ctx.key?("____contract.default").must_equal false
|
91
|
-
ctx.key?("contract.default").must_equal true
|
92
|
-
ctx.key?(:"contract.default").must_equal true
|
101
|
+
_(ctx.key?("____contract.default")).must_equal false
|
102
|
+
_(ctx.key?("contract.default")).must_equal true
|
103
|
+
_(ctx.key?(:"contract.default")).must_equal true
|
93
104
|
|
94
105
|
# context in context
|
95
|
-
ctx2 = Trailblazer::Context.for_circuit(ctx, {}, [ctx, flow_options], circuit_options)
|
106
|
+
ctx2 = Trailblazer::Context.for_circuit(ctx, {}, [ctx, flow_options], **circuit_options)
|
96
107
|
|
97
|
-
ctx2[:model].must_equal Object
|
98
|
-
ctx2["model"].must_equal Object
|
108
|
+
_(ctx2[:model]).must_equal Object
|
109
|
+
_(ctx2["model"]).must_equal Object
|
99
110
|
|
100
111
|
ctx2["contract.default"] = Class
|
101
|
-
ctx2["contract.default"].must_equal Class
|
102
|
-
ctx2[:"contract.default"].must_equal Class
|
112
|
+
_(ctx2["contract.default"]).must_equal Class
|
113
|
+
_(ctx2[:"contract.default"]).must_equal Class
|
103
114
|
|
104
115
|
# key?
|
105
|
-
ctx2.key?("contract.default").must_equal true
|
106
|
-
ctx2.key?(:"contract.default").must_equal true
|
107
|
-
ctx2.key?("model").must_equal true
|
116
|
+
_(ctx2.key?("contract.default")).must_equal true
|
117
|
+
_(ctx2.key?(:"contract.default")).must_equal true
|
118
|
+
_(ctx2.key?("model")).must_equal true
|
108
119
|
|
109
120
|
# wrapped ctx doesn't change
|
110
|
-
ctx["contract.default"].must_equal Module
|
111
|
-
ctx[:"contract.default"].must_equal Module
|
121
|
+
_(ctx["contract.default"]).must_equal Module
|
122
|
+
_(ctx[:"contract.default"]).must_equal Module
|
112
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
|
113
130
|
|
114
131
|
ctx3 = ctx.merge("result" => false)
|
115
132
|
|
116
|
-
ctx3["contract.default"].must_equal Module
|
117
|
-
ctx3[:"contract.default"].must_equal Module
|
118
|
-
ctx3["result"].must_equal false
|
119
|
-
ctx3[:result].must_equal false
|
120
|
-
ctx3.key?("result").must_equal true
|
121
|
-
ctx3.key?(:result).must_equal true
|
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
|
122
139
|
end
|
123
140
|
|
124
141
|
it "Aliasable" do
|
125
|
-
flow_options = {
|
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
|
+
|
126
150
|
circuit_options = {}
|
127
151
|
|
128
152
|
immutable = {model: Object, "policy" => Hash}
|
129
153
|
|
130
|
-
ctx = Trailblazer::Context.for_circuit(immutable, {}, [immutable, flow_options], circuit_options)
|
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)
|
131
160
|
|
132
|
-
ctx[:model].must_equal Object
|
133
|
-
ctx["model"].must_equal Object
|
134
|
-
ctx[:policy].must_equal Hash
|
135
|
-
ctx["policy"].must_equal Hash
|
161
|
+
_(ctx[:model]).must_equal Object
|
162
|
+
_(ctx["model"]).must_equal Object
|
163
|
+
_(ctx[:policy]).must_equal Hash
|
164
|
+
_(ctx["policy"]).must_equal Hash
|
136
165
|
|
137
166
|
ctx["contract.default"] = Module
|
138
|
-
ctx["contract.default"].must_equal Module
|
139
|
-
ctx[:"contract.default"].must_equal Module
|
167
|
+
_(ctx["contract.default"]).must_equal Module
|
168
|
+
_(ctx[:"contract.default"]).must_equal Module
|
140
169
|
|
141
170
|
# alias
|
142
|
-
ctx[:result]
|
143
|
-
ctx["result"]
|
171
|
+
assert_nil ctx[:result]
|
172
|
+
assert_nil ctx["result"]
|
144
173
|
|
145
|
-
ctx[:contract].must_equal Module
|
174
|
+
_(ctx[:contract]).must_equal Module
|
175
|
+
_(ctx['contract']).must_equal Module
|
146
176
|
|
147
|
-
ctx[:stack]
|
177
|
+
assert_nil ctx[:stack]
|
178
|
+
assert_nil ctx['stack']
|
148
179
|
|
149
180
|
# Set an aliased property via setter
|
150
181
|
ctx["trace.stack"] = Object
|
151
|
-
ctx[:stack].must_equal Object
|
152
|
-
ctx["
|
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)
|
153
192
|
|
154
193
|
# key?
|
155
|
-
ctx.key?("____contract.default").must_equal false
|
156
|
-
ctx.key?("contract.default").must_equal true
|
157
|
-
ctx.key?(:"contract.default").must_equal true
|
158
|
-
ctx.key?(:contract).must_equal true
|
159
|
-
ctx.key?(:result).must_equal false
|
160
|
-
ctx.key?(:stack).must_equal true
|
161
|
-
ctx.key?("trace.stack").must_equal true
|
162
|
-
ctx.key?(:"trace.stack").must_equal true
|
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
|
+
|
163
213
|
|
164
214
|
# to_hash
|
165
|
-
ctx.to_hash.must_equal(:model=>Object, :policy=>Hash, :
|
215
|
+
_(ctx.to_hash).must_equal(:model=>Object, :policy=>Hash, :contract=>Module, :"contract.default"=>Module, :stack=>String, :"trace.stack"=>String)
|
166
216
|
|
167
217
|
# context in context
|
168
|
-
ctx2 = Trailblazer::Context.for_circuit(ctx, {}, [ctx, flow_options], circuit_options)
|
169
|
-
|
170
|
-
ctx2.key?("____contract.default").must_equal false
|
171
|
-
ctx2.key?("contract.default").must_equal true
|
172
|
-
ctx2.key?(:"contract.default").must_equal true
|
173
|
-
ctx2.key?(:contract).must_equal true
|
174
|
-
ctx2.key?(:result).must_equal false
|
175
|
-
ctx2.key?("result.default").must_equal false
|
176
|
-
ctx2.key?(:stack).must_equal true
|
177
|
-
ctx2.key?("trace.stack").must_equal true
|
178
|
-
ctx2.key?(:"trace.stack").must_equal true
|
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
|
179
229
|
|
180
230
|
# Set aliased in new context via setter
|
181
231
|
ctx2["result.default"] = Class
|
182
232
|
|
183
|
-
ctx2[:result].must_equal Class
|
184
|
-
ctx2[:"result.default"].must_equal Class
|
233
|
+
_(ctx2[:result]).must_equal Class
|
234
|
+
_(ctx2[:"result.default"]).must_equal Class
|
185
235
|
|
186
|
-
ctx2.key?("result.default").must_equal true
|
187
|
-
ctx2.key?(:"result.default").must_equal true
|
188
|
-
ctx2.key?(:result).must_equal true
|
236
|
+
_(ctx2.key?("result.default")).must_equal true
|
237
|
+
_(ctx2.key?(:"result.default")).must_equal true
|
238
|
+
_(ctx2.key?(:result)).must_equal true
|
189
239
|
|
190
240
|
# todo: TEST flow_options={context_class: SomethingElse}
|
191
241
|
end
|
192
242
|
|
193
|
-
it ".build
|
194
|
-
|
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={}>")
|
195
256
|
|
196
|
-
|
197
|
-
ctx = Trailblazer::Context::IndifferentAccess.new(immutable, {}, context_alias: {"policy.default" => :policy})
|
257
|
+
_(ctx.to_hash).must_equal({ model: Object })
|
198
258
|
|
199
|
-
ctx[:
|
200
|
-
ctx
|
201
|
-
|
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
|
202
299
|
|
203
300
|
ctx2 = ctx.merge(result: :success)
|
204
301
|
|
205
302
|
|
206
|
-
ctx2[:model].must_equal Object
|
207
|
-
ctx2["model"].must_equal Object
|
208
|
-
ctx2[:policy].must_equal Hash
|
209
|
-
ctx2[:result].must_equal :success
|
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`}
|
210
320
|
end
|
211
321
|
end
|
212
322
|
|