fabrique 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -0
  3. data/Gemfile +5 -0
  4. data/Guardfile +9 -0
  5. data/README.md +152 -3
  6. data/Rakefile +11 -3
  7. data/config/cucumber.yml +2 -0
  8. data/constructors +70 -0
  9. data/docs/autowiring.yml +23 -0
  10. data/docs/multiple_providers.rb +104 -0
  11. data/fabrique.gemspec +3 -0
  12. data/features/bean_factory.feature +295 -0
  13. data/features/plugin_registry.feature +79 -0
  14. data/features/step_definitions/bean_factory_steps.rb +73 -0
  15. data/features/step_definitions/plugin_registry_steps.rb +207 -0
  16. data/features/support/byebug.rb +4 -0
  17. data/lib/fabrique/argument_adaptor/keyword.rb +19 -0
  18. data/lib/fabrique/argument_adaptor/positional.rb +76 -0
  19. data/lib/fabrique/bean_definition.rb +46 -0
  20. data/lib/fabrique/bean_definition_registry.rb +43 -0
  21. data/lib/fabrique/bean_factory.rb +78 -0
  22. data/lib/fabrique/bean_reference.rb +13 -0
  23. data/lib/fabrique/construction/as_is.rb +16 -0
  24. data/lib/fabrique/construction/builder_method.rb +21 -0
  25. data/lib/fabrique/construction/default.rb +17 -0
  26. data/lib/fabrique/construction/keyword_argument.rb +16 -0
  27. data/lib/fabrique/construction/positional_argument.rb +40 -0
  28. data/lib/fabrique/construction/properties_hash.rb +19 -0
  29. data/lib/fabrique/constructor/identity.rb +10 -0
  30. data/lib/fabrique/cyclic_bean_dependency_error.rb +6 -0
  31. data/lib/fabrique/plugin_registry.rb +56 -0
  32. data/lib/fabrique/test/fixtures/constructors.rb +81 -0
  33. data/lib/fabrique/test/fixtures/modules.rb +35 -0
  34. data/lib/fabrique/test/fixtures/opengl.rb +37 -0
  35. data/lib/fabrique/test/fixtures/repository.rb +139 -0
  36. data/lib/fabrique/test.rb +8 -0
  37. data/lib/fabrique/version.rb +1 -1
  38. data/lib/fabrique/yaml_bean_factory.rb +42 -0
  39. data/lib/fabrique.rb +4 -2
  40. data/spec/fabrique/argument_adaptor/keyword_spec.rb +50 -0
  41. data/spec/fabrique/argument_adaptor/positional_spec.rb +166 -0
  42. data/spec/fabrique/construction/as_is_spec.rb +23 -0
  43. data/spec/fabrique/construction/builder_method_spec.rb +29 -0
  44. data/spec/fabrique/construction/default_spec.rb +19 -0
  45. data/spec/fabrique/construction/positional_argument_spec.rb +61 -0
  46. data/spec/fabrique/construction/properties_hash_spec.rb +36 -0
  47. data/spec/fabrique/constructor/identity_spec.rb +4 -0
  48. data/spec/fabrique/plugin_registry_spec.rb +78 -0
  49. data/spec/fabrique_spec.rb +0 -4
  50. metadata +72 -4
