payload 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/CONTRIBUTING.md +10 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +28 -0
- data/LICENSE +19 -0
- data/README.md +242 -0
- data/Rakefile +10 -0
- data/lib/payload/container.rb +96 -0
- data/lib/payload/controller.rb +12 -0
- data/lib/payload/decorator_chain.rb +23 -0
- data/lib/payload/definition_list.rb +41 -0
- data/lib/payload/exported_definition.rb +19 -0
- data/lib/payload/factory.rb +33 -0
- data/lib/payload/factory_definition.rb +24 -0
- data/lib/payload/mutable_container.rb +57 -0
- data/lib/payload/rack_container.rb +23 -0
- data/lib/payload/rails_loader.rb +58 -0
- data/lib/payload/railtie.rb +20 -0
- data/lib/payload/service_definition.rb +24 -0
- data/lib/payload/testing.rb +84 -0
- data/lib/payload/undefined_dependency_error.rb +5 -0
- data/lib/payload/version.rb +3 -0
- data/payload.gemspec +24 -0
- data/spec/payload/container_spec.rb +154 -0
- data/spec/payload/controller_spec.rb +32 -0
- data/spec/payload/decorator_chain_spec.rb +29 -0
- data/spec/payload/definition_list_spec.rb +80 -0
- data/spec/payload/exported_definition_spec.rb +30 -0
- data/spec/payload/factory_definition_spec.rb +49 -0
- data/spec/payload/factory_spec.rb +46 -0
- data/spec/payload/mutable_container_spec.rb +68 -0
- data/spec/payload/rack_container_spec.rb +33 -0
- data/spec/payload/rails_loader_spec.rb +62 -0
- data/spec/payload/service_definition_spec.rb +39 -0
- data/spec/spec_helper.rb +0 -0
- metadata +137 -0
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'payload/container'
|
3
|
+
require 'payload/testing'
|
4
|
+
|
5
|
+
describe Payload::Container do
|
6
|
+
include Payload::Testing
|
7
|
+
|
8
|
+
describe '#factory' do
|
9
|
+
it 'returns an object that responds to new' do
|
10
|
+
container = build_container
|
11
|
+
.service(:from_container) { |config| 'From container' }
|
12
|
+
.factory(:example) do |config|
|
13
|
+
"#{config[:from_new]} and #{config[:from_container]}"
|
14
|
+
end
|
15
|
+
|
16
|
+
result = container[:example].new(from_new: 'From new')
|
17
|
+
|
18
|
+
expect(result).to eq('From new and From container')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#service' do
|
23
|
+
it 'returns a container with the given dependency defined' do
|
24
|
+
container = build_container
|
25
|
+
.service(:example) { |config| 'expected result' }
|
26
|
+
|
27
|
+
expect(container[:example]).to eq('expected result')
|
28
|
+
end
|
29
|
+
|
30
|
+
it "doesn't mutate the container" do
|
31
|
+
original = build_container
|
32
|
+
original.service(:example) { |config| 'expected result' }
|
33
|
+
|
34
|
+
expect { original[:example] }
|
35
|
+
.to raise_error(Payload::UndefinedDependencyError)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'provides access to other dependencies' do
|
39
|
+
container = build_container
|
40
|
+
.service(:example) { |config| "got #{config[:dependency].inspect}" }
|
41
|
+
.service(:dependency) { |config| 'expected result' }
|
42
|
+
|
43
|
+
expect(container[:example]).to eq('got "expected result"')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#decorate' do
|
48
|
+
it 'returns a container with the given dependency decorated' do
|
49
|
+
container = build_container
|
50
|
+
.service(:example) { |config| 'expected component' }
|
51
|
+
.decorate(:example) do |component, config|
|
52
|
+
"decorated #{component.inspect} with #{config[:param].inspect}"
|
53
|
+
end
|
54
|
+
.decorate(:example) { |component, config| "redecorated #{component}" }
|
55
|
+
.service(:param) { |config| 'expected param' }
|
56
|
+
|
57
|
+
expect(container[:example]).to eq(
|
58
|
+
'redecorated decorated "expected component" with "expected param"'
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "doesn't mutate the container" do
|
63
|
+
original = build_container
|
64
|
+
.service(:example) { |config| 'expected component' }
|
65
|
+
original
|
66
|
+
.decorate(:example) { |component, config| "decorated #{component}" }
|
67
|
+
|
68
|
+
expect(original[:example]).to eq('expected component')
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'raises an exception for an unknown dependency' do
|
72
|
+
container = build_container
|
73
|
+
|
74
|
+
expect { container.decorate(:anything) }
|
75
|
+
.to raise_error(Payload::UndefinedDependencyError)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '#export' do
|
80
|
+
it 'allows access to exported definitions' do
|
81
|
+
exports = build_container.
|
82
|
+
service(:example) { |config| 'expected component' }.
|
83
|
+
export(:example)
|
84
|
+
container = build_container.import(exports)
|
85
|
+
|
86
|
+
expect(container[:example]).to eq('expected component')
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'does not allow access to private definitions' do
|
90
|
+
exports = build_container.
|
91
|
+
service(:exported) { |config| 'exported' }.
|
92
|
+
service(:private) { |config| 'private' }.
|
93
|
+
export(:exported)
|
94
|
+
container = build_container.import(exports)
|
95
|
+
|
96
|
+
expect { container[:private] }.
|
97
|
+
to raise_error(Payload::UndefinedDependencyError)
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'allows exported definitions to reference private definitions' do
|
101
|
+
exports = build_container.
|
102
|
+
service(:exported) { |config| "got #{config[:private].inspect}" }.
|
103
|
+
service(:private) { |config| 'private' }.
|
104
|
+
export(:exported)
|
105
|
+
container = build_container.import(exports)
|
106
|
+
|
107
|
+
expect(container[:exported]).to eq('got "private"')
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'exposes local definitions to private definitions' do
|
111
|
+
exports = build_container.
|
112
|
+
service(:exported) { |config| "got #{config[:local].inspect}" }.
|
113
|
+
export(:exported)
|
114
|
+
container = build_container.
|
115
|
+
service(:local) { |config| 'local' }.
|
116
|
+
import(exports)
|
117
|
+
|
118
|
+
expect(container[:exported]).to eq('got "local"')
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe '#import' do
|
123
|
+
it 'returns a new container with the given definitions' do
|
124
|
+
first_export =
|
125
|
+
Payload::ServiceDefinition.new(lambda { |config| 'one' })
|
126
|
+
second_export =
|
127
|
+
Payload::ServiceDefinition.new(lambda { |config| 'two' })
|
128
|
+
definitions = Payload::DefinitionList.
|
129
|
+
new.
|
130
|
+
add(:one, first_export).
|
131
|
+
add(:two, second_export)
|
132
|
+
container = build_container.
|
133
|
+
service(:original) { |config| 'original' }.
|
134
|
+
import(definitions)
|
135
|
+
|
136
|
+
expect(container[:original]).to eq('original')
|
137
|
+
expect(container[:one]).to eq('one')
|
138
|
+
expect(container[:two]).to eq('two')
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe '#[]' do
|
143
|
+
context 'with an undefined dependency' do
|
144
|
+
it 'raises an undefined dependency error' do
|
145
|
+
container = build_container
|
146
|
+
|
147
|
+
expect { container[:undefined] }.to raise_error(
|
148
|
+
Payload::UndefinedDependencyError,
|
149
|
+
'No definition for dependency: undefined'
|
150
|
+
)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'payload/controller'
|
3
|
+
|
4
|
+
describe Payload::Controller do
|
5
|
+
describe '#dependencies' do
|
6
|
+
it 'looks for dependencies from the Rack environment' do
|
7
|
+
dependency = double('dependency')
|
8
|
+
container = { lookup: dependency }
|
9
|
+
env = { dependencies: container }
|
10
|
+
request = double('request', env: env)
|
11
|
+
controller_class = Class.new do
|
12
|
+
include Payload::Controller
|
13
|
+
|
14
|
+
def initialize(request)
|
15
|
+
@request = request
|
16
|
+
end
|
17
|
+
|
18
|
+
def lookup
|
19
|
+
dependencies[:lookup]
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :request
|
25
|
+
end
|
26
|
+
|
27
|
+
result = controller_class.new(request).lookup
|
28
|
+
|
29
|
+
expect(result).to eq(dependency)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'payload/decorator_chain'
|
3
|
+
|
4
|
+
describe Payload::DecoratorChain do
|
5
|
+
describe '#decorate' do
|
6
|
+
it 'applies a series of decorators to a component' do
|
7
|
+
chain = Payload::DecoratorChain
|
8
|
+
.new
|
9
|
+
.add(lambda { |base, config| "Decorated #{base} with #{config[:one]}" })
|
10
|
+
.add(lambda { |base, config| "#{base} and #{config[:two]}" })
|
11
|
+
|
12
|
+
result = chain.decorate('original', one: 'one', two: 'two')
|
13
|
+
|
14
|
+
expect(result).to eq('Decorated original with one and two')
|
15
|
+
end
|
16
|
+
|
17
|
+
it "doesn't mutate" do
|
18
|
+
chain = Payload::DecoratorChain
|
19
|
+
.new
|
20
|
+
.add(lambda { |base, config| "Decorated #{base}" })
|
21
|
+
|
22
|
+
chain.add(lambda { |base, config| 'Unreferenced decorator' })
|
23
|
+
|
24
|
+
result = chain.decorate('original', {})
|
25
|
+
|
26
|
+
expect(result).to eq('Decorated original')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'payload/definition_list'
|
3
|
+
|
4
|
+
describe Payload::DefinitionList do
|
5
|
+
describe '#add' do
|
6
|
+
it 'adds a dependency to be found later' do
|
7
|
+
definition = double('definition')
|
8
|
+
definition_list = Payload::DefinitionList.new
|
9
|
+
|
10
|
+
defined = definition_list.add(:example, definition)
|
11
|
+
|
12
|
+
expect(defined.find(:example)).to eq(definition)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'does not mutate the list' do
|
16
|
+
definition = double('definition')
|
17
|
+
definition_list = Payload::DefinitionList.new
|
18
|
+
|
19
|
+
definition_list.add(:example, definition)
|
20
|
+
|
21
|
+
expect { definition_list.find(:example) }.
|
22
|
+
to raise_error(Payload::UndefinedDependencyError)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#export' do
|
27
|
+
it 'returns a new list with only the given dependencies' do
|
28
|
+
definition_list = Payload::DefinitionList.
|
29
|
+
new.
|
30
|
+
add(:one, 'first').
|
31
|
+
add(:two, 'second').
|
32
|
+
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
|
+
|
44
|
+
exported = definition_list.export([:one, :two])
|
45
|
+
|
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)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#import' do
|
54
|
+
it 'returns a new list with old dependencies and the given dependencies' do
|
55
|
+
left = Payload::DefinitionList.new.add(:one, 'first')
|
56
|
+
right = Payload::DefinitionList.new.add(:two, 'second')
|
57
|
+
merged = left.import(right)
|
58
|
+
|
59
|
+
expect(merged.find(:one)).to eq('first')
|
60
|
+
expect(merged.find(:two)).to eq('second')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#find' do
|
65
|
+
it 'raises for an unknown definition' do
|
66
|
+
definition_list = Payload::DefinitionList.new
|
67
|
+
|
68
|
+
expect { definition_list.find(:example) }.
|
69
|
+
to raise_error(Payload::UndefinedDependencyError)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'returns an existing definition' do
|
73
|
+
definition = double('definition')
|
74
|
+
definition_list =
|
75
|
+
Payload::DefinitionList.new.add(:example, definition)
|
76
|
+
|
77
|
+
expect(definition_list.find(:example)).to eq(definition)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'payload/exported_definition'
|
3
|
+
require 'payload/testing'
|
4
|
+
|
5
|
+
describe Payload::ExportedDefinition do
|
6
|
+
include Payload::Testing
|
7
|
+
|
8
|
+
describe '#resolve' do
|
9
|
+
it 'finds definitions from both the source and argument containers' do
|
10
|
+
block = lambda do |container|
|
11
|
+
"Ran with #{container[:private]} " \
|
12
|
+
"and #{container[:container]}"
|
13
|
+
end
|
14
|
+
|
15
|
+
private_definition =
|
16
|
+
Payload::ServiceDefinition.new(lambda { |config| 'private' })
|
17
|
+
private_definitions = Payload::DefinitionList.
|
18
|
+
new.
|
19
|
+
add(:private, private_definition)
|
20
|
+
container = build_container.service(:container) { |config| 'container' }
|
21
|
+
definition = Payload::ServiceDefinition.new(block)
|
22
|
+
exported_definition =
|
23
|
+
Payload::ExportedDefinition.new(definition, private_definitions)
|
24
|
+
|
25
|
+
result = exported_definition.resolve(container)
|
26
|
+
|
27
|
+
expect(result).to eq('Ran with private and container')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'payload/factory_definition'
|
3
|
+
require 'payload/testing'
|
4
|
+
|
5
|
+
describe Payload::FactoryDefinition do
|
6
|
+
include Payload::Testing
|
7
|
+
|
8
|
+
describe '#resolve' do
|
9
|
+
it 'returns an object that responds to new' do
|
10
|
+
block = lambda do |config|
|
11
|
+
"#{config[:from_new]} and #{config[:from_container]}"
|
12
|
+
end
|
13
|
+
container = build_container
|
14
|
+
.service(:from_container) { 'From container' }
|
15
|
+
definition = Payload::FactoryDefinition.new(block)
|
16
|
+
|
17
|
+
result = definition.resolve(container).new(from_new: 'From new')
|
18
|
+
|
19
|
+
expect(result).to eq('From new and From container')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#decorate' do
|
24
|
+
it 'returns a decorated object' do
|
25
|
+
block = lambda do |config|
|
26
|
+
"#{config[:from_new]} and #{config[:from_container]}"
|
27
|
+
end
|
28
|
+
first = lambda do |component, config|
|
29
|
+
"Decorated #{component.inspect} with #{config[:from_new]}"
|
30
|
+
end
|
31
|
+
second = lambda do |component, config|
|
32
|
+
"#{component} and #{config[:other]}"
|
33
|
+
end
|
34
|
+
container = build_container
|
35
|
+
.service(:from_container) { 'From container' }
|
36
|
+
.service(:other) { 'Other' }
|
37
|
+
definition = Payload::FactoryDefinition
|
38
|
+
.new(block)
|
39
|
+
.decorate(first)
|
40
|
+
.decorate(second)
|
41
|
+
|
42
|
+
result = definition.resolve(container).new(from_new: 'From new')
|
43
|
+
|
44
|
+
expect(result).to eq(
|
45
|
+
'Decorated "From new and From container" with From new and Other'
|
46
|
+
)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'payload/factory'
|
3
|
+
require 'payload/decorator_chain'
|
4
|
+
require 'payload/testing'
|
5
|
+
|
6
|
+
describe Payload::Factory do
|
7
|
+
include Payload::Testing
|
8
|
+
|
9
|
+
describe '#new' do
|
10
|
+
it 'instantiates the dependency' do
|
11
|
+
decorator = lambda do |component, config|
|
12
|
+
"Decorated #{component.inspect} " \
|
13
|
+
"with #{config[:from_container]} " \
|
14
|
+
"and #{config[:from_new]}"
|
15
|
+
end
|
16
|
+
block = lambda do |config|
|
17
|
+
"Component with #{config[:from_container]} and #{config[:from_new]}"
|
18
|
+
end
|
19
|
+
decorators = Payload::DecoratorChain.new.add(decorator)
|
20
|
+
container = build_container
|
21
|
+
.service(:from_container) { 'From container' }
|
22
|
+
factory = Payload::Factory.new(container, block, decorators)
|
23
|
+
|
24
|
+
result = factory.new(from_new: 'From new')
|
25
|
+
|
26
|
+
expect(result).to eq(
|
27
|
+
'Decorated "Component with From container and From new" ' \
|
28
|
+
'with From container and From new'
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'instantiates the dependency without arguments' do
|
33
|
+
block = lambda do |config|
|
34
|
+
"Component with #{config[:from_container]}"
|
35
|
+
end
|
36
|
+
decorators = Payload::DecoratorChain.new
|
37
|
+
container = build_container
|
38
|
+
.service(:from_container) { 'From container' }
|
39
|
+
factory = Payload::Factory.new(container, block, decorators)
|
40
|
+
|
41
|
+
result = factory.new
|
42
|
+
|
43
|
+
expect(result).to eq('Component with From container')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'payload/mutable_container'
|
3
|
+
|
4
|
+
describe Payload::MutableContainer do
|
5
|
+
%w(service factory decorate).each do |method_name|
|
6
|
+
describe "##{method_name}" do
|
7
|
+
it "redefines the container with a #{method_name}" do
|
8
|
+
base_container = double('base_container')
|
9
|
+
modified_container = double('modified_container')
|
10
|
+
allow(base_container).
|
11
|
+
to receive(method_name).
|
12
|
+
with(:example).
|
13
|
+
and_return(modified_container)
|
14
|
+
container = Payload::MutableContainer.new(base_container)
|
15
|
+
|
16
|
+
result = container.send(method_name, :example)
|
17
|
+
|
18
|
+
expect(container.build).to eq(modified_container)
|
19
|
+
expect(result).to eq(container)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#respond_to_missing?' do
|
25
|
+
context 'for a method defined on its base container' do
|
26
|
+
it 'returns true' do
|
27
|
+
base_container = double('base_container')
|
28
|
+
allow(base_container).to receive(:example)
|
29
|
+
container = Payload::MutableContainer.new(base_container)
|
30
|
+
|
31
|
+
expect(container.method(:example)).not_to be_nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'for an undefined method' do
|
36
|
+
it 'returns false' do
|
37
|
+
base_container = double('base_container')
|
38
|
+
container = Payload::MutableContainer.new(base_container)
|
39
|
+
|
40
|
+
expect { container.method(:unknown) }.to raise_error(NameError)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#export' do
|
46
|
+
it 'adds dependencies to the export list' do
|
47
|
+
exports = double('exports')
|
48
|
+
immutable_container = double('base_container')
|
49
|
+
allow(immutable_container).
|
50
|
+
to receive(:service).
|
51
|
+
and_return(immutable_container)
|
52
|
+
allow(immutable_container).
|
53
|
+
to receive(:export).
|
54
|
+
with(:one, :two, :three).
|
55
|
+
and_return(exports)
|
56
|
+
container = Payload::MutableContainer.new(immutable_container)
|
57
|
+
|
58
|
+
container.service(:one)
|
59
|
+
container.service(:two)
|
60
|
+
container.service(:three)
|
61
|
+
container.service(:unexported)
|
62
|
+
container.export(:one, :two)
|
63
|
+
container.export(:three)
|
64
|
+
|
65
|
+
expect(container.exports).to eq(exports)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|