fabrique 0.3.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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