fabrique 0.3.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -2
  3. data/Gemfile +4 -0
  4. data/README.md +2 -155
  5. data/fabrique.gemspec +3 -3
  6. data/features/bean_factory.feature +194 -3
  7. data/features/step_definitions/bean_factory_steps.rb +41 -3
  8. data/features/support/fabrique.rb +1 -0
  9. data/fixtures/local_only/.gitignore +9 -0
  10. data/fixtures/local_only/Gemfile +4 -0
  11. data/fixtures/local_only/README.md +36 -0
  12. data/fixtures/local_only/Rakefile +2 -0
  13. data/fixtures/local_only/bin/console +14 -0
  14. data/fixtures/local_only/bin/setup +8 -0
  15. data/fixtures/local_only/lib/local_only/version.rb +3 -0
  16. data/fixtures/local_only/lib/local_only.rb +7 -0
  17. data/fixtures/local_only/local_only.gemspec +31 -0
  18. data/fixtures/local_only-0.1.0.gem +0 -0
  19. data/fixtures/sample/.gitignore +9 -0
  20. data/fixtures/sample/Gemfile +4 -0
  21. data/fixtures/sample/README.md +36 -0
  22. data/fixtures/sample/Rakefile +2 -0
  23. data/fixtures/sample/bin/console +14 -0
  24. data/fixtures/sample/bin/setup +8 -0
  25. data/fixtures/sample/lib/sample/version.rb +3 -0
  26. data/fixtures/sample/lib/sample.rb +9 -0
  27. data/fixtures/sample/sample.gemspec +23 -0
  28. data/lib/fabrique/bean_definition.rb +4 -4
  29. data/lib/fabrique/bean_definition_registry.rb +11 -5
  30. data/lib/fabrique/bean_factory.rb +34 -13
  31. data/lib/fabrique/bean_property_reference.rb +9 -1
  32. data/lib/fabrique/data_bean.rb +62 -0
  33. data/lib/fabrique/gem_definition.rb +15 -0
  34. data/lib/fabrique/gem_dependency_error.rb +6 -0
  35. data/lib/fabrique/gem_loader.rb +25 -0
  36. data/lib/fabrique/test/fixtures/constructors.rb +12 -1
  37. data/lib/fabrique/version.rb +1 -1
  38. data/lib/fabrique.rb +0 -3
  39. data/spec/fabrique/data_bean_spec.rb +129 -0
  40. metadata +32 -38
  41. data/features/plugin_registry.feature +0 -79
  42. data/features/step_definitions/plugin_registry_steps.rb +0 -207
  43. data/lib/fabrique/argument_adaptor/keyword.rb +0 -19
  44. data/lib/fabrique/argument_adaptor/positional.rb +0 -76
  45. data/lib/fabrique/construction/as_is.rb +0 -16
  46. data/lib/fabrique/construction/builder_method.rb +0 -21
  47. data/lib/fabrique/construction/default.rb +0 -17
  48. data/lib/fabrique/construction/keyword_argument.rb +0 -16
  49. data/lib/fabrique/construction/positional_argument.rb +0 -40
  50. data/lib/fabrique/construction/properties_hash.rb +0 -19
  51. data/lib/fabrique/constructor/identity.rb +0 -10
  52. data/lib/fabrique/plugin_registry.rb +0 -56
  53. data/spec/fabrique/argument_adaptor/keyword_spec.rb +0 -50
  54. data/spec/fabrique/argument_adaptor/positional_spec.rb +0 -166
  55. data/spec/fabrique/construction/as_is_spec.rb +0 -23
  56. data/spec/fabrique/construction/builder_method_spec.rb +0 -29
  57. data/spec/fabrique/construction/default_spec.rb +0 -19
  58. data/spec/fabrique/construction/positional_argument_spec.rb +0 -61
  59. data/spec/fabrique/construction/properties_hash_spec.rb +0 -36
  60. data/spec/fabrique/constructor/identity_spec.rb +0 -4
  61. data/spec/fabrique/plugin_registry_spec.rb +0 -78
@@ -1,56 +0,0 @@
1
- module Fabrique
2
-
3
- class PluginRegistry
4
-
5
- def initialize(name)
6
- @name = name
7
- @registrations = []
8
- end
9
-
10
- def register(id, type, constructor)
11
- if existing = find_registration(id)
12
- raise ArgumentError, "could not register #{type} as #{id} in #{@name}: #{existing.type} already registered as #{id}"
13
- end
14
- @registrations << Registration.new(id, type, constructor)
15
- true
16
- end
17
-
18
- def acquire(id, properties = nil)
19
- if registration = find_registration(id)
20
- registration.call_constructor(properties)
21
- else
22
- raise ArgumentError, "#{id} not registered in #{@name}"
23
- end
24
- end
25
-
26
- private
27
-
28
- def find_registration(id)
29
- @registrations.detect { |r| r.id == id }
30
- end
31
-
32
- def unregister(id)
33
- @registrations.delete(find_registration(id))
34
- end
35
-
36
- class Registration
37
-
38
- attr_reader :id, :type, :constructor
39
-
40
- def initialize(id, type, constructor)
41
- @id, @type, @constructor = id, type, constructor
42
- end
43
-
44
- def call_constructor(properties = nil)
45
- # TODO Push conditional into construction helpers
46
- if properties.nil?
47
- @constructor.call(@type)
48
- else
49
- @constructor.call(@type, properties)
50
- end
51
- end
52
-
53
- end
54
- end
55
-
56
- end
@@ -1,50 +0,0 @@
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
@@ -1,166 +0,0 @@
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
@@ -1,23 +0,0 @@
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
@@ -1,29 +0,0 @@
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
@@ -1,19 +0,0 @@
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
@@ -1,61 +0,0 @@
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
@@ -1,36 +0,0 @@
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
@@ -1,4 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe Fabrique::Constructor::Identity do
4
- end
@@ -1,78 +0,0 @@
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