payload 0.1.0 → 0.2.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.
@@ -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