payload 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,6 @@
1
+ require 'payload/definition'
1
2
  require 'payload/exported_definition'
2
- require 'payload/undefined_dependency_error'
3
+ require 'payload/undefined_definition'
3
4
 
4
5
  module Payload
5
6
  # Immutable list of definitions.
@@ -12,14 +13,18 @@ module Payload
12
13
  @definitions = definitions
13
14
  end
14
15
 
15
- def add(name, definition)
16
- self.class.new(definitions.merge(name => definition))
16
+ def add(name, resolver)
17
+ value = find(name).set(Definition.new(resolver))
18
+ self.class.new(definitions.merge(name => value))
17
19
  end
18
20
 
19
21
  def find(name)
20
- definitions.fetch(name) do
21
- raise(UndefinedDependencyError, "No definition for dependency: #{name}")
22
- end
22
+ definitions.fetch(name) { UndefinedDefinition.new(name) }
23
+ end
24
+
25
+ def decorate(name, block)
26
+ decorated = find(name).decorate(block)
27
+ self.class.new(@definitions.merge(name => decorated))
23
28
  end
24
29
 
25
30
  def export(names)
@@ -0,0 +1,5 @@
1
+ module Payload
2
+ # Raised when attempting to define a dependency which is already defined.
3
+ class DependencyAlreadyDefinedError < StandardError
4
+ end
5
+ end
@@ -1,5 +1,5 @@
1
1
  module Payload
2
- # Decorates a base definition such as {ServiceDefinition} to provide access to
2
+ # Decorates a base definition such as {ServiceResolver} to provide access to
3
3
  # private dependencies in the {Container} from which the definition was
4
4
  # exported.
5
5
  #
@@ -13,7 +13,17 @@ module Payload
13
13
  end
14
14
 
15
15
  def resolve(container)
16
- @definition.resolve(container.import(@private_definitions))
16
+ definition.resolve(container.import(private_definitions))
17
17
  end
18
+
19
+ def ==(other)
20
+ other.is_a?(ExportedDefinition) &&
21
+ definition == other.definition &&
22
+ private_definitions == other.private_definitions
23
+ end
24
+
25
+ protected
26
+
27
+ attr_reader :definition, :private_definitions
18
28
  end
19
29
  end
@@ -6,7 +6,7 @@ module Payload
6
6
  #
7
7
  # @see Container#factory Container#factory for defining and using factories.
8
8
  class Factory
9
- # Used internally by {FactoryDefinition}.
9
+ # Used internally by {FactoryResolver}.
10
10
  #
11
11
  # @api private
12
12
  def initialize(container, block, decorators)
@@ -0,0 +1,18 @@
1
+ require 'payload/factory'
2
+
3
+ module Payload
4
+ # Encapsulates logic for resolving factory definitions.
5
+ #
6
+ # Used internally by {Container}. Use {Container#factory}.
7
+ #
8
+ # @api private
9
+ class FactoryResolver
10
+ def initialize(block)
11
+ @block = block
12
+ end
13
+
14
+ def resolve(container, decorators)
15
+ Factory.new(container, @block, decorators)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ module Payload
2
+ # Encapsulates logic for resolving service definitions.
3
+ #
4
+ # Used internally by {Container}. Use {Container#service}.
5
+ #
6
+ # @api private
7
+ class ServiceResolver
8
+ def initialize(block)
9
+ @block = block
10
+ end
11
+
12
+ def resolve(container, decorators)
13
+ base = @block.call(container)
14
+ decorators.decorate(base, container)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,37 @@
1
+ require 'payload/decorator_chain'
2
+ require 'payload/undefined_dependency_error'
3
+
4
+ module Payload
5
+ # Returns from {DefinitionList} when attempting to find an undefined
6
+ # definition.
7
+ #
8
+ # @api private
9
+ class UndefinedDefinition
10
+ def initialize(name, decorators = DecoratorChain.new)
11
+ @name = name
12
+ @decorators = decorators
13
+ end
14
+
15
+ def ==(other)
16
+ other.is_a?(UndefinedDefinition) && name == other.name
17
+ end
18
+
19
+ def resolve(container)
20
+ raise UndefinedDependencyError, "No definition for dependency: #{name}"
21
+ end
22
+
23
+ def decorate(block)
24
+ self.class.new(@name, @decorators.add(block))
25
+ end
26
+
27
+ def set(replacement)
28
+ @decorators.inject(replacement) do |definition, decorator|
29
+ definition.decorate(decorator)
30
+ end
31
+ end
32
+
33
+ protected
34
+
35
+ attr_reader :name
36
+ end
37
+ end
@@ -1,3 +1,3 @@
1
1
  module Payload
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -68,10 +68,18 @@ describe Payload::Container do
68
68
  expect(original[:example]).to eq('expected component')
