conject 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.
- data/.gitignore +4 -0
- data/.rvmrc +2 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +32 -0
- data/NOTES.txt +61 -0
- data/README.md +8 -0
- data/Rakefile +12 -0
- data/TODO +9 -0
- data/conject.gemspec +21 -0
- data/lib/conject.rb +40 -0
- data/lib/conject/borrowed_active_support_inflector.rb +525 -0
- data/lib/conject/class_ext_construct_with.rb +123 -0
- data/lib/conject/class_finder.rb +11 -0
- data/lib/conject/composition_error.rb +33 -0
- data/lib/conject/dependency_resolver.rb +16 -0
- data/lib/conject/extended_metaid.rb +33 -0
- data/lib/conject/object_context.rb +61 -0
- data/lib/conject/object_definition.rb +10 -0
- data/lib/conject/object_factory.rb +28 -0
- data/lib/conject/utilities.rb +8 -0
- data/lib/conject/version.rb +3 -0
- data/rake_tasks/rspec.rake +25 -0
- data/spec/acceptance/dev/README +7 -0
- data/spec/acceptance/regression/README +12 -0
- data/spec/acceptance/regression/basic_composition_spec.rb +29 -0
- data/spec/acceptance/regression/basic_object_creation_spec.rb +42 -0
- data/spec/acceptance/regression/nested_contexts_spec.rb +86 -0
- data/spec/conject/borrowed_active_support_inflector_spec.rb +28 -0
- data/spec/conject/class_ext_construct_with_spec.rb +226 -0
- data/spec/conject/class_finder_spec.rb +36 -0
- data/spec/conject/composition_error_spec.rb +124 -0
- data/spec/conject/dependency_resolver_spec.rb +32 -0
- data/spec/conject/extended_metaid_spec.rb +90 -0
- data/spec/conject/object_context_spec.rb +186 -0
- data/spec/conject/object_definition_spec.rb +31 -0
- data/spec/conject/object_factory_spec.rb +89 -0
- data/spec/conject/utilities_spec.rb +30 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support/SPEC_HELPERS_GO_HERE +0 -0
- data/spec/support/load_path_helpers.rb +27 -0
- data/spec/test_data/basic_composition/fence.rb +5 -0
- data/spec/test_data/basic_composition/front_desk.rb +2 -0
- data/spec/test_data/basic_composition/grass.rb +2 -0
- data/spec/test_data/basic_composition/guest.rb +5 -0
- data/spec/test_data/basic_composition/lobby.rb +5 -0
- data/spec/test_data/basic_composition/nails.rb +2 -0
- data/spec/test_data/basic_composition/tv.rb +2 -0
- data/spec/test_data/basic_composition/wood.rb +2 -0
- data/spec/test_data/simple_stuff/some_random_class.rb +2 -0
- data/spike/arity_funny_business_in_different_ruby_versions.rb +34 -0
- data/spike/depends_on_spike.rb +146 -0
- data/spike/donkey_fail.rb +48 -0
- data/spike/donkey_journey.rb +50 -0
- data/spike/go.rb +11 -0
- data/spike/metaid.rb +28 -0
- data/spike/object_definition.rb +125 -0
- data/spike/sample.rb +125 -0
- data/src/user_model.rb +4 -0
- data/src/user_presenter.rb +10 -0
- data/src/user_view.rb +3 -0
- metadata +165 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
|
2
|
+
|
3
|
+
describe Conject::DependencyResolver do
|
4
|
+
|
5
|
+
class StubbedObjectContext
|
6
|
+
attr_accessor :objects
|
7
|
+
def initialize(objects)
|
8
|
+
@objects = objects
|
9
|
+
end
|
10
|
+
def get(name)
|
11
|
+
@objects[name]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
let :klass do
|
16
|
+
Class.new do
|
17
|
+
construct_with :cow, :dog
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
let :oc_objects do { cow: "the cow", dog: "the dog" } end
|
22
|
+
|
23
|
+
let :object_context do StubbedObjectContext.new(oc_objects) end
|
24
|
+
|
25
|
+
it "maps the object definition component names of the given class to a set of objects gotten from the object context" do
|
26
|
+
subject.resolve_for_class(klass, object_context).should == {
|
27
|
+
cow: "the cow",
|
28
|
+
dog: "the dog"
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
|
2
|
+
|
3
|
+
#
|
4
|
+
# This set of specs isn't intended to exhaust metaid's functionality.
|
5
|
+
# Just to frame up something as a basis for test driving further mods.
|
6
|
+
#
|
7
|
+
describe "Extended metaid" do
|
8
|
+
|
9
|
+
# Not testing:
|
10
|
+
# Object#metaclass
|
11
|
+
# Object#meta_eval
|
12
|
+
# because we're not using them directly, don't plan to
|
13
|
+
# change them, they're long-standing, widely-used Ruby
|
14
|
+
# metaprogramming methods, and we're using and testing
|
15
|
+
# them indirectly through #metadef
|
16
|
+
|
17
|
+
describe "Object#meta_def" do
|
18
|
+
it "adds a method to an instance" do
|
19
|
+
obj = Object.new
|
20
|
+
obj2 = Object.new
|
21
|
+
obj.meta_def :berzerker do
|
22
|
+
37
|
23
|
+
end
|
24
|
+
|
25
|
+
obj.berzerker.should == 37
|
26
|
+
lambda do obj2.berzerker.should == 37 end.should raise_error
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "Class#meta_def" do
|
31
|
+
subject do
|
32
|
+
Class.new do
|
33
|
+
def self.use_metaclass_method
|
34
|
+
try_me
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it "adds a method to the metaclass of a class" do
|
40
|
+
subject.meta_def :try_me do "is nice" end
|
41
|
+
subject.try_me.should == "is nice"
|
42
|
+
subject.use_metaclass_method.should == "is nice"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Not testing
|
47
|
+
# Module#module_def
|
48
|
+
# Module#module_def_private
|
49
|
+
# because we're not using them directly,
|
50
|
+
# so let's move onto the class stuff
|
51
|
+
|
52
|
+
describe "Class#class_def" do
|
53
|
+
subject do
|
54
|
+
Class.new
|
55
|
+
end
|
56
|
+
|
57
|
+
it "enables dynamic generation of new instance methods" do
|
58
|
+
obj = subject.new
|
59
|
+
|
60
|
+
subject.class_def :sgt do
|
61
|
+
"highway"
|
62
|
+
end
|
63
|
+
|
64
|
+
obj.sgt.should == "highway"
|
65
|
+
subject.new.sgt.should == "highway"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "Class#class_def_private" do
|
70
|
+
subject do
|
71
|
+
Class.new
|
72
|
+
end
|
73
|
+
|
74
|
+
it "enables dynamic generation of new PRIVATE instance methods" do
|
75
|
+
obj = subject.new
|
76
|
+
|
77
|
+
subject.class_def_private :sgt do "highway" end
|
78
|
+
|
79
|
+
|
80
|
+
# sgt is private so we shouldn't be able to call it outright
|
81
|
+
lambda do obj.sgt end.should raise_error(NoMethodError)
|
82
|
+
|
83
|
+
# Make a public method that can access it
|
84
|
+
subject.class_def :gunnery do sgt end
|
85
|
+
|
86
|
+
obj.gunnery.should == "highway"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
|
2
|
+
|
3
|
+
describe Conject::ObjectContext do
|
4
|
+
subject do
|
5
|
+
Conject::ObjectContext.new(:parent_context => parent_context, :object_factory => object_factory)
|
6
|
+
end
|
7
|
+
|
8
|
+
let :parent_context do mock(:parent_context) end
|
9
|
+
let :object_factory do mock(:object_factory) end
|
10
|
+
|
11
|
+
describe "#get" do
|
12
|
+
describe "when an object has been #put" do
|
13
|
+
before { subject.put(:kraft, "verk") }
|
14
|
+
|
15
|
+
it "returns the #put object" do
|
16
|
+
subject.get(:kraft).should == "verk"
|
17
|
+
end
|
18
|
+
|
19
|
+
it "can use strings and symbols interchangebly" do
|
20
|
+
subject.get('kraft').should == "verk"
|
21
|
+
|
22
|
+
subject.put('happy', 'hat')
|
23
|
+
subject.get('happy').should == 'hat'
|
24
|
+
subject.get(:happy).should == 'hat'
|
25
|
+
end
|
26
|
+
|
27
|
+
it "support [] as shorthand for get and []= for put" do
|
28
|
+
subject['kraft'].should == "verk"
|
29
|
+
|
30
|
+
subject['happy'] = 'hat'
|
31
|
+
subject['happy'].should == 'hat'
|
32
|
+
subject[:happy].should == 'hat'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "when the object is not in the cache" do
|
37
|
+
describe "and the parent context has got the requested object" do
|
38
|
+
before do
|
39
|
+
parent_context.should_receive(:has?).with(:cheezburger).and_return(true)
|
40
|
+
parent_context.should_receive(:get).with(:cheezburger).and_return("can haz")
|
41
|
+
end
|
42
|
+
|
43
|
+
it "returns the object from the parent context" do
|
44
|
+
subject.get(:cheezburger).should == "can haz"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "and the parent context does NOT have the requested object" do
|
49
|
+
before do
|
50
|
+
parent_context.should_receive(:has?).with(:cheezburger).and_return(false)
|
51
|
+
parent_context.should_not_receive(:get)
|
52
|
+
@but_i_ated_it = Object.new
|
53
|
+
object_factory.should_receive(:construct_new).with(:cheezburger, subject).and_return(@but_i_ated_it)
|
54
|
+
end
|
55
|
+
|
56
|
+
# NOTE: these examples are identical to those in the spec for missing parent context (below)
|
57
|
+
it "constructs the object anew using the object factory" do
|
58
|
+
subject.get(:cheezburger).should == @but_i_ated_it
|
59
|
+
end
|
60
|
+
|
61
|
+
it "caches the results of using the object facatory" do
|
62
|
+
subject.get(:cheezburger).should == @but_i_ated_it
|
63
|
+
# mock object factory would explode if we asked it twice:
|
64
|
+
subject.get(:cheezburger).should == @but_i_ated_it
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "but there is no parent context" do
|
70
|
+
let :parent_context do nil end
|
71
|
+
before do
|
72
|
+
@but_i_ated_it = Object.new
|
73
|
+
object_factory.should_receive(:construct_new).with(:cheezburger, subject).and_return(@but_i_ated_it)
|
74
|
+
end
|
75
|
+
|
76
|
+
# NOTE: these examples are identical to those in the spec for parent context not having the object (above)
|
77
|
+
it "constructs the object anew using the object factory" do
|
78
|
+
subject.get(:cheezburger).should == @but_i_ated_it
|
79
|
+
end
|
80
|
+
|
81
|
+
it "caches the results of using the object facatory" do
|
82
|
+
subject.get(:cheezburger).should == @but_i_ated_it
|
83
|
+
# mock object factory would explode if we asked it twice:
|
84
|
+
subject.get(:cheezburger).should == @but_i_ated_it
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "#has?" do
|
91
|
+
describe "when the object exists in the cache" do
|
92
|
+
describe "due to #put" do
|
93
|
+
it "returns true" do
|
94
|
+
subject.put(:a_clue, "Wodsworth")
|
95
|
+
subject.has?(:a_clue).should be_true
|
96
|
+
subject.has?(:a_clue).should be_true # do it again for good measure
|
97
|
+
end
|
98
|
+
end
|
99
|
+
describe "due to caching a previous #get" do
|
100
|
+
before do
|
101
|
+
parent_context.stub(:has?).and_return(false)
|
102
|
+
object_factory.stub(:construct_new).and_return("Mr Green")
|
103
|
+
end
|
104
|
+
it "returns true" do
|
105
|
+
subject.get(:a_clue).should == "Mr Green"
|
106
|
+
subject.has?(:a_clue).should be_true
|
107
|
+
subject.has?(:a_clue).should be_true # do it again for good measure
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "when the object does NOT exist in the cache" do
|
113
|
+
describe "when there is no parent context" do
|
114
|
+
let :parent_context do nil end
|
115
|
+
it "returns false" do
|
116
|
+
subject.has?(:a_clue).should == false
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe "when there is a parent context" do
|
121
|
+
it "delegates the question to the parent context" do
|
122
|
+
parent_context.should_receive(:has?).with(:a_clue).and_return("the parent answer")
|
123
|
+
subject.has?(:a_clue).should == "the parent answer"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "#directly_has?" do
|
130
|
+
describe "when the object exists in the cache" do
|
131
|
+
describe "due to #put" do
|
132
|
+
it "returns true" do
|
133
|
+
subject.put(:a_clue, "Wodsworth")
|
134
|
+
subject.directly_has?(:a_clue).should be_true
|
135
|
+
subject.directly_has?(:a_clue).should be_true # twice for luck
|
136
|
+
subject.directly_has?('a_clue').should be_true # twice for luck
|
137
|
+
end
|
138
|
+
end
|
139
|
+
describe "due to caching a previous #get" do
|
140
|
+
before do
|
141
|
+
parent_context.stub(:has?).and_return(false)
|
142
|
+
object_factory.stub(:construct_new).and_return("Mr Green")
|
143
|
+
end
|
144
|
+
it "returns true" do
|
145
|
+
subject.get(:a_clue).should == "Mr Green"
|
146
|
+
subject.directly_has?(:a_clue).should be_true
|
147
|
+
subject.directly_has?('a_clue').should be_true
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe "when the object does NOT exist in the cache" do
|
153
|
+
it "returns false" do
|
154
|
+
subject.directly_has?(:a_clue).should == false
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe "#in_subcontext" do
|
160
|
+
it "yields a new context with the given context as its parent" do
|
161
|
+
subcontext_executed = false
|
162
|
+
subsubcontext_executed = false
|
163
|
+
ident = Object.new
|
164
|
+
subject.put("identifier", ident)
|
165
|
+
subject.in_subcontext do |subcontext|
|
166
|
+
subcontext_executed = true
|
167
|
+
subcontext.class.should == Conject::ObjectContext # See it's an ObjectContext
|
168
|
+
subcontext.object_id.should_not == subject.object_id # See it's different
|
169
|
+
subcontext.get("identifier").object_id.should == ident.object_id # See it has access to the parent
|
170
|
+
|
171
|
+
ident2 = Object.new
|
172
|
+
subcontext.put("sub_ident", ident2)
|
173
|
+
subcontext.in_subcontext do |subsub|
|
174
|
+
subsubcontext_executed = true
|
175
|
+
subsub.class.should == Conject::ObjectContext # See it's an ObjectContext
|
176
|
+
subsub.object_id.should_not == subject.object_id # See it's different
|
177
|
+
subsub.object_id.should_not == subcontext.object_id # See it's different
|
178
|
+
subsub.get("identifier").object_id.should == ident.object_id # See it has access to the parent
|
179
|
+
subsub.get("sub_ident").object_id.should == ident2.object_id # See it has access to the parent's parent
|
180
|
+
end
|
181
|
+
end
|
182
|
+
subcontext_executed.should be_true
|
183
|
+
subsubcontext_executed.should be_true
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
|
2
|
+
|
3
|
+
describe Conject::ObjectDefinition do
|
4
|
+
def new_def(*args)
|
5
|
+
Conject::ObjectDefinition.new(*args)
|
6
|
+
end
|
7
|
+
|
8
|
+
it "has a field for :component_names and :owner and can be built with them" do
|
9
|
+
subject = new_def :owner => "the owner", :component_names => [ :one, :two ]
|
10
|
+
subject.owner.should == "the owner"
|
11
|
+
subject.component_names.should == [:one, :two]
|
12
|
+
end
|
13
|
+
|
14
|
+
it "defaults :component_names to an empty array" do
|
15
|
+
subject = new_def :owner => "the owner"
|
16
|
+
subject.owner.should == "the owner"
|
17
|
+
subject.component_names.should be_empty
|
18
|
+
end
|
19
|
+
|
20
|
+
it "defaults :owner to nil" do
|
21
|
+
subject = new_def :component_names => [ :one, :two ]
|
22
|
+
subject.owner.should nil
|
23
|
+
subject.component_names.should == [:one, :two]
|
24
|
+
end
|
25
|
+
|
26
|
+
it "can be built with no args" do
|
27
|
+
subject = new_def
|
28
|
+
subject.owner.should nil
|
29
|
+
subject.component_names.should be_empty
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
|
2
|
+
|
3
|
+
describe Conject::ObjectFactory do
|
4
|
+
|
5
|
+
subject do
|
6
|
+
described_class.new component_map
|
7
|
+
end
|
8
|
+
|
9
|
+
let :component_map do
|
10
|
+
{ :class_finder => class_finder, :dependency_resolver => dependency_resolver }
|
11
|
+
end
|
12
|
+
|
13
|
+
let :my_object_name do :my_object_name end
|
14
|
+
|
15
|
+
let :class_finder do mock(:class_finder) end
|
16
|
+
let :dependency_resolver do mock(:dependency_resolver) end
|
17
|
+
|
18
|
+
let :object_context do mock(:object_context) end
|
19
|
+
|
20
|
+
let :my_object_class do mock(:my_object_class) end
|
21
|
+
let :my_object do mock(:my_object) end
|
22
|
+
let :my_objects_components do mock(:my_objects_components) end
|
23
|
+
|
24
|
+
describe "#construct_new" do
|
25
|
+
before do
|
26
|
+
class_finder.should_receive(:find_class).with(my_object_name).and_return my_object_class
|
27
|
+
Conject::Utilities.stub(:has_zero_arg_constructor?).and_return true
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "when target class has an object definition (implying composition dependencies)" do
|
31
|
+
before do
|
32
|
+
my_object_class.should_receive(:has_object_definition?).and_return true
|
33
|
+
end
|
34
|
+
|
35
|
+
it "finds the object definition, pulls its deps, and instantiates a new instance" do
|
36
|
+
dependency_resolver.should_receive(:resolve_for_class).with(my_object_class, object_context).and_return my_objects_components
|
37
|
+
my_object_class.should_receive(:new).with(my_objects_components).and_return(my_object)
|
38
|
+
|
39
|
+
subject.construct_new(my_object_name, object_context).should == my_object
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "when target class has no object definition" do
|
44
|
+
before do
|
45
|
+
my_object_class.should_receive(:has_object_definition?).and_return false
|
46
|
+
end
|
47
|
+
|
48
|
+
it "creates a new instance of the class without any arguments" do
|
49
|
+
my_object_class.should_receive(:new).and_return(my_object)
|
50
|
+
subject.construct_new(my_object_name, object_context).should == my_object
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "when target class has no object def, but also a non-default constructor" do
|
55
|
+
before do
|
56
|
+
my_object_class.should_receive(:has_object_definition?).and_return false
|
57
|
+
Conject::Utilities.stub(:has_zero_arg_constructor?).and_return false
|
58
|
+
end
|
59
|
+
|
60
|
+
it "raises a CompositionError" do
|
61
|
+
lambda do
|
62
|
+
subject.construct_new(my_object_name, object_context)
|
63
|
+
end.should raise_error(ArgumentError)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
__END__
|
70
|
+
|
71
|
+
|
72
|
+
Decide that we're going to class Type 1 object creation:
|
73
|
+
Type 1 = create a normal object instance by invoking its constructors with a map of its declared object dependencies
|
74
|
+
|
75
|
+
Find class for object name
|
76
|
+
assume strong naming convention
|
77
|
+
|
78
|
+
Ask class for dependency list
|
79
|
+
|
80
|
+
Get dependencies from context
|
81
|
+
Instantiate and return object
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
- definition
|
86
|
+
|
87
|
+
find definition
|
88
|
+
|
89
|
+
build_from(definition, context)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
|
2
|
+
|
3
|
+
describe Conject::Utilities do
|
4
|
+
subject do described_class end
|
5
|
+
|
6
|
+
before do
|
7
|
+
@class_a = Class.new do end
|
8
|
+
@class_b = Class.new do
|
9
|
+
def initialize
|
10
|
+
end
|
11
|
+
end
|
12
|
+
@class_c = Class.new do
|
13
|
+
def initialize(x)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe ".has_zero_arg_constructor?" do
|
19
|
+
it "returns true when a class defines no initialize method" do
|
20
|
+
subject.has_zero_arg_constructor?(@class_a).should be_true
|
21
|
+
end
|
22
|
+
it "returns true when a class defines initialize method without args" do
|
23
|
+
subject.has_zero_arg_constructor?(@class_b).should be_true
|
24
|
+
end
|
25
|
+
it "returns false when a class defines initialize method WITH args" do
|
26
|
+
subject.has_zero_arg_constructor?(@class_c).should be_false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|