conject 0.1.6 → 0.1.7
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.
- data/CHANGELOG +5 -1
- data/README.md +24 -1
- data/lib/conject/dependency_resolver.rb +3 -2
- data/lib/conject/object_factory.rb +14 -5
- data/lib/conject/version.rb +1 -1
- data/spec/acceptance/regression/specialize_spec.rb +60 -0
- data/spec/conject/dependency_resolver_spec.rb +18 -4
- data/spec/conject/object_factory_spec.rb +62 -7
- data/spec/test_data/specialization/batman.rb +4 -0
- data/spec/test_data/specialization/clock.rb +2 -0
- data/spec/test_data/specialization/finn.rb +4 -0
- data/spec/test_data/specialization/floor.rb +2 -0
- data/spec/test_data/specialization/jake.rb +4 -0
- data/spec/test_data/specialization/robin.rb +4 -0
- data/spec/test_data/specialization/team.rb +5 -0
- metadata +20 -4
data/CHANGELOG
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
Conject v0.1.7
|
2
|
+
* Added :class and :specialize object configurations
|
3
|
+
* Fixed a bug in the error handler that catches exceptions in object constructors
|
4
|
+
|
1
5
|
Conject v0.1.6
|
2
6
|
* Object#contextual_name provides the string name of an object as it is keyed in its context
|
3
7
|
|
@@ -25,7 +29,7 @@ Conject v0.0.9
|
|
25
29
|
* Classes with module namespaces can now refer to collaborators in construct_with by omitting the full path corresponding to the object namespace
|
26
30
|
|
27
31
|
Conject v0.0.8
|
28
|
-
*
|
32
|
+
* object_peers lets you declare within one object that if some subcontext should request an object, that object should be instantiated in THIS context
|
29
33
|
|
30
34
|
Conject v0.0.7
|
31
35
|
(re-release due to release automation fail on my part)
|
data/README.md
CHANGED
@@ -78,7 +78,7 @@ object peers within the class definition of that owner object. This ensures tha
|
|
78
78
|
objects to be instantiated in those subcontexts as well.
|
79
79
|
|
80
80
|
class Game
|
81
|
-
|
81
|
+
object_peers: :missile_coordinator, :wind_affector
|
82
82
|
end
|
83
83
|
|
84
84
|
In this example, the instantiation of a Game instance in an object context will cause missile coordinator and wind affector to be "anchored"
|
@@ -104,3 +104,26 @@ This is more or less expressive shorthand for:
|
|
104
104
|
...EXCEPT that it's a lazy approach: using 'is' means nothing actually gets built or set in the context before it
|
105
105
|
is requested or composed into another object.
|
106
106
|
|
107
|
+
# Specialization via Composition#
|
108
|
+
|
109
|
+
To declare objects as instances of a specific class, you may specify the :class of the object via #configure_objects.
|
110
|
+
Furthermore, you can specialize the instance by indicating which specific objects you would like to supply as the object's collaborators:
|
111
|
+
|
112
|
+
context.configure_objects({
|
113
|
+
adventure_time: {
|
114
|
+
class: Team,
|
115
|
+
specialize: {
|
116
|
+
hero: "finn",
|
117
|
+
sidekick: "jake" }},
|
118
|
+
dark_knight: {
|
119
|
+
class: Team,
|
120
|
+
specialize: {
|
121
|
+
hero: "batman",
|
122
|
+
sidekick: "robin" }}
|
123
|
+
})
|
124
|
+
|
125
|
+
The keys in :specialize match some or all of the keys defined in the class (in this case, the Team class is defined to be #construct_with :hero, :sidekick, :clock.
|
126
|
+
The values are object names within the ObjectContext; they will be resolved in the usual manner.
|
127
|
+
Components that a Class wants to be built with, but which you omit from the :specialize Hash, will be resolved in the usual way.
|
128
|
+
|
129
|
+
Using :class and :specialize is NOT THE SAME as building objects within a subcontext. Objects mentioned here are expected to be found and kept within the defining ObjectContext.
|
@@ -9,9 +9,10 @@ module Conject
|
|
9
9
|
# This method assumes the Class has_object_defintion? (Client code should
|
10
10
|
# determine that before invoking this method.)
|
11
11
|
#
|
12
|
-
def resolve_for_class(klass, object_context)
|
12
|
+
def resolve_for_class(klass, object_context, remapping=nil)
|
13
|
+
remapping ||= {}
|
13
14
|
klass.object_definition.component_names.inject({}) do |obj_map, name|
|
14
|
-
obj_map[name] = search_for(klass, object_context, name)
|
15
|
+
obj_map[name] = search_for(klass, object_context, remapping[name.to_sym] || remapping[name.to_s] || name)
|
15
16
|
obj_map
|
16
17
|
end
|
17
18
|
end
|
@@ -8,12 +8,16 @@ module Conject
|
|
8
8
|
config = object_context.get_object_config(name)
|
9
9
|
lambda_constructor = config[:construct]
|
10
10
|
alias_for = config[:is]
|
11
|
+
klass = config[:class]
|
12
|
+
raise "#{klass.inspect} is not a Class" if klass and !(Class === klass)
|
11
13
|
if alias_for
|
14
|
+
# This object is defined as simply being another name for a different object
|
12
15
|
begin
|
13
16
|
object = object_context.get(alias_for)
|
14
17
|
rescue Exception => ex
|
15
18
|
raise "(When attempting to fill alias '#{name}' with actual object '#{alias_for}') #{ex.message}"
|
16
19
|
end
|
20
|
+
|
17
21
|
elsif lambda_constructor
|
18
22
|
case lambda_constructor.arity
|
19
23
|
when 0
|
@@ -25,8 +29,12 @@ module Conject
|
|
25
29
|
else
|
26
30
|
raise "Constructor lambda takes 0, 1 or 2 params; this lambda takes #{lambda_constructor.arity}"
|
27
31
|
end
|
32
|
+
|
33
|
+
elsif klass
|
34
|
+
# This object has a specified class
|
35
|
+
object = type_1_constructor(klass, name, object_context, config[:specialize]) # Specify class
|
28
36
|
else
|
29
|
-
object = type_1_constructor(name, object_context)
|
37
|
+
object = type_1_constructor(nil, name, object_context, config[:specialize]) # Let class be determined by name
|
30
38
|
end
|
31
39
|
|
32
40
|
# Stuff an internal back reference to the object context into the new object:
|
@@ -42,8 +50,8 @@ module Conject
|
|
42
50
|
# - Assume we're looking for a class to create an instance with
|
43
51
|
# - it may or may not have a declared list of named objects it needs to be constructed with
|
44
52
|
#
|
45
|
-
def type_1_constructor(name, object_context)
|
46
|
-
klass
|
53
|
+
def type_1_constructor(klass, name, object_context, overrides=nil)
|
54
|
+
klass ||= class_finder.find_class(name)
|
47
55
|
|
48
56
|
if !klass.object_peers.empty?
|
49
57
|
anchor_object_peers object_context, klass.object_peers
|
@@ -51,7 +59,7 @@ module Conject
|
|
51
59
|
|
52
60
|
constructor_func = nil
|
53
61
|
if klass.has_object_definition?
|
54
|
-
object_map = dependency_resolver.resolve_for_class(klass, object_context)
|
62
|
+
object_map = dependency_resolver.resolve_for_class(klass, object_context, overrides)
|
55
63
|
constructor_func = lambda do klass.new(object_map) end
|
56
64
|
|
57
65
|
elsif Utilities.has_zero_arg_constructor?(klass)
|
@@ -67,7 +75,8 @@ module Conject
|
|
67
75
|
begin
|
68
76
|
object = constructor_func.call
|
69
77
|
rescue Exception => ex
|
70
|
-
origin = "#{
|
78
|
+
origin = "#{ex.message}\n\t#{ex.backtrace.join("\n\t")}"
|
79
|
+
name ||= "(no name)"
|
71
80
|
raise "Error while constructing object '#{name}' of class #{klass}: #{origin}"
|
72
81
|
end
|
73
82
|
end
|
data/lib/conject/version.rb
CHANGED
@@ -0,0 +1,60 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper")
|
2
|
+
|
3
|
+
describe "object specialization" do
|
4
|
+
subject { new_object_context }
|
5
|
+
|
6
|
+
before do
|
7
|
+
append_test_load_path "specialization"
|
8
|
+
require 'finn'
|
9
|
+
require 'jake'
|
10
|
+
require 'batman'
|
11
|
+
require 'robin'
|
12
|
+
require 'clock'
|
13
|
+
require 'team'
|
14
|
+
require 'floor'
|
15
|
+
end
|
16
|
+
|
17
|
+
after do
|
18
|
+
restore_load_path
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
it "lets you define an object by class and a subset of specialized components" do
|
23
|
+
subject.configure_objects({
|
24
|
+
adventure_time: {
|
25
|
+
class: Team,
|
26
|
+
specialize: {
|
27
|
+
hero: "finn",
|
28
|
+
sidekick: "jake" }},
|
29
|
+
dark_knight: {
|
30
|
+
class: Team,
|
31
|
+
specialize: {
|
32
|
+
hero: "batman",
|
33
|
+
sidekick: "robin" }}
|
34
|
+
})
|
35
|
+
|
36
|
+
adventure_time = subject[:adventure_time]
|
37
|
+
adventure_time.class.should == Team
|
38
|
+
adventure_time.hero.should be
|
39
|
+
adventure_time.hero.should == subject[:finn]
|
40
|
+
adventure_time.hero.floor.should == subject[:floor]
|
41
|
+
adventure_time.sidekick.should be
|
42
|
+
adventure_time.sidekick.should == subject[:jake]
|
43
|
+
adventure_time.sidekick.floor.should == subject[:floor]
|
44
|
+
adventure_time.clock.should be
|
45
|
+
adventure_time.clock.should == subject[:clock]
|
46
|
+
|
47
|
+
dark_knight = subject[:dark_knight]
|
48
|
+
dark_knight.class.should == Team
|
49
|
+
dark_knight.hero.should be
|
50
|
+
dark_knight.hero.should == subject[:batman]
|
51
|
+
dark_knight.hero.floor.should == subject[:floor]
|
52
|
+
dark_knight.sidekick.should be
|
53
|
+
dark_knight.sidekick.should == subject[:robin]
|
54
|
+
dark_knight.sidekick.floor.should == subject[:floor]
|
55
|
+
dark_knight.clock.should be
|
56
|
+
dark_knight.clock.should == subject[:clock]
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
@@ -10,7 +10,7 @@ describe Conject::DependencyResolver do
|
|
10
10
|
@objects = objects
|
11
11
|
end
|
12
12
|
def get(name)
|
13
|
-
@objects[name]
|
13
|
+
@objects[name.to_sym] || @object[name.to_s]
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
@@ -20,7 +20,7 @@ describe Conject::DependencyResolver do
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
let :oc_objects do { cow: "the cow", dog: "the dog" } end
|
23
|
+
let :oc_objects do { cow: "the cow", dog: "the dog", bessy: "the bessy", rover: "the rover" } end
|
24
24
|
|
25
25
|
let :object_context do StubbedObjectContext.new(oc_objects) end
|
26
26
|
|
@@ -37,6 +37,22 @@ describe Conject::DependencyResolver do
|
|
37
37
|
}
|
38
38
|
end
|
39
39
|
|
40
|
+
it "uses optional remapping to look for different object names" do
|
41
|
+
remapping = {dog: :rover}
|
42
|
+
subject.resolve_for_class(klass, object_context, remapping).should == {
|
43
|
+
cow: "the cow",
|
44
|
+
dog: "the rover"
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
it "optional remapping is string/symbol insensitive" do
|
49
|
+
remapping = {dog: "rover", cow: :bessy}
|
50
|
+
subject.resolve_for_class(klass, object_context, remapping).should == {
|
51
|
+
cow: "the bessy",
|
52
|
+
dog: "the rover"
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
40
56
|
describe "when the class is in a module" do
|
41
57
|
before do
|
42
58
|
class_finder.stub(:get_module_path).with(klass).and_return("a/module/path")
|
@@ -50,8 +66,6 @@ describe Conject::DependencyResolver do
|
|
50
66
|
dog: "the relative dog",
|
51
67
|
}
|
52
68
|
end
|
53
|
-
|
54
|
-
|
55
69
|
end
|
56
70
|
end
|
57
71
|
|
@@ -17,18 +17,18 @@ describe Conject::ObjectFactory do
|
|
17
17
|
|
18
18
|
let :object_context do mock(:object_context) end
|
19
19
|
|
20
|
-
let :my_object_class do
|
20
|
+
let :my_object_class do Class.new end
|
21
21
|
let :my_object do mock(:my_object) end
|
22
22
|
let :my_objects_components do mock(:my_objects_components) end
|
23
23
|
|
24
24
|
describe "#construct_new" do
|
25
25
|
describe "for Type 1 object construction" do
|
26
|
+
let(:object_config) do {} end
|
26
27
|
before do
|
27
|
-
object_context.stub(:get_object_config).and_return(
|
28
|
+
object_context.stub(:get_object_config).and_return(object_config)
|
28
29
|
my_object_class.stub(:class_def_private) # the effect of this is tested in acceptance tests
|
29
30
|
my_object_class.stub(:object_peers).and_return([]) # the effect of this is tested in acceptance tests
|
30
31
|
|
31
|
-
class_finder.should_receive(:find_class).with(my_object_name).and_return my_object_class
|
32
32
|
Conject::Utilities.stub(:has_zero_arg_constructor?).and_return true
|
33
33
|
end
|
34
34
|
|
@@ -36,18 +36,58 @@ describe Conject::ObjectFactory do
|
|
36
36
|
before do
|
37
37
|
my_object_class.should_receive(:has_object_definition?).and_return true
|
38
38
|
end
|
39
|
+
describe "and no specialization has been made" do
|
40
|
+
before do class_finder.should_receive(:find_class).with(my_object_name).and_return my_object_class end
|
41
|
+
it "finds the object definition, pulls its deps, and instantiates a new instance" do
|
42
|
+
dependency_resolver.should_receive(:resolve_for_class).with(my_object_class, object_context, nil).and_return my_objects_components
|
43
|
+
my_object_class.should_receive(:new).with(my_objects_components).and_return(my_object)
|
44
|
+
|
45
|
+
subject.construct_new(my_object_name, object_context).should == my_object
|
46
|
+
end
|
47
|
+
end
|
39
48
|
|
40
|
-
|
41
|
-
|
42
|
-
|
49
|
+
describe "when specialization is provided" do
|
50
|
+
let(:overrides) do { remapped: "names" } end
|
51
|
+
let(:object_config) do { :class => my_object_class, :specialize => overrides } end
|
43
52
|
|
44
|
-
|
53
|
+
before do
|
54
|
+
class_finder.should_not_receive(:find_class)#.with(my_object_name).and_return my_object_class
|
55
|
+
end
|
56
|
+
|
57
|
+
it "uses the specified class and remapped objects to reolve and build the object" do
|
58
|
+
dependency_resolver.should_receive(:resolve_for_class).with(my_object_class, object_context, overrides).and_return my_objects_components
|
59
|
+
my_object_class.should_receive(:new).with(my_objects_components).and_return(my_object)
|
60
|
+
subject.construct_new(my_object_name, object_context).should == my_object
|
61
|
+
end
|
45
62
|
end
|
63
|
+
|
64
|
+
describe "when :class is configured" do
|
65
|
+
let(:object_config) do { :class => my_object_class } end
|
66
|
+
|
67
|
+
before do
|
68
|
+
class_finder.should_not_receive(:find_class)#.with(my_object_name).and_return my_object_class
|
69
|
+
end
|
70
|
+
|
71
|
+
it "uses the specified class to build the object" do
|
72
|
+
dependency_resolver.should_receive(:resolve_for_class).with(my_object_class, object_context, nil).and_return my_objects_components
|
73
|
+
my_object_class.should_receive(:new).with(my_objects_components).and_return(my_object)
|
74
|
+
subject.construct_new(my_object_name, object_context).should == my_object
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
46
79
|
end
|
80
|
+
describe "when :class is not a Class" do
|
81
|
+
let(:object_config) do { :class => "Oops! not a Class" } end
|
82
|
+
it "raises" do
|
83
|
+
lambda do subject.construct_new(my_object_name, object_context) end.should raise_error(/is not a Class/)
|
84
|
+
end
|
85
|
+
end
|
47
86
|
|
48
87
|
describe "when target class has no object definition" do
|
49
88
|
before do
|
50
89
|
my_object_class.should_receive(:has_object_definition?).and_return false
|
90
|
+
class_finder.should_receive(:find_class).with(my_object_name).and_return my_object_class
|
51
91
|
end
|
52
92
|
|
53
93
|
it "creates a new instance of the class without any arguments" do
|
@@ -60,6 +100,7 @@ describe Conject::ObjectFactory do
|
|
60
100
|
before do
|
61
101
|
my_object_class.should_receive(:has_object_definition?).and_return false
|
62
102
|
Conject::Utilities.stub(:has_zero_arg_constructor?).and_return false
|
103
|
+
class_finder.should_receive(:find_class).with(my_object_name).and_return my_object_class
|
63
104
|
end
|
64
105
|
|
65
106
|
it "raises a CompositionError" do
|
@@ -67,9 +108,23 @@ describe Conject::ObjectFactory do
|
|
67
108
|
subject.construct_new(my_object_name, object_context)
|
68
109
|
end.should raise_error(ArgumentError)
|
69
110
|
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
describe "when target constructor raises an error" do
|
115
|
+
it "re-raises with an explanation" do
|
116
|
+
class_finder.should_receive(:find_class).with(my_object_name).and_return my_object_class
|
117
|
+
my_object_class.stub(:has_object_definition?).and_return false
|
118
|
+
my_object_class.should_receive(:new).and_raise("BOOM")
|
119
|
+
lambda do
|
120
|
+
subject.construct_new(my_object_name, object_context)
|
121
|
+
end.should raise_error(/Error while constructing object '#{my_object_name}'.*BOOM/)
|
122
|
+
end
|
70
123
|
end
|
71
124
|
end
|
72
125
|
|
126
|
+
|
127
|
+
|
73
128
|
describe "for custom lambda construction" do
|
74
129
|
let(:object_config) do { :construct => lambda do "The Object" end } end
|
75
130
|
let(:object_config2) do { :construct => lambda do |object_context| { :the_oc => object_context } end } end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: conject
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.7
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-02-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -133,6 +133,7 @@ files:
|
|
133
133
|
- spec/acceptance/regression/nested_contexts_spec.rb
|
134
134
|
- spec/acceptance/regression/non_singleton_spec.rb
|
135
135
|
- spec/acceptance/regression/object_peers_spec.rb
|
136
|
+
- spec/acceptance/regression/specialize_spec.rb
|
136
137
|
- spec/conject/class_ext_construct_with_spec.rb
|
137
138
|
- spec/conject/class_finder_spec.rb
|
138
139
|
- spec/conject/composition_error_spec.rb
|
@@ -183,6 +184,13 @@ files:
|
|
183
184
|
- spec/test_data/object_peers/game.rb
|
184
185
|
- spec/test_data/simple_stuff/class_that_uses_context_in_init.rb
|
185
186
|
- spec/test_data/simple_stuff/some_random_class.rb
|
187
|
+
- spec/test_data/specialization/batman.rb
|
188
|
+
- spec/test_data/specialization/clock.rb
|
189
|
+
- spec/test_data/specialization/finn.rb
|
190
|
+
- spec/test_data/specialization/floor.rb
|
191
|
+
- spec/test_data/specialization/jake.rb
|
192
|
+
- spec/test_data/specialization/robin.rb
|
193
|
+
- spec/test_data/specialization/team.rb
|
186
194
|
- src/user_model.rb
|
187
195
|
- src/user_presenter.rb
|
188
196
|
- src/user_view.rb
|
@@ -200,7 +208,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
200
208
|
version: '0'
|
201
209
|
segments:
|
202
210
|
- 0
|
203
|
-
hash: -
|
211
|
+
hash: -2747476053760078928
|
204
212
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
205
213
|
none: false
|
206
214
|
requirements:
|
@@ -209,7 +217,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
209
217
|
version: '0'
|
210
218
|
segments:
|
211
219
|
- 0
|
212
|
-
hash: -
|
220
|
+
hash: -2747476053760078928
|
213
221
|
requirements: []
|
214
222
|
rubyforge_project:
|
215
223
|
rubygems_version: 1.8.24
|
@@ -232,6 +240,7 @@ test_files:
|
|
232
240
|
- spec/acceptance/regression/nested_contexts_spec.rb
|
233
241
|
- spec/acceptance/regression/non_singleton_spec.rb
|
234
242
|
- spec/acceptance/regression/object_peers_spec.rb
|
243
|
+
- spec/acceptance/regression/specialize_spec.rb
|
235
244
|
- spec/conject/class_ext_construct_with_spec.rb
|
236
245
|
- spec/conject/class_finder_spec.rb
|
237
246
|
- spec/conject/composition_error_spec.rb
|
@@ -282,3 +291,10 @@ test_files:
|
|
282
291
|
- spec/test_data/object_peers/game.rb
|
283
292
|
- spec/test_data/simple_stuff/class_that_uses_context_in_init.rb
|
284
293
|
- spec/test_data/simple_stuff/some_random_class.rb
|
294
|
+
- spec/test_data/specialization/batman.rb
|
295
|
+
- spec/test_data/specialization/clock.rb
|
296
|
+
- spec/test_data/specialization/finn.rb
|
297
|
+
- spec/test_data/specialization/floor.rb
|
298
|
+
- spec/test_data/specialization/jake.rb
|
299
|
+
- spec/test_data/specialization/robin.rb
|
300
|
+
- spec/test_data/specialization/team.rb
|