69
69
  end
70
70
 
71
+ it 'decorates a dependency which is defined later' do
72
+ container = build_container.
73
+ decorate(:example) { |component, _| "decorated #{component}" }.
74
+ service(:example) { |_| 'expected component' }
75
+
76
+ expect(container[:example]).to eq('decorated expected component')
77
+ end
78
+
71
79
  it 'raises an exception for an unknown dependency' do
72
80
  container = build_container
73
81
 
74
- expect { container.decorate(:anything) }
82
+ expect { container.decorate(:undefined)[:undefined] }
75
83
  .to raise_error(Payload::UndefinedDependencyError)
76
84
  end
77
85
  end
@@ -122,9 +130,9 @@ describe Payload::Container do
122
130
  describe '#import' do
123
131
  it 'returns a new container with the given definitions' do
124
132
  first_export =
125
- Payload::ServiceDefinition.new(lambda { |config| 'one' })
133
+ Payload::ServiceResolver.new(lambda { |config| 'one' })
126
134
  second_export =
127
- Payload::ServiceDefinition.new(lambda { |config| 'two' })
135
+ Payload::ServiceResolver.new(lambda { |config| 'two' })
128
136
  definitions = Payload::DefinitionList.
129
137
  new.
130
138
  add(:one, first_export).
@@ -2,6 +2,10 @@ require 'spec_helper'
2
2
  require 'payload/decorator_chain'
3
3
 
4
4
  describe Payload::DecoratorChain do
5
+ it 'is enumerable' do
6
+ expect(Payload::DecoratorChain.new).to be_a(Enumerable)
7
+ end
8
+
5
9
  describe '#decorate' do
6
10
  it 'applies a series of decorators to a component' do
7
11
  chain = Payload::DecoratorChain
@@ -26,4 +30,34 @@ describe Payload::DecoratorChain do
26
30
  expect(result).to eq('Decorated original')
27
31
  end
28
32
  end
33
+
34
+ describe '#each' do
35
+ it 'yields each decorator' do
36
+ first = double('first')
37
+ second = double('second')
38
+ chain = Payload::DecoratorChain.new.add(first).add(second)
39
+ result = []
40
+
41
+ chain.each { |yielded| result << yielded }
42
+
43
+ expect(result).to eq([first, second])
44
+ end
45
+ end
46
+
47
+ describe '#==' do
48
+ it 'is equal with the same decorators' do
49
+ expect(Payload::DecoratorChain.new.add(:one).add(:two)).
50
+ to eq(Payload::DecoratorChain.new.add(:one).add(:two))
51
+ end
52
+
53
+ it 'is unequal with different decorators' do
54
+ expect(Payload::DecoratorChain.new.add(:one).add(:two)).
55
+ not_to eq(Payload::DecoratorChain.new.add(:one).add(:three))
56
+ end
57
+
58
+ it 'is unequal with another type' do
59
+ expect(Payload::DecoratorChain.new.add(:one).add(:two)).
60
+ not_to eq(:other)
61
+ end
62
+ end
29
63
  end
@@ -4,22 +4,74 @@ require 'payload/definition_list'
4
4
  describe Payload::DefinitionList do
5
5
  describe '#add' do
6
6
  it 'adds a dependency to be found later' do
7
- definition = double('definition')
7
+ resolver = double('resolver')
8
+ definition = Payload::Definition.new(resolver)
8
9
  definition_list = Payload::DefinitionList.new