@@ -0,0 +1,139 @@
1
+ module Fabrique
2
+
3
+ module Test
4
+
5
+ module Fixtures
6
+
7
+ module Repository
8
+
9
+ class Customer
10
+
11
+ attr_reader :id, :name, :date_of_birth
12
+
13
+ def initialize(id: nil, name: nil, date_of_birth: nil)
14
+ @id, @name, @date_of_birth = id, name, date_of_birth
15
+ end
16
+
17
+ end
18
+
19
+ class CustomerRepository
20
+
21
+ # Exposed for testing
22
+ attr_reader :store, :data_mapper
23
+
24
+ def initialize(store, data_mapper)
25
+ @store, @data_mapper = store, data_mapper
26
+ end
27
+
28
+ def persist(entity)
29
+ id = @store.save(:customer, @data_mapper.to_dto(entity))
30
+ if !id.nil?
31
+ entity.id = id
32
+ end
33
+ end
34
+
35
+ def locate(entity_id)
36
+ @data_mapper.from_dto(@store.find(:customer, entity_id))
37
+ end
38
+
39
+ end
40
+
41
+ class ProductRepository
42
+
43
+ # Exposed for testing
44
+ attr_reader :store, :data_mapper
45
+
46
+ def initialize(store: nil, data_mapper: nil)
47
+ @store, @data_mapper = store, data_mapper
48
+ end
49
+
50
+ def persist(entity)
51
+ id = @store.save(:customer, @data_mapper.to_dto(entity))
52
+ if !id.nil?
53
+ entity.id = id
54
+ end
55
+ end
56
+
57
+ def search(filter)
58
+ @data_mapper.from_dto(@store.search(:customer, filter))
59
+ end
60
+
61
+ end
62
+
63
+ class MysqlConnection
64
+
65
+ def initialize(*args)
66
+ # ...
67
+ end
68
+
69
+ def method_missing(method_sym, *arguments)
70
+ 42
71
+ end
72
+
73
+ end
74
+
75
+ class MysqlStore
76
+
77
+ def initialize(host: 'localhost', port: 3306, username: nil, password: nil)
78
+ @connection = MysqlConnection.new(host, port, username, password)
79
+ end
80
+
81
+ def find(table, id)
82
+ @connection.find(table, "WHERE customer_id = ?", [id])
83
+ end
84
+
85
+ def search(table, filter)
86
+ clauses, bindings = [], []
87
+ filter.each do |k, v|
88
+ clauses << k
89
+ bindings << v
90
+ end
91
+ @connection.select(table, clauses.join(" AND "), bindings)
92
+ end
93
+
94
+ def save(table, record)
95
+ @connection.update_or_insert(table, record)
96
+ end
97
+
98
+ end
99
+
100
+ require "date"
101
+ class CustomerDataMapper
102
+
103
+ def to_dto(entity)
104
+ {
105
+ id: entity.id,
106
+ name: entity.name,
107
+ dob: entity.date_of_birth.iso8601
108
+ }
109
+ end
110
+
111
+ def from_dto(dto)
112
+ Customer.new(id: dto[:id], name: dto[:name], date_of_birth: Date.parse(dto[:dob]))
113
+ end
114
+
115
+ end
116
+
117
+ class ProductDataMapper
118
+
119
+ def to_dto(entity)
120
+ {
121
+ id: entity.id,
122
+ name: entity.name,
123
+ price: entity.price,
124
+ }
125
+ end
126
+
127
+ def from_dto(dto)
128
+ Product.new(id: dto[:id], name: dto[:name], price: dto[:price])
129
+ end
130
+
131
+ end
132
+
133
+ end
134
+
135
+ end
136
+
137
+ end
138
+
139
+ end
@@ -0,0 +1,8 @@
1
+ Dir[File.join(File.dirname(__FILE__), "test", "**", "*.rb")].each { |f| require_relative f }
2
+
3
+ module Fabrique
4
+
5
+ module Test
6
+ end
7
+
8
+ end
@@ -1,3 +1,3 @@
1
1
  module Fabrique
2
- VERSION = "0.0.0"
2
+ VERSION = "0.0.1"
3
3
  end
