accord 0.1.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.
- data/.gitignore +7 -0
- data/.rvmrc +1 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +43 -0
- data/accord.gemspec +20 -0
- data/lib/accord.rb +4 -0
- data/lib/accord/adapter_registry.rb +106 -0
- data/lib/accord/base_registry.rb +73 -0
- data/lib/accord/declarations.rb +131 -0
- data/lib/accord/exceptions.rb +22 -0
- data/lib/accord/extendor.rb +36 -0
- data/lib/accord/extendor_container.rb +43 -0
- data/lib/accord/interface.rb +189 -0
- data/lib/accord/interface_body.rb +73 -0
- data/lib/accord/interface_members.rb +31 -0
- data/lib/accord/interface_method.rb +27 -0
- data/lib/accord/interfaces.rb +471 -0
- data/lib/accord/nested_key_hash.rb +66 -0
- data/lib/accord/ro.rb +65 -0
- data/lib/accord/signature_info.rb +76 -0
- data/lib/accord/specification.rb +83 -0
- data/lib/accord/subscription_registry.rb +76 -0
- data/lib/accord/tags.rb +25 -0
- data/lib/accord/version.rb +3 -0
- data/spec/adapter_registry_spec.rb +296 -0
- data/spec/declarations_spec.rb +144 -0
- data/spec/extendor_container_spec.rb +101 -0
- data/spec/extendor_spec.rb +203 -0
- data/spec/integration/adaptation_spec.rb +86 -0
- data/spec/integration/adapter_for_class_declaration_spec.rb +22 -0
- data/spec/integration/adapter_hook_spec.rb +41 -0
- data/spec/integration/default_adapters_spec.rb +81 -0
- data/spec/integration/hash_adapters_spec.rb +20 -0
- data/spec/integration/interface_declaration_spec.rb +258 -0
- data/spec/integration/multi_adapters_spec.rb +83 -0
- data/spec/integration/named_adapters_spec.rb +54 -0
- data/spec/integration/single_adapters_spec.rb +93 -0
- data/spec/integration/subscriptions_spec.rb +245 -0
- data/spec/integration/verification_spec.rb +215 -0
- data/spec/interface_body_spec.rb +157 -0
- data/spec/interface_members_spec.rb +57 -0
- data/spec/interface_spec.rb +147 -0
- data/spec/nested_key_hash_spec.rb +140 -0
- data/spec/signature_info_spec.rb +65 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/specification_spec.rb +246 -0
- data/spec/subscription_registry_spec.rb +206 -0
- data/spec/tags_spec.rb +38 -0
- metadata +134 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
module Accord
|
2
|
+
class NestedKeyHash
|
3
|
+
def [](keys)
|
4
|
+
keys = keys.dup
|
5
|
+
last_key = keys.pop
|
6
|
+
last_hash = keys.inject(hash) do |partial, key|
|
7
|
+
return nil unless partial.has_key?(key)
|
8
|
+
partial[key]
|
9
|
+
end
|
10
|
+
last_hash[last_key]
|
11
|
+
end
|
12
|
+
|
13
|
+
def []=(keys, value)
|
14
|
+
keys = keys.dup
|
15
|
+
last_key = keys.pop
|
16
|
+
last_hash = keys.inject(hash) { |partial, key| partial[key] ||= {} }
|
17
|
+
last_hash[last_key] = value
|
18
|
+
end
|
19
|
+
|
20
|
+
def delete(keys)
|
21
|
+
keys = keys.dup
|
22
|
+
last_hash = {}
|
23
|
+
result = nil
|
24
|
+
while last_hash.size == 0 && keys.any?
|
25
|
+
last_key = keys.pop
|
26
|
+
last_hash = keys.inject(hash) { |partial, key| partial[key] || {} }
|
27
|
+
partial_result = last_hash.delete(last_key)
|
28
|
+
result ||= partial_result
|
29
|
+
end
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
33
|
+
def detect_expansion(keys)
|
34
|
+
keys.inject(hash) do |partial, part|
|
35
|
+
expansion = yield(part)
|
36
|
+
valid_key = expansion.detect { |key| partial.has_key?(key) }
|
37
|
+
return unless valid_key
|
38
|
+
partial[valid_key]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def select_expansions(keys, partial=hash, results=[], &block)
|
43
|
+
if keys.size == 1
|
44
|
+
expansion = block.call(keys.first)
|
45
|
+
valid_expansions = expansion.select { |key| partial.has_key?(key) }
|
46
|
+
valid_expansions.each { |key| results << partial[key] }
|
47
|
+
return results
|
48
|
+
end
|
49
|
+
keys = keys.dup
|
50
|
+
first_key = keys.shift
|
51
|
+
expansion = block.call(first_key)
|
52
|
+
valid_expansions = expansion.select { |key| partial.has_key?(key) }
|
53
|
+
return results unless valid_expansions.any?
|
54
|
+
valid_expansions.each do |key|
|
55
|
+
select_expansions(keys, partial[key], results, &block)
|
56
|
+
end
|
57
|
+
results
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def hash
|
63
|
+
@hash ||= {}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/accord/ro.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
module Accord
|
2
|
+
class RO
|
3
|
+
CACHE_IVAR_NAME = :@_accord_ro_cache_
|
4
|
+
|
5
|
+
def initialize(item, &block)
|
6
|
+
@item = item
|
7
|
+
@block = block
|
8
|
+
end
|
9
|
+
|
10
|
+
def resolve
|
11
|
+
cached = from_cache
|
12
|
+
return cached if cached
|
13
|
+
|
14
|
+
bases = block.call(item)
|
15
|
+
bases_ros = bases.map { |b| RO.new(b, &block).resolve }
|
16
|
+
merge([[item]] + bases_ros + [bases]).tap { |ro| cache(ro) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def changed
|
20
|
+
invalidate
|
21
|
+
resolve
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :item
|
27
|
+
attr_reader :block
|
28
|
+
|
29
|
+
def from_cache
|
30
|
+
item.instance_variable_get(CACHE_IVAR_NAME)
|
31
|
+
end
|
32
|
+
|
33
|
+
def cache(ro)
|
34
|
+
item.instance_variable_set(CACHE_IVAR_NAME, ro)
|
35
|
+
end
|
36
|
+
|
37
|
+
def invalidate
|
38
|
+
item.instance_variable_set(CACHE_IVAR_NAME, nil)
|
39
|
+
end
|
40
|
+
|
41
|
+
def merge(sequences)
|
42
|
+
sequences = sequences.map { |seq| seq.dup }
|
43
|
+
result = []
|
44
|
+
|
45
|
+
loop do
|
46
|
+
sequences.delete_if { |sequence| sequence.empty? }
|
47
|
+
return result unless sequences.any?
|
48
|
+
|
49
|
+
good = sequences.detect do |current|
|
50
|
+
sequences.all? { |seq| !tail(seq).include?(current.first) }
|
51
|
+
end
|
52
|
+
|
53
|
+
raise TypeError, "inconsistent hierarchy" unless good
|
54
|
+
|
55
|
+
head = good.first
|
56
|
+
sequences.each { |seq| seq.shift if seq.first == head }
|
57
|
+
result << head
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def tail(array)
|
62
|
+
array[1..-1] || []
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Accord
|
2
|
+
UNSPECIFIED = Object.new.tap do |o|
|
3
|
+
o.define_singleton_method(:inspect) { 'UNSPECIFIED' }
|
4
|
+
end
|
5
|
+
|
6
|
+
class SignatureInfo
|
7
|
+
def arguments
|
8
|
+
@arguments ||= []
|
9
|
+
end
|
10
|
+
|
11
|
+
def param(arg)
|
12
|
+
if arg.is_a?(Symbol) || arg.is_a?(String)
|
13
|
+
arg = { name: arg.to_sym }
|
14
|
+
elsif arg.is_a?(Hash) && arg.size == 1
|
15
|
+
arg = { name: arg.keys.first, default: arg.values.first }
|
16
|
+
else
|
17
|
+
raise ArgumentError, "bad argument: #{arg.inspect}."
|
18
|
+
end
|
19
|
+
arguments << arg
|
20
|
+
end
|
21
|
+
|
22
|
+
def splat(name)
|
23
|
+
arguments << { name: name.to_sym, splat: true }
|
24
|
+
end
|
25
|
+
|
26
|
+
def block(name=nil)
|
27
|
+
if name
|
28
|
+
@block = name.to_sym
|
29
|
+
else
|
30
|
+
@block
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def match?(params)
|
35
|
+
args = normalized_arguments
|
36
|
+
match_without_all_required_args(args, params) ||
|
37
|
+
match_without_all_required_args(args, without_last_defaults(params))
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def normalized_arguments
|
43
|
+
(arguments + (block ? [{ name: block, block: true }] : [])).map do |arg|
|
44
|
+
if arg[:splat]
|
45
|
+
:rest
|
46
|
+
elsif arg[:block]
|
47
|
+
:block
|
48
|
+
elsif arg.has_key?(:default)
|
49
|
+
:opt
|
50
|
+
else
|
51
|
+
:req
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def without_last_defaults(params)
|
57
|
+
params = params.dup
|
58
|
+
params.pop if params.any? && params.last.first == :block
|
59
|
+
params.pop while params.any? && params.last.first == :opt
|
60
|
+
params
|
61
|
+
end
|
62
|
+
|
63
|
+
def match_without_all_required_args(args, params)
|
64
|
+
return false unless args.size == params.size
|
65
|
+
args.each_with_index do |arg, index|
|
66
|
+
if arg == :req && [:req, :opt].include?(params[index].first)
|
67
|
+
next
|
68
|
+
elsif params[index].first == arg
|
69
|
+
next
|
70
|
+
end
|
71
|
+
return false
|
72
|
+
end
|
73
|
+
true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'accord/ro'
|
3
|
+
|
4
|
+
module Accord
|
5
|
+
class Specification
|
6
|
+
def initialize(*args)
|
7
|
+
if args.size == 2 || args.first.is_a?(String) || args.first.is_a?(Symbol)
|
8
|
+
@name = args.first.to_s
|
9
|
+
bases = (args.size == 2 ? (args[1] || []) : [])
|
10
|
+
else
|
11
|
+
@name = '?'
|
12
|
+
bases = args.first || []
|
13
|
+
end
|
14
|
+
@dependents = Set.new
|
15
|
+
@ro = Accord::RO.new(self) { |spec| spec.bases }
|
16
|
+
self.bases = bases
|
17
|
+
end
|
18
|
+
|
19
|
+
def bases
|
20
|
+
(@bases || []).dup
|
21
|
+
end
|
22
|
+
|
23
|
+
def bases= new_bases
|
24
|
+
new_bases = new_bases.uniq
|
25
|
+
new_bases.each do |base|
|
26
|
+
raise TypeError,
|
27
|
+
'cannot use something other than Specification as a base' \
|
28
|
+
unless base.is_a?(Specification)
|
29
|
+
end
|
30
|
+
|
31
|
+
bases.each { |base| base.unsubscribe(self) }
|
32
|
+
new_bases.each { |base| base.subscribe(self) }
|
33
|
+
|
34
|
+
@bases = new_bases
|
35
|
+
|
36
|
+
changed(self)
|
37
|
+
end
|
38
|
+
|
39
|
+
def each_interface
|
40
|
+
seen = Set.new
|
41
|
+
@bases.each do |base|
|
42
|
+
base.each_interface do |interface|
|
43
|
+
next if seen.include?(interface)
|
44
|
+
seen << interface
|
45
|
+
yield(interface)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def interfaces
|
51
|
+
enum_for(:each_interface).to_a
|
52
|
+
end
|
53
|
+
|
54
|
+
def ancestors
|
55
|
+
@ro.resolve
|
56
|
+
end
|
57
|
+
|
58
|
+
def extends?(other)
|
59
|
+
ancestors.include?(other)
|
60
|
+
end
|
61
|
+
|
62
|
+
def inspect
|
63
|
+
"<Specification #{@name.inspect}>"
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
def changed(originally_changed)
|
69
|
+
@ro.changed
|
70
|
+
@dependents.each do |dependent|
|
71
|
+
dependent.changed(originally_changed)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def subscribe(dependent)
|
76
|
+
@dependents << dependent
|
77
|
+
end
|
78
|
+
|
79
|
+
def unsubscribe(dependent)
|
80
|
+
@dependents.delete(dependent)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'accord/interface'
|
2
|
+
require 'accord/base_registry'
|
3
|
+
|
4
|
+
module Accord
|
5
|
+
class SubscriberLookup < BaseLookup
|
6
|
+
def all(required, provided)
|
7
|
+
extendor = extendors.get(provided)
|
8
|
+
return [] unless extendor
|
9
|
+
hash.select_expansions(required + [provided, '']) do |key|
|
10
|
+
if required.include?(key)
|
11
|
+
key.ancestors.reverse
|
12
|
+
elsif key.equal?(provided)
|
13
|
+
extendor.current.reverse
|
14
|
+
else
|
15
|
+
['']
|
16
|
+
end
|
17
|
+
end.flatten
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class SubscriptionRegistry < BaseRegistry
|
22
|
+
def self.lookup_class
|
23
|
+
SubscriberLookup
|
24
|
+
end
|
25
|
+
|
26
|
+
def subscribe(required, provided, &value)
|
27
|
+
raise ArgumentError, "cannot subscribe without a block" unless value
|
28
|
+
required = normalize_interfaces(required || [nil])
|
29
|
+
provided ||= Interface
|
30
|
+
key = [required, provided, '']
|
31
|
+
(registrations.by_order(required.size)[key] ||= []) << value
|
32
|
+
end
|
33
|
+
|
34
|
+
def unsubscribe(required, provided, value=nil)
|
35
|
+
required = normalize_interfaces(required || [nil])
|
36
|
+
provided ||= Interface
|
37
|
+
lookup = registrations.by_order(required.size)
|
38
|
+
key = [required, provided, '']
|
39
|
+
old = lookup[key] || []
|
40
|
+
|
41
|
+
return if old.empty?
|
42
|
+
|
43
|
+
new = value.nil?? [] : old.select { |v| !v.equal?(value) }
|
44
|
+
if new.any?
|
45
|
+
lookup[key] = new
|
46
|
+
else
|
47
|
+
lookup.delete(key)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def all(options={})
|
52
|
+
required = normalize_interfaces(options[:required] || [nil])
|
53
|
+
provided = options[:provided] || Interface
|
54
|
+
registrations.by_order(required.size)[[required, provided, '']] || []
|
55
|
+
end
|
56
|
+
|
57
|
+
def lookup(required, provided)
|
58
|
+
required = normalize_interfaces(required || [nil])
|
59
|
+
provided ||= Interface
|
60
|
+
lookup = registrations.by_order(required.size)
|
61
|
+
lookup.all(required, provided)
|
62
|
+
end
|
63
|
+
|
64
|
+
def get(objects, provided=Interface)
|
65
|
+
lookup(map_provided_by(objects), provided).map do |subscriber|
|
66
|
+
subscriber.call(*objects)
|
67
|
+
end.compact
|
68
|
+
end
|
69
|
+
|
70
|
+
def call(objects, provided=Interface)
|
71
|
+
lookup(map_provided_by(objects), provided).each do |subscriber|
|
72
|
+
subscriber.call(*objects)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/accord/tags.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module Accord
|
2
|
+
class Tags
|
3
|
+
def initialize
|
4
|
+
@hash = {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def []=(tag, value)
|
8
|
+
@hash[tag.to_sym] = value
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](tag)
|
12
|
+
@hash[tag.to_sym]
|
13
|
+
end
|
14
|
+
|
15
|
+
def fetch(tag, default=Tags.marker)
|
16
|
+
return @hash[tag] if @hash.has_key?(tag)
|
17
|
+
return default unless default.equal?(self.class.marker)
|
18
|
+
raise ArgumentError, "tag #{tag.inspect} not found."
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.marker
|
22
|
+
@marker ||= Object.new
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,296 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'accord/adapter_registry'
|
3
|
+
|
4
|
+
module Accord
|
5
|
+
describe AdapterRegistry do
|
6
|
+
subject { AdapterRegistry.new }
|
7
|
+
|
8
|
+
let(:adapter_factory) { Proc.new {} }
|
9
|
+
let(:other_adapter_factory) { Proc.new {} }
|
10
|
+
|
11
|
+
|
12
|
+
let(:i1) { stub_interface }
|
13
|
+
let(:i2) { stub_interface }
|
14
|
+
|
15
|
+
def stub_interface(*bases)
|
16
|
+
stub.tap do |interface|
|
17
|
+
interface.stub(
|
18
|
+
:iro => [interface],
|
19
|
+
:ancestors => [interface]
|
20
|
+
)
|
21
|
+
interface.stub(:extends?).and_return(false)
|
22
|
+
interface.stub(:extends?).with(interface).and_return(true)
|
23
|
+
bases.each do |base|
|
24
|
+
interface.iro << base
|
25
|
+
interface.ancestors << base
|
26
|
+
interface.stub(:extends?).with(base).and_return(true)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#first" do
|
32
|
+
it "returns no adapter when registry is empty" do
|
33
|
+
expect(subject.first).to be_nil
|
34
|
+
end
|
35
|
+
|
36
|
+
context "registered most simple single adapter" do
|
37
|
+
it "returns that if no arguments are passed" do
|
38
|
+
subject.register([Interface], Interface, '', &adapter_factory)
|
39
|
+
expect(subject.first).to be adapter_factory
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it "hits right adapter factory if matches" do
|
44
|
+
subject.register([i1], nil, '', &adapter_factory)
|
45
|
+
expect(
|
46
|
+
subject.first(required: [i1], provided: nil, name: '')
|
47
|
+
).to be adapter_factory
|
48
|
+
end
|
49
|
+
|
50
|
+
it "miss adapter factory if none matches" do
|
51
|
+
subject.register([i1], nil, '', &adapter_factory)
|
52
|
+
expect(
|
53
|
+
subject.first(required: [i2], provided: nil, name: '')
|
54
|
+
).to be_nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#all" do
|
59
|
+
it "returns no adapter when empty" do
|
60
|
+
expect(subject.all).to be_empty
|
61
|
+
end
|
62
|
+
|
63
|
+
context "registered most simple single adapter" do
|
64
|
+
before do
|
65
|
+
subject.register([Interface], Interface, '', &adapter_factory)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "returns that if no arguments are passed" do
|
69
|
+
expect(subject.all).to eq [['', adapter_factory]]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "selection in registration for multiple names" do
|
74
|
+
it "returns all registered adapters" do
|
75
|
+
subject.register([nil], nil, &adapter_factory)
|
76
|
+
subject.register([nil], nil, 'other name', &other_adapter_factory)
|
77
|
+
expect(subject.all(required: [nil], provided: nil)).to eq [
|
78
|
+
['', adapter_factory],
|
79
|
+
['other name', other_adapter_factory]
|
80
|
+
]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "#register" do
|
86
|
+
it "complains if registration happens without block" do
|
87
|
+
expect {
|
88
|
+
subject.register([Interface], Interface, '')
|
89
|
+
}.to raise_error(ArgumentError)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "allows registering a null adapter" do
|
93
|
+
subject.register([], i1, '', &adapter_factory)
|
94
|
+
expect(
|
95
|
+
subject.first(required: [], provided: i1, name:'')
|
96
|
+
).to be adapter_factory
|
97
|
+
end
|
98
|
+
|
99
|
+
context "when using [nil] and nil for required and provided" do
|
100
|
+
it "defaults to most simple single adapter" do
|
101
|
+
subject.register([nil], nil, '', &adapter_factory)
|
102
|
+
expect(
|
103
|
+
subject.first(required: [Interface], provided: Interface, name: '')
|
104
|
+
).to be adapter_factory
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "when using nil and nil for required and provided" do
|
109
|
+
it "defaults to most simple single adapter" do
|
110
|
+
subject.register(nil, nil, '', &adapter_factory)
|
111
|
+
expect(
|
112
|
+
subject.first(required: [Interface], provided: Interface, name: '')
|
113
|
+
).to be adapter_factory
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context "registration without name" do
|
118
|
+
it "defaults to empty name" do
|
119
|
+
subject.register([nil], nil, &adapter_factory)
|
120
|
+
expect(
|
121
|
+
subject.first(required: [nil], provided: nil, name: '')
|
122
|
+
).to be adapter_factory
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe "#lookup" do
|
128
|
+
it "returns no adapter when registry is empty" do
|
129
|
+
expect(subject.lookup([], nil)).to be_nil
|
130
|
+
end
|
131
|
+
|
132
|
+
context "registered most simple single adapter" do
|
133
|
+
before do
|
134
|
+
subject.register([Interface], Interface,
|
135
|
+
'', &adapter_factory)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "returns that if required is nil and provided is nil" do
|
139
|
+
expect(subject.lookup(nil, nil)).to be adapter_factory
|
140
|
+
end
|
141
|
+
|
142
|
+
it "returns that if required is [nil] and provided is nil" do
|
143
|
+
expect(subject.lookup([nil], nil)).to be adapter_factory
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
it "hits right adapter factory if matches exactly" do
|
148
|
+
subject.register([i1], nil, '', &adapter_factory)
|
149
|
+
expect(
|
150
|
+
subject.lookup([i1], nil, '')
|
151
|
+
).to be adapter_factory
|
152
|
+
end
|
153
|
+
|
154
|
+
it "defaults name to empty string" do
|
155
|
+
subject.register([i1], nil, '', &adapter_factory)
|
156
|
+
expect(subject.lookup([i1], nil)).to be adapter_factory
|
157
|
+
end
|
158
|
+
|
159
|
+
it "hits right adapter factory if required extends some registered" do
|
160
|
+
i12 = stub_interface(i1)
|
161
|
+
subject.register([i1], nil, '', &adapter_factory)
|
162
|
+
expect(subject.lookup([i12], nil, '')).to be adapter_factory
|
163
|
+
end
|
164
|
+
|
165
|
+
it "miss adapter factory if none matches" do
|
166
|
+
subject.register([i1], nil, '', &adapter_factory)
|
167
|
+
expect(subject.lookup([i2], nil, '')).to be_nil
|
168
|
+
end
|
169
|
+
|
170
|
+
it "returns default value if missed" do
|
171
|
+
subject.register([i1], nil, '', &adapter_factory)
|
172
|
+
expect(subject.lookup([i2], nil, default: 'default')).to eq 'default'
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
describe "#lookup_all" do
|
177
|
+
it "returns no adapter when registry is empty" do
|
178
|
+
expect(subject.lookup_all([], nil)).to be_empty
|
179
|
+
end
|
180
|
+
|
181
|
+
context "registered most simple single adapter" do
|
182
|
+
before do
|
183
|
+
subject.register([Interface], Interface, '', &adapter_factory)
|
184
|
+
end
|
185
|
+
|
186
|
+
it "returns that if required is nil and provided is nil" do
|
187
|
+
expect(subject.lookup_all(nil, nil)).to eq({'' => adapter_factory})
|
188
|
+
end
|
189
|
+
|
190
|
+
it "returns that if required is [nil] and provided is nil" do
|
191
|
+
expect(subject.lookup_all([nil], nil)).to eq({'' => adapter_factory})
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
it "hits right adapter factory if matches" do
|
196
|
+
subject.register([i1], nil, '', &adapter_factory)
|
197
|
+
expect(subject.lookup_all([i1], nil)).to eq({'' => adapter_factory})
|
198
|
+
end
|
199
|
+
|
200
|
+
it "hits right adapter factory if required extends some registered" do
|
201
|
+
i12 = stub_interface(i1)
|
202
|
+
subject.register([i1], nil, '', &adapter_factory)
|
203
|
+
expect(subject.lookup_all([i12], nil)).to eq({'' => adapter_factory})
|
204
|
+
end
|
205
|
+
|
206
|
+
it "hits right adapter factory if provided is extended by some registered" do
|
207
|
+
i12 = stub_interface(i1)
|
208
|
+
subject.register([nil], i12, '', &adapter_factory)
|
209
|
+
expect(subject.lookup_all([nil], i1)).to eq({'' => adapter_factory})
|
210
|
+
end
|
211
|
+
|
212
|
+
it "in the face of ambiguity, brings the most specific for required" do
|
213
|
+
i12 = stub_interface(i1)
|
214
|
+
subject.register([i1], nil, '', &other_adapter_factory)
|
215
|
+
subject.register([i12], nil, '', &adapter_factory)
|
216
|
+
expect(subject.lookup_all([i12], nil)).to eq({'' => adapter_factory})
|
217
|
+
end
|
218
|
+
|
219
|
+
it "in the face of ambiguity, brings the most specific for provided" do
|
220
|
+
i12 = stub_interface(i1)
|
221
|
+
subject.register([nil], i1, '', &other_adapter_factory)
|
222
|
+
subject.register([nil], i12, '', &adapter_factory)
|
223
|
+
expect(subject.lookup_all([nil], i12)).to eq({'' => adapter_factory})
|
224
|
+
end
|
225
|
+
|
226
|
+
it "miss adapter factory if none matches" do
|
227
|
+
subject.register([i1], nil, '', &adapter_factory)
|
228
|
+
expect(subject.lookup_all([i2], nil)).to be_empty
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
describe "#unregister" do
|
233
|
+
it "doesn't complain when unregistering empty registry" do
|
234
|
+
expect { subject.unregister([nil], nil, '') }.to_not raise_error
|
235
|
+
end
|
236
|
+
|
237
|
+
it "keeps registered if required doesn't match on unregistration" do
|
238
|
+
subject.register([i1], nil, '', &adapter_factory)
|
239
|
+
subject.unregister([i2], nil, '')
|
240
|
+
expect(
|
241
|
+
subject.first(required: [i1], provided: nil, name: '')
|
242
|
+
).to be adapter_factory
|
243
|
+
end
|
244
|
+
|
245
|
+
it "keeps registered if provided doesn't match on unregistration" do
|
246
|
+
subject.register([nil], i1, '', &adapter_factory)
|
247
|
+
subject.unregister([nil], i2, '')
|
248
|
+
expect(
|
249
|
+
subject.first(required: [nil], provided: i1, name: '')
|
250
|
+
).to be adapter_factory
|
251
|
+
end
|
252
|
+
|
253
|
+
it "keeps registered if name doesn't match on unregistration" do
|
254
|
+
subject.register([i1], nil, '', &adapter_factory)
|
255
|
+
subject.unregister([i1], nil, 'other name')
|
256
|
+
expect(
|
257
|
+
subject.first(required: [i1], provided: nil, name: '')
|
258
|
+
).to be adapter_factory
|
259
|
+
end
|
260
|
+
|
261
|
+
it "keeps registered if value is passed but doesn't match on "\
|
262
|
+
"unregistration" do
|
263
|
+
subject.register([i1], nil, '', &adapter_factory)
|
264
|
+
subject.unregister([i1], nil, '', stub)
|
265
|
+
expect(
|
266
|
+
subject.first(required: [i1], provided: nil, name: '')
|
267
|
+
).to be adapter_factory
|
268
|
+
end
|
269
|
+
|
270
|
+
it "unregisters if value not passed and everything else matches "\
|
271
|
+
"previous registration" do
|
272
|
+
subject.register([i1], nil, '', &adapter_factory)
|
273
|
+
subject.unregister([i1], nil, '')
|
274
|
+
expect(
|
275
|
+
subject.first(required: [i1], provided: nil, name: '')
|
276
|
+
).to be_nil
|
277
|
+
end
|
278
|
+
|
279
|
+
context "when using [nil] and nil for required and provided" do
|
280
|
+
it "defaults to most simple single adapter" do
|
281
|
+
subject.register([Interface], Interface, '', &adapter_factory)
|
282
|
+
subject.unregister([nil], nil, '')
|
283
|
+
expect(subject.first).to be_nil
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
context "when using nil and nil for required and provided" do
|
288
|
+
it "defaults to most simple single adapter" do
|
289
|
+
subject.register([Interface], Interface, '', &adapter_factory)
|
290
|
+
subject.unregister(nil, nil, '')
|
291
|
+
expect(subject.first).to be_nil
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|