9
10
 
10
- defined = definition_list.add(:example, definition)
11
+ defined = definition_list.add(:example, resolver)
11
12
 
12
13
  expect(defined.find(:example)).to eq(definition)
13
14
  end
14
15
 
16
+ it 'does not replace an existing definition' do
17
+ definition_list = Payload::DefinitionList.new
18
+
19
+ defined = definition_list.add(:example, :original)
20
+
21
+ expect { defined.add(:example, :replacement) }.
22
+ to raise_error(Payload::DependencyAlreadyDefinedError)
23
+ end
24
+
25
+ it 'does not mutate the list' do
26
+ resolver = double('resolver')
27
+ definition_list = Payload::DefinitionList.new
28
+
29
+ definition_list.add(:example, resolver)
30
+
31
+ expect(definition_list.find(:example)).
32
+ to eq(Payload::UndefinedDefinition.new(:example))
33
+ end
34
+ end
35
+
36
+ describe '#decorate' do
37
+ it 'replaces a dependency with a decorated version' do
38
+ resolver = double('resolver')
39
+ decorator = double('decorator')
40
+ decorated = Payload::Definition.new(resolver).decorate(decorator)
41
+ definition_list = Payload::DefinitionList.new
42
+
43
+ defined = definition_list.
44
+ add(:example, resolver).
45
+ decorate(:example, decorator)
46
+
47
+ expect(defined.find(:example)).to eq(decorated)
48
+ end
49
+
50
+ it 'decorates an undefined dependency' do
51
+ resolver = double('resolver')
52
+ decorator = double('decorator')
53
+ decorated = Payload::Definition.new(resolver).decorate(decorator)
54
+ definition_list = Payload::DefinitionList.new
55
+
56
+ defined = definition_list.
57
+ decorate(:example, decorator).
58
+ add(:example, resolver)
59
+
60
+ expect(defined.find(:example)).to eq(decorated)
61
+ end
62
+
15
63
  it 'does not mutate the list' do
16
- definition = double('definition')
64
+ resolver = double('resolver')
65
+ decorator = double('decorator')
66
+ definition = Payload::Definition.new(resolver)
17
67
  definition_list = Payload::DefinitionList.new
18
68
 
19
- definition_list.add(:example, definition)
69
+ defined = definition_list.
70
+ add(:example, resolver)
71
+
72
+ defined.decorate(:example, decorator)
20
73
 
21
- expect { definition_list.find(:example) }.
22
- to raise_error(Payload::UndefinedDependencyError)
74
+ expect(defined.find(:example)).to eq(definition)
23
75
  end
24
76
  end
25
77
 
@@ -30,23 +82,21 @@ describe Payload::DefinitionList do
30
82
  add(:one, 'first').
31
83
  add(:two, 'second').
32
84
  add(:three, 'third')
33
- first_exported = double('exported_first')
34
- allow(Payload::ExportedDefinition).
35
- to receive(:new).
36
- with('first', definition_list).
37
- and_return(first_exported)
38
- second_exported = double('exported_second')
39
- allow(Payload::ExportedDefinition).
40
- to receive(:new).
41
- with('second', definition_list).
42
- and_return(second_exported)
43
85
 
44
86
  exported = definition_list.export([:one, :two])
45
87
 
46
- expect(exported.find(:one)).to eq(first_exported)
47
- expect(exported.find(:two)).to eq(second_exported)
48
- expect { exported.find(:three) }.
49
- to raise_error(Payload::UndefinedDependencyError)
88
+ first = Payload::ExportedDefinition.new(
89
+ Payload::Definition.new('first'),
90
+ definition_list
91
+ )
92
+ second = Payload::ExportedDefinition.new(
93
+ Payload::Definition.new('second'),
94
+ definition_list
95
+ )
96
+ third = Payload::UndefinedDefinition.new(:three)
97
+ expect(exported.find(:one)).to eq(first)
98
+ expect(exported.find(:two)).to eq(second)
99
+ expect(exported.find(:three)).to eq(third)
50
100
  end
51
101
  end
52
102
 