@@ -0,0 +1,42 @@
1
+ require "yaml"
2
+ require_relative "bean_definition_registry"
3
+ require_relative "bean_definition"
4
+ require_relative "bean_factory"
5
+ require_relative "bean_reference"
6
+
7
+ module Fabrique
8
+
9
+ YAML.add_domain_type("starjuice.net,2015-03-13", "beans") do |type, value|
10
+ BeanDefinitionRegistry.new(value)
11
+ end
12
+
13
+ YAML.add_domain_type("starjuice.net,2015-03-13", "bean") do |type, value|
14
+ BeanDefinition.new(value)
15
+ end
16
+
17
+ YAML.add_domain_type("starjuice.net,2015-03-13", "bean/ref") do |type, value|
18
+ BeanReference.new(value)
19
+ end
20
+
21
+ class YamlBeanFactory < BeanFactory
22
+
23
+ def initialize(pathname)
24
+ data = YAML.load_file(pathname)
25
+ if data.respond_to?(:keys) and data["beans"]
26
+ beans = data["beans"]
27
+ else
28
+ raise "YAML contains no top-level beans node"
29
+ end
30
+
31
+ if beans.is_a?(BeanDefinitionRegistry)
32
+ super(beans)
33
+ elsif beans.is_a?(Array)
34
+ super(BeanDefinitionRegistry.new(beans))
35
+ else
36
+ raise "YAML top-level beans node must be an Array or a #{BeanDefinitionRegistry}"
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ end
data/lib/fabrique.rb CHANGED
@@ -1,5 +1,7 @@
1
- require "fabrique/version"
1
+ Dir[File.join(File.dirname(__FILE__), "fabrique", "*.rb")].each { |f| require_relative f }
2
+ Dir[File.join(File.dirname(__FILE__), "fabrique", "construction", "*.rb")].each { |f| require_relative f }
3
+ Dir[File.join(File.dirname(__FILE__), "fabrique", "constructor", "*.rb")].each { |f| require_relative f }
4
+ Dir[File.join(File.dirname(__FILE__), "fabrique", "argument_adaptor", "*.rb")].each { |f| require_relative f }
2
5
 
3
6
  module Fabrique
4
- # Your code goes here...
5
7
  end
