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.
- checksums.yaml +4 -4
- data/.travis.yml +3 -0
- data/Gemfile +5 -0
- data/Guardfile +9 -0
- data/README.md +152 -3
- data/Rakefile +11 -3
- data/config/cucumber.yml +2 -0
- data/constructors +70 -0
- data/docs/autowiring.yml +23 -0
- data/docs/multiple_providers.rb +104 -0
- data/fabrique.gemspec +3 -0
- data/features/bean_factory.feature +295 -0
- data/features/plugin_registry.feature +79 -0
- data/features/step_definitions/bean_factory_steps.rb +73 -0
- data/features/step_definitions/plugin_registry_steps.rb +207 -0
- data/features/support/byebug.rb +4 -0
- data/lib/fabrique/argument_adaptor/keyword.rb +19 -0
- data/lib/fabrique/argument_adaptor/positional.rb +76 -0
- data/lib/fabrique/bean_definition.rb +46 -0
- data/lib/fabrique/bean_definition_registry.rb +43 -0
- data/lib/fabrique/bean_factory.rb +78 -0
- data/lib/fabrique/bean_reference.rb +13 -0
- data/lib/fabrique/construction/as_is.rb +16 -0
- data/lib/fabrique/construction/builder_method.rb +21 -0
- data/lib/fabrique/construction/default.rb +17 -0
- data/lib/fabrique/construction/keyword_argument.rb +16 -0
- data/lib/fabrique/construction/positional_argument.rb +40 -0
- data/lib/fabrique/construction/properties_hash.rb +19 -0
- data/lib/fabrique/constructor/identity.rb +10 -0
- data/lib/fabrique/cyclic_bean_dependency_error.rb +6 -0
- data/lib/fabrique/plugin_registry.rb +56 -0
- data/lib/fabrique/test/fixtures/constructors.rb +81 -0
- data/lib/fabrique/test/fixtures/modules.rb +35 -0
- data/lib/fabrique/test/fixtures/opengl.rb +37 -0
- data/lib/fabrique/test/fixtures/repository.rb +139 -0
- data/lib/fabrique/test.rb +8 -0
- data/lib/fabrique/version.rb +1 -1
- data/lib/fabrique/yaml_bean_factory.rb +42 -0
- data/lib/fabrique.rb +4 -2
- data/spec/fabrique/argument_adaptor/keyword_spec.rb +50 -0
- data/spec/fabrique/argument_adaptor/positional_spec.rb +166 -0
- data/spec/fabrique/construction/as_is_spec.rb +23 -0
- data/spec/fabrique/construction/builder_method_spec.rb +29 -0
- data/spec/fabrique/construction/default_spec.rb +19 -0
- data/spec/fabrique/construction/positional_argument_spec.rb +61 -0
- data/spec/fabrique/construction/properties_hash_spec.rb +36 -0
- data/spec/fabrique/constructor/identity_spec.rb +4 -0
- data/spec/fabrique/plugin_registry_spec.rb +78 -0
- data/spec/fabrique_spec.rb +0 -4
- 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
|
data/lib/fabrique/version.rb
CHANGED
@@ -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
|
-
|
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,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
|