@@ -56,23 +106,24 @@ describe Payload::DefinitionList do
56
106
  right = Payload::DefinitionList.new.add(:two, 'second')
57
107
  merged = left.import(right)
58
108
 
59
- expect(merged.find(:one)).to eq('first')
60
- expect(merged.find(:two)).to eq('second')
109
+ expect(merged.find(:one)).to eq(Payload::Definition.new('first'))
110
+ expect(merged.find(:two)).to eq(Payload::Definition.new('second'))
61
111
  end
62
112
  end
63
113
 
64
114
  describe '#find' do
65
- it 'raises for an unknown definition' do
115
+ it 'returns an unknown definition' do
66
116
  definition_list = Payload::DefinitionList.new
67
117
 
68
- expect { definition_list.find(:example) }.
69
- to raise_error(Payload::UndefinedDependencyError)
118
+ expect(definition_list.find(:example)).
119
+ to eq(Payload::UndefinedDefinition.new(:example))
70
120
  end
71
121
 
72
122
  it 'returns an existing definition' do
73
- definition = double('definition')
123
+ resolver = double('resolver')
124
+ definition = Payload::Definition.new(resolver)
74
125
  definition_list =
75
- Payload::DefinitionList.new.add(:example, definition)
126
+ Payload::DefinitionList.new.add(:example, resolver)
76
127
 
77
128
  expect(definition_list.find(:example)).to eq(definition)
78
129
  end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+ require 'payload/definition'
3
+
4
+ describe Payload::Definition do
5
+ describe '#resolve' do
6
+ it 'provides the container and decorators to its resolver' do
7
+ resolved = double('resolved')
8
+ resolver = double('resolver')
9
+ container = double('container')
10
+ decorators = double('decorators')
11
+ resolver.stub(:resolve).and_return(resolved)
12
+ definition = Payload::Definition.new(resolver, decorators)
13
+
14
+ result = definition.resolve(container)
15
+
16
+ expect(result).to eq(resolved)
17
+ expect(resolver).to have_received(:resolve).with(container, decorators)
18
+ end
19
+ end
20
+
21
+ describe '#decorate' do
22
+ it 'returns a new decorated service' do
23
+ decorator = double('first_decorator')
24
+ resolver = double('resolver')
25
+ resolver.stub(:resolve)
26
+ container = double('container')
27
+ decorated = double('decorated')
28
+ decorators = double('decorators')
29
+ decorators.stub(:add).and_return(decorated)
30
+ Payload::DecoratorChain.stub(:new).and_return(decorators)
31
+ definition = Payload::Definition.new(resolver)
32
+
33
+ definition.
34
+ decorate(decorator).
35
+ resolve(container)
36
+
37
+ expect(decorators).to have_received(:add).with(decorator)
38
+ expect(resolver).to have_received(:resolve).with(container, decorated)
39
+ end
40
+ end
41
+
42
+ describe '#set' do
43
+ it 'raises an already defined error' do
44
+ service = Payload::Definition.new(double('resolver'))
45
+
46
+ expect { service.set(double('replacement')) }.
47
+ to raise_error(Payload::DependencyAlreadyDefinedError)
48
+ end
49
+ end
50
+
51
+ describe '#==' do
52
+ it 'is true with the same resolver and decorators' do
53
+ expect(Payload::Definition.new(:resolver).decorate(:decorator)).
54
+ to eq(Payload::Definition.new(:resolver).decorate(:decorator))
55
+ end
56
+
57
+ it 'is false with a different resolver' do
58
+ expect(Payload::Definition.new(:resolver).decorate(:decorator)).
59
+ not_to eq(Payload::Definition.new(:other_resolver).decorate(:decorator))
60
+ end
61
+
62
+ it 'is false with different decorators' do
63
+ expect(Payload::Definition.new(:resolver).decorate(:decorator)).
64
+ not_to eq(Payload::Definition.new(:resolver).decorate(:other_decorator))
65
+ end
66
+
67
+ it 'is false with a non-definition' do
68
+ expect(Payload::Definition.new(:resolver).decorate(:decorator)).
69
+ not_to eq(:other)
70
+ end
71
+ end
72
+ end