@@ -0,0 +1,50 @@
1
+ require "spec_helper"
2
+ require "fabrique/test"
3
+
4
+ describe Fabrique::ArgumentAdaptor::Keyword do
5
+
6
+ let(:properties) { {size: "small", color: "red", shape: "dot"} }
7
+
8
+ describe "#adapt(*properties)" do
9
+
10
+ context "when called without properties" do
11
+
12
+ it "returns an empty array" do
13
+ expect(subject.adapt).to eql []
14
+ end
15
+
16
+ end
17
+
18
+ context "when called with empty properties" do
19
+
20
+ it "returns an empty hash in an empty array" do
21
+ expect(subject.adapt({})).to eql [{}]
22
+ end
23
+
24
+ end
25
+
26
+ context "when called with properties" do
27
+
28
+ it "returns a hash containing the properties" do
29
+ expect(subject.adapt(properties)).to eql [properties]
30
+ end
31
+
32
+ end
33
+
34
+ it "supports properties hash constructors" do
35
+ object = Fabrique::Test::Fixtures::Constructors::ClassWithPropertiesHashConstructor.new(*subject.adapt(properties))
36
+ expect(object.size).to eql properties[:size]
37
+ expect(object.color).to eql properties[:color]
38
+ expect(object.shape).to eql properties[:shape]
39
+ end
40
+
41
+ it "supports keyword argument constructors" do
42
+ object = Fabrique::Test::Fixtures::Constructors::ClassWithKeywordArgumentConstructor.new(*subject.adapt(properties))
43
+ expect(object.size).to eql properties[:size]
44
+ expect(object.color).to eql properties[:color]
45
+ expect(object.shape).to eql properties[:shape]
46
+ end
47
+
48
+ end
49
+
50
+ end
@@ -0,0 +1,166 @@
1
+ require "spec_helper"
2
+
3
+ describe Fabrique::ArgumentAdaptor::Positional do
4
+
5
+ describe "::new(*argument_specifieres)" do
6
+
7
+ it "takes symbols as required argument names" do
8
+ expect { described_class.new(:arg_name1, :arg_name2, :arg_name3) }.to_not raise_error
9
+ end
10
+
11
+ it "takes one-element arrays as optional argument names with no default value" do
12
+ expect { described_class.new(:arg_name1, :arg_name2, [:arg_name3]) }.to_not raise_error
13
+ end
14
+
15
+ it "takes two-element arrays as optional argument names with a default value" do
16
+ expect { described_class.new(:arg_name1, [:arg_name2], [:arg_name3, "default arg3"]) }.to_not raise_error
17
+ end
18
+
19
+ it "allows no argument names (useful for a default constructor)" do
20
+ expect { described_class.new }.to_not raise_error
21
+ end
22
+
23
+ context "when passed arguments that are not symbols or optional argument specifier arrays" do
24
+
25
+ it "raises an ArgumentError" do
26
+ expect { described_class.new(:arg_name1, "arg_name2") }.to raise_error(ArgumentError, /invalid argument specifier/)
27
+ expect { described_class.new(:arg_name1, []) }.to raise_error(ArgumentError, /invalid argument specifier/)
28
+ expect { described_class.new(:arg_name1, ["arg_name2"]) }.to raise_error(ArgumentError, /invalid argument specifier/)
29
+ expect { described_class.new(:arg_name1, [:arg_name2, "value", "nonsense"]) }.to raise_error(ArgumentError, /invalid argument specifier/)
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+
36
+ describe "#adapt(*properties)" do
37
+
38
+ context "when initialized with no required argument specifiers" do
39
+ context "when called without properties" do
40
+ it "returns an empty array" do
41
+ expect(subject.adapt).to eql []
42
+ end
43
+ end
44
+
45
+ context "when called with empty properties" do
46
+ it "returns an empty array" do
47
+ expect(subject.adapt({})).to eql []
48
+ end
49
+ end
50
+
51
+ context "when called with properties" do
52
+ it "returns an empty array" do
53
+ expect(subject.adapt(size: "small", color: "red", shape: "dot")).to eql []
54
+ end
55
+ end
56
+ end
57
+
58
+ context "when initialized with only required argument names" do
59
+ subject { described_class.new(:size, :color, :shape) }
60
+
61
+ context "when called with a property for each argument name" do
62
+ it "returns an array of the property value of each argument name in the order specified to new()" do
63
+ expect(subject.adapt(shape: "dot", size: "small", color: "red")).to eql ["small", "red", "dot"]
64
+ end
65
+ end
66
+
67
+ context "when called with at least one property missing for an argument name" do
68
+ it "raises an ArgumentError" do
69
+ expect { subject.adapt(size: "small", color: "red") }.to raise_error(ArgumentError, /required argument \w+ missing from properties/)
70
+ end
71
+ end
72
+
73
+ context "when called with extraneous properties" do
74
+ it "ignores them, returning an array of the property value of each argument name in the order specified to new()" do
75
+ expect(subject.adapt(size: "small", color: "red", shape: "dot", status: "on")).to eql ["small", "red", "dot"]
76
+ end
77
+ end
78
+ end
79
+
80
+ context "when initialized with only optional argument names with default values" do
81
+ subject { described_class.new([:size, "default size"], [:color, "default color"], [:shape, "default shape"]) }
82
+
83
+ context "when called with a property for each argument name" do
84
+ it "returns an array of the property value of each argument name in the order specified to new()" do
85
+ expect(subject.adapt(shape: "dot", size: "small", color: "red")).to eql ["small", "red", "dot"]
86
+ end
87
+ end
88
+
89
+ context "when called with at least one property missing for an argument name" do
90
+ it "returns an array of the provided property values and default values, in the order specified to new()" do
91
+ expect(subject.adapt(size: "small", color: "red")).to eql ["small", "red", "default shape"]
92
+ end
93
+ end
94
+
95
+ context "when called with extraneous properties" do
96
+ it "ignores them" do
97
+ expect(subject.adapt(size: "small", color: "red", status: "on")).to eql ["small", "red", "default shape"]
98
+ end
99
+ end
100
+
101
+ end
102
+
103
+ context "when initialized with optional argument names with no default values, followed by required argument names" do
104
+ subject { described_class.new([:size], [:color], :shape) }
105
+
106
+ context "when called with a property for each argument name" do
107
+ it "returns an array of the property value of each argument name in the order specified to new()" do
108
+ expect(subject.adapt(shape: "dot", size: "small", color: "red")).to eql ["small", "red", "dot"]
109
+ end
110
+ end
111
+
112
+ context "when called with at least one property missing for an optional argument" do
113
+ it "raises an ArgumentError (because the caller would be surprised by Ruby filling optional arguments from left to right)" do
114
+ expect {subject.adapt(color: "red", shape: "dot") }.to raise_error(ArgumentError, /optional argument size \(with no default\) missing from properties/)
115
+ end
116
+ end
117
+
118
+ context "when called with at least one property missing for a required argument" do
119
+ it "raises an ArgumentError" do
120
+ expect { subject.adapt(size: "small", color: "red") }.to raise_error(ArgumentError, /required argument \w+ missing from properties/)
121
+ end
122
+ end
123
+ end
124
+
125
+ context "when initialized with optional argument names with nil as the default value, followed by required argument names" do
126
+ subject { described_class.new([:size, nil], [:color, nil], :shape) }
127
+
128
+ context "when called with a property for each argument name" do
129
+ it "returns an array of the property value of each argument name in the order specified to new()" do
130
+ expect(subject.adapt(shape: "dot", size: "small", color: "red")).to eql ["small", "red", "dot"]
131
+ end
132
+ end
133
+
134
+ context "when called with at least one property missing for an optional argument" do
135
+ it "returns an array of the provided property values and default values, in the order specified to new()" do
136
+ expect(subject.adapt(color: "red", shape: "dot")).to eql [nil, "red", "dot"]
137
+ end
138
+ end
139
+
140
+ context "when called with at least one property missing for a required argument" do
141
+ it "raises an ArgumentError" do
142
+ expect { subject.adapt(size: "small", color: "red") }.to raise_error(ArgumentError, /required argument \w+ missing from properties/)
143
+ end
144
+ end
145
+ end
146
+
147
+ it "supports default constructors" do
148
+ klass = Fabrique::Test::Fixtures::Constructors::ClassWithDefaultConstructor
149
+ object = klass.new(*subject.adapt())
150
+ expect(object.size).to eql klass::DEFAULT_SIZE
151
+ expect(object.color).to eql klass::DEFAULT_COLOR
152
+ expect(object.shape).to eql klass::DEFAULT_SHAPE
153
+ end
154
+
155
+ it "supports positional argument constructors" do
156
+ klass = Fabrique::Test::Fixtures::Constructors::ClassWithPositionalArgumentConstructor
157
+ subject = described_class.new(:size, :color, :shape)
158
+ object = klass.new(*subject.adapt(size: "small", color: "red", shape: "dot"))
159
+ expect(object.size).to eql "small"
160
+ expect(object.color).to eql "red"
161
+ expect(object.shape).to eql "dot"
162
+ end
163
+
164
+ end
165
+
166
+ end
@@ -0,0 +1,23 @@
1
+ require "spec_helper"
2
+
3
+ describe Fabrique::Construction::AsIs do
4
+
5
+ describe "call(type, properties = nil)" do
6
+
7
+ it "applies identity to the type" do
8
+ constructed = subject.call(type = Object.new)
9
+ expect(constructed.object_id).to eql type.object_id
10
+ end
11
+
12
+ it "accepts optional properties to support construction interface" do
13
+ constructed = subject.call(type = Object.new, {})
14
+ expect(constructed.object_id).to eql type.object_id
15
+ end
16
+
17
+ it "raises an ArgumentError if properties is specified and not empty" do
18
+ expect { subject.call(Object.new, {some: "properties"}) }.to raise_error(ArgumentError, /unexpected properties/)
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,29 @@
1
+ require "spec_helper"
2
+ require "fabrique/test"
3
+
4
+ describe Fabrique::Construction::BuilderMethod do
5
+
6
+ describe "call(type, properties = nil)" do
7
+
8
+ context "when initialized with a builder method name and builder runner block" do
9
+
10
+ subject do
11
+ described_class.new(:build) do |builder, properties|
12
+ builder.size = properties[:size]
13
+ builder.color = properties[:color]
14
+ builder.shape = properties[:shape]
15
+ end
16
+ end
17
+
18
+ it "calls the builder method on the type and yields the builder and the specified properties to the builder runner block" do
19
+ o = subject.call(Fabrique::Test::Fixtures::Constructors::ClassWithBuilderMethod, size: "huge", color: "black", shape: "hole")
20
+ expect(o.size).to eql "huge"
21
+ expect(o.color).to eql "black"
22
+ expect(o.shape).to eql "hole"
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,19 @@
1
+ require "spec_helper"
2
+
3
+ describe Fabrique::Construction::Default do
4
+
5
+ describe "call(type, properties = nil)" do
6
+
7
+ it "calls type.new() without arguments" do
8
+ constructed = subject.call(type = Object)
9
+ expect(constructed.class).to eql type
10
+ end
11
+
12
+ it "accepts optional properties to support construction interface" do
13
+ constructed = subject.call(type = Object, {})
14
+ expect(constructed.class).to eql type
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,61 @@
1
+ require "spec_helper"
2
+
3
+ class PositionalArgumentFixture
4
+ attr_reader :color, :shape, :size
5
+
6
+ DEFAULT_ATTRS = {color: "red", shape: "dot", size: "small"} unless defined?(DEFAULT_ATTRS)
7
+
8
+ def initialize(color = DEFAULT_ATTRS[:color], shape = DEFAULT_ATTRS[:shape], size = DEFAULT_ATTRS[:size])
9
+ @color, @shape, @size = color, shape, size
10
+ end
11
+
12
+ def attrs
13
+ {color: @color, shape: @shape, size: @size}
14
+ end
15
+
16
+ end
17
+
18
+ describe Fabrique::Construction::PositionalArgument do
19
+
20
+ let(:type) { PositionalArgumentFixture }
21
+
22
+ describe "call(type, properties = nil)" do
23
+
24
+ it "applies positional argument construction to the type, in the order they were provided to new()" do
25
+ subject = described_class.new(:color, :shape, :size)
26
+ constructed = subject.call(type, {size: "tiny", color: "purple", shape: "dot"})
27
+ expect(constructed.attrs).to eql({color: "purple", shape: "dot", size: "tiny"})
28
+ end
29
+
30
+ it "calls type.new() if no arguments were specified to new()" do
31
+ subject = described_class.new()
32
+ type = spy('type')
33
+ subject.call(type, {size: "tiny", color: "purple", shape: "dot"})
34
+ expect(type).to have_received(:new).with(no_args)
35
+ end
36
+
37
+ context "when one or more optional arguments were specified to new()" do
38
+
39
+ subject = described_class.new(:color, [:shape, :size])
40
+
41
+ it "passes optional arguments provided in properties" do
42
+ type = spy('type')
43
+ subject.call(type, {color: "purple", shape: "dot", size: "tiny"})
44
+ expect(type).to have_received(:new).with("purple", "dot", "tiny")
45
+ end
46
+
47
+ it "discards optional arguments if they are not present in the properties" do
48
+ type = spy('type')
49
+ subject.call(type, {size: "tiny", color: "purple"})
50
+ expect(type).to have_received(:new).with("purple", "tiny")
51
+ end
52
+
53
+ it "raises an ArgumentError if required arguments are not present in the properties" do
54
+ expect { subject.call(Object, shape: "dot", size: "tiny") }.to raise_error(ArgumentError, /required argument color missing/)
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+
61
+ end
@@ -0,0 +1,36 @@
1
+ require "spec_helper"
2
+
3
+ class PropertiesHashFixture
4
+ attr_reader :color, :shape, :size
5
+
6
+ DEFAULT_ATTRS = {color: "red", shape: "dot", size: "small"} unless defined?(DEFAULT_ATTRS)
7
+
8
+ def initialize(properties = DEFAULT_ATTRS)
9
+ @color, @shape, @size = properties[:color], properties[:shape], properties[:size]
10
+ end
11
+
12
+ def attrs
13
+ {color: @color, shape: @shape, size: @size}
14
+ end
15
+
16
+ end
17
+
18
+ describe Fabrique::Construction::PropertiesHash do
19
+
20
+ describe "call(type, properties = nil)" do
21
+
22
+ let(:type) { PropertiesHashFixture }
23
+
24
+ it "calls type.new() if properties is not provided" do
25
+ o = subject.call(type)
26
+ expect(o.attrs).to eql type::DEFAULT_ATTRS
27
+ end
28
+
29
+ it "calls type.new(properties) if properties is provided" do
30
+ o = subject.call(type, properties = {color: "green", shape: "patch", size: "large"})
31
+ expect(o.attrs).to eql properties
32
+ end
33
+
34
+ end
35
+
36
+ end
@@ -0,0 +1,4 @@
1
+ require "spec_helper"
2
+
3
+ describe Fabrique::Constructor::Identity do
4
+ end
@@ -0,0 +1,78 @@
1
+ require "spec_helper"
2
+
3
+ describe Fabrique::PluginRegistry do
4
+
5
+ subject { described_class.new("Test Plugin Factory") }
6
+ let(:constructor) { spy("constructor") }
7
+
8
+ describe "#register(id, type, constructor)" do
9
+
10
+ it "applies the strategy pattern to construction" do
11
+ subject.register(:my_plugin, type = Class.new, constructor)
12
+ subject.acquire(:my_plugin, {some: "properties"})
13
+ expect(constructor).to have_received(:call).with(type, {some: "properties"})
14
+ end
15
+
16
+ it "returns true on success (to avoid leaking registration internals)" do
17
+ expect(subject.register(:my_plugin, Class.new, constructor)).to be true
18
+ end
19
+
20
+ context "when the unique identity has already been registered" do
21
+
22
+ let(:existing_type) { Object.new }
23
+ before(:each) { subject.register(:existing, existing_type, constructor) }
24
+
25
+ it "raises an ArgumentError" do
26
+ expect {
27
+ subject.register(:existing, Object.new, double("constructor").as_null_object)
28
+ }.to raise_error(ArgumentError, /#{existing_type} already registered/)
29
+ end
30
+
31
+ it "leaves the original registration intact" do
32
+ begin
33
+ subject.register(:existing, Object.new, double("constructor").as_null_object)
34
+ rescue
35
+ end
36
+ subject.acquire(:existing)
37
+ expect(constructor).to have_received(:call).with(existing_type)
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+
44
+ describe "#acquire(id, properties = {})" do
45
+
46
+ let(:type) { Object }
47
+ before(:each) { subject.register(:my_plugin, type, constructor) }
48
+
49
+ it "applies the registered constructor to the registered type with the given properties" do
50
+ subject.acquire(:my_plugin, properties = {some: "properties"})
51
+ expect(constructor).to have_received(:call).with(type, properties)
52
+ end
53
+
54
+ it "applies the registered constructor to the registered type only if no properties are given" do
55
+ subject.acquire(:my_plugin)
56
+ expect(constructor).to have_received(:call).with(type)
57
+ end
58
+
59
+ it "returns the constructor call's return value" do
60
+ allow(constructor).to receive(:call).and_return(plugin = type.new)
61
+ expect(subject.acquire(:my_plugin)).to be plugin
62
+ end
63
+
64
+ context "when the unique identity has not yet been registered" do
65
+
66
+ before(:each) { subject.send(:unregister, :my_plugin) }
67
+
68
+ it "raises an ArgumentError" do
69
+ expect {
70
+ subject.acquire(:my_plugin)
71
+ }.to raise_error(ArgumentError, /not registered/)
72
+ end
73
+
74
+ end
75
+
76
+ end
77
+
78
+ end