fabrique 0.0.0 → 0.0.1

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.
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