conject 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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,86 @@
|
|
1
|
+
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper")
|
3
|
+
|
4
|
+
describe "nested contexts" do
|
5
|
+
subject { Conject.default_object_context }
|
6
|
+
|
7
|
+
let(:root_context) { Conject.default_object_context }
|
8
|
+
|
9
|
+
before do
|
10
|
+
append_test_load_path "basic_composition"
|
11
|
+
require 'lobby'
|
12
|
+
require 'front_desk'
|
13
|
+
require 'guest'
|
14
|
+
require 'tv'
|
15
|
+
end
|
16
|
+
|
17
|
+
after do
|
18
|
+
restore_load_path
|
19
|
+
end
|
20
|
+
|
21
|
+
it "can create an object within a subcontext" do
|
22
|
+
root_context.in_subcontext do |hotel|
|
23
|
+
front_desk = hotel[:front_desk]
|
24
|
+
front_desk.should be
|
25
|
+
front_desk.class.should == FrontDesk
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it "reuse objects within themselves, but do not share with peer contexts" do
|
30
|
+
front_desk = nil
|
31
|
+
another_front_desk = nil
|
32
|
+
|
33
|
+
root_context.in_subcontext do |hotel|
|
34
|
+
front_desk = hotel[:front_desk]
|
35
|
+
hotel[:front_desk].object_id.should == front_desk.object_id
|
36
|
+
hotel.directly_has?(:front_desk).should be_true
|
37
|
+
end
|
38
|
+
|
39
|
+
root_context.in_subcontext do |hotel|
|
40
|
+
another_front_desk = hotel[:front_desk]
|
41
|
+
hotel[:front_desk].object_id.should == another_front_desk.object_id
|
42
|
+
end
|
43
|
+
|
44
|
+
another_front_desk.object_id.should_not == front_desk.object_id
|
45
|
+
end
|
46
|
+
|
47
|
+
it "provide dependencies by generating objects within itself" do
|
48
|
+
root_context.in_subcontext do |hotel|
|
49
|
+
lobby = hotel[:lobby]
|
50
|
+
lobby.front_desk.should be
|
51
|
+
lobby.front_desk.should == hotel[:front_desk]
|
52
|
+
|
53
|
+
hotel.directly_has?(:front_desk).should be_true
|
54
|
+
hotel.directly_has?(:lobby).should be_true
|
55
|
+
|
56
|
+
root_context.has?(:front_desk).should_not be_true
|
57
|
+
root_context.has?(:lobby).should_not be_true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it "provide dependencies via its parent context, THEN generating objects within itself" do
|
62
|
+
root_context.in_subcontext do |hotel|
|
63
|
+
lobby = hotel[:lobby] # this establishes the lobby and front desk in the hotel context
|
64
|
+
|
65
|
+
hotel.in_subcontext do |room|
|
66
|
+
guest = room[:guest]
|
67
|
+
# Guest should have reference to TV from the room context, and front desk from the hotel context
|
68
|
+
guest.tv.should be
|
69
|
+
guest.front_desk.should be
|
70
|
+
|
71
|
+
# Double check which contexts own which objects
|
72
|
+
room.directly_has?(:guest).should be_true
|
73
|
+
room.directly_has?(:tv).should be_true
|
74
|
+
|
75
|
+
hotel.directly_has?(:guest).should_not be_true
|
76
|
+
hotel.directly_has?(:tv).should_not be_true
|
77
|
+
|
78
|
+
guest.front_desk.should == hotel[:front_desk]
|
79
|
+
room.directly_has?(:front_desk).should_not be_true
|
80
|
+
hotel.directly_has?(:front_desk).should be_true
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
|
2
|
+
|
3
|
+
#
|
4
|
+
# Some super-basic specs that don't really exercise Inflector,
|
5
|
+
# but demonstrate that we have this capability built-in
|
6
|
+
# when using ObjectContext.
|
7
|
+
#
|
8
|
+
describe "Borrowed ActiveSupport Inflector" do
|
9
|
+
describe "#camelize" do
|
10
|
+
it "converts underscored strings to camel case" do
|
11
|
+
"four_five_six".camelize.should == "FourFiveSix"
|
12
|
+
end
|
13
|
+
|
14
|
+
it "leaves camel case words along" do
|
15
|
+
"HoppingSizzler".camelize.should == "HoppingSizzler"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#underscore" do
|
20
|
+
it "converts camel-case words to underscored" do
|
21
|
+
"HoppingSizzler".underscore.should == "hopping_sizzler"
|
22
|
+
end
|
23
|
+
|
24
|
+
it "leaves underscored strings alone" do
|
25
|
+
"four_five_six".underscore.should == "four_five_six"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
|
2
|
+
|
3
|
+
describe "Class" do
|
4
|
+
|
5
|
+
describe ".construct_with" do
|
6
|
+
subject do
|
7
|
+
Class.new do
|
8
|
+
construct_with :object1, :object2
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@saw = "initialize has access to #{object1} and #{object2}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def explain
|
15
|
+
"Made of #{object1} and #{object2}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
@saw
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it "lets you construct instances with sets of named objects" do
|
25
|
+
instance = subject.new :object1 => "first object", :object2 => "second object"
|
26
|
+
instance.explain.should == "Made of first object and second object"
|
27
|
+
end
|
28
|
+
|
29
|
+
it "sets objects before initialize is invoked" do
|
30
|
+
instance = subject.new :object1 => "one", :object2 => "two"
|
31
|
+
instance.to_s.should == "initialize has access to one and two"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "defines the object accessors as private" do
|
35
|
+
instance = subject.new :object1 => "one", :object2 => "two"
|
36
|
+
lambda do instance.object1 end.should raise_error(NoMethodError)
|
37
|
+
lambda do instance.object2 end.should raise_error(NoMethodError)
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "when user defines initialize with one argument" do
|
41
|
+
subject do
|
42
|
+
Class.new do
|
43
|
+
construct_with :ant, :aardvark
|
44
|
+
|
45
|
+
attr_reader :map_string, :also
|
46
|
+
|
47
|
+
def initialize(map)
|
48
|
+
@map_string = map.inspect
|
49
|
+
@also = "Preset #{ant} and #{aardvark}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
let :map do { :ant => "red", :aardvark => "blue" } end
|
55
|
+
|
56
|
+
it "passes along the object map" do
|
57
|
+
subject.new(map).map_string.should == map.inspect
|
58
|
+
end
|
59
|
+
|
60
|
+
it "still pre-sets the object accessors" do
|
61
|
+
subject.new(map).also.should == "Preset red and blue"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "when user defines initialize with var args" do
|
66
|
+
subject do
|
67
|
+
Class.new do
|
68
|
+
construct_with :ant, :aardvark
|
69
|
+
|
70
|
+
attr_reader :map_string, :also
|
71
|
+
|
72
|
+
def initialize(*stuff)
|
73
|
+
map = stuff.shift
|
74
|
+
@map_string = map.inspect
|
75
|
+
@also = "Preset #{ant} and #{aardvark}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
let :map do { :ant => "red", :aardvark => "blue" } end
|
81
|
+
|
82
|
+
it "passes along the object map" do
|
83
|
+
subject.new(map).map_string.should == map.inspect
|
84
|
+
end
|
85
|
+
|
86
|
+
it "still pre-sets the object accessors" do
|
87
|
+
subject.new(map).also.should == "Preset red and blue"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "when user defines initialize with too many args" do
|
92
|
+
subject do
|
93
|
+
Class.new do
|
94
|
+
construct_with :beevis, :butthead
|
95
|
+
def initialize(one,two)
|
96
|
+
# won't ever get run because the .new override will freak due to our arity
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
let :map do { :beevis => "nh nh!", :butthead => "uhhh huh huh" } end
|
102
|
+
|
103
|
+
it "raises an error due to invalid #initialize signature" do
|
104
|
+
lambda do subject.new(map) end.should raise_error(RuntimeError, /initialize method defined with 2 parameters/)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "when user doesn't define an initialize" do
|
109
|
+
subject do
|
110
|
+
Class.new do
|
111
|
+
construct_with :something
|
112
|
+
def to_s
|
113
|
+
"Something is #{something}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
it "works normally" do
|
119
|
+
subject.new(:something => "normal").to_s.should == "Something is normal"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "when no object map is supplied to constructor" do
|
124
|
+
it "raises a typical argument error" do
|
125
|
+
lambda do subject.new end.should raise_error(ArgumentError)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "when object map does not contain any required objects" do
|
130
|
+
it "raises a composition error explaining missing objects" do
|
131
|
+
lambda do subject.new({}) end.should raise_error(
|
132
|
+
Conject::CompositionError,
|
133
|
+
/missing required.*object1.*object2/i
|
134
|
+
)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe "when object map contains only SOME of the required objects" do
|
139
|
+
it "raises a composition error explaining which missing objects" do
|
140
|
+
lambda do subject.new(:object2 => "ok") end.should raise_error(
|
141
|
+
Conject::CompositionError,
|
142
|
+
/missing required.*object1/i
|
143
|
+
)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe "when object map contains objects that are not accepted" do
|
148
|
+
it "raises a composition error explaining rejected objects" do
|
149
|
+
lambda do
|
150
|
+
subject.new(
|
151
|
+
:object1 => "ok",
|
152
|
+
:object2 => "ok",
|
153
|
+
:not_even_supposed => "to be here",
|
154
|
+
:stop => "whining"
|
155
|
+
)
|
156
|
+
end.should raise_error(
|
157
|
+
Conject::CompositionError,
|
158
|
+
/unexpected object.*not_even_supposed.*stop/i
|
159
|
+
)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
describe "when object map has a mix of missing and unexpected objects" do
|
164
|
+
it "raises a composition error explaining missing and rejected objects" do
|
165
|
+
lambda do
|
166
|
+
subject.new(
|
167
|
+
:object1 => "ok",
|
168
|
+
:not_even_supposed => "to be here"
|
169
|
+
)
|
170
|
+
end.should raise_error(
|
171
|
+
Conject::CompositionError,
|
172
|
+
/missing required.*object2.*unexpected object.*not_even_supposed/i
|
173
|
+
)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
describe ".object_definition" do
|
180
|
+
subject do
|
181
|
+
Class.new do
|
182
|
+
construct_with :cats, :dogs
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
it "exists on classes that use construct_with" do
|
187
|
+
subject.should respond_to(:object_definition)
|
188
|
+
end
|
189
|
+
|
190
|
+
it "references the owning class" do
|
191
|
+
subject.object_definition.owner.should == subject
|
192
|
+
end
|
193
|
+
|
194
|
+
it "contains the list of required object names for construction of new instances" do
|
195
|
+
subject.object_definition.component_names.to_set.should == [:cats,:dogs].to_set
|
196
|
+
end
|
197
|
+
|
198
|
+
describe "for classes that don't use construct_with" do
|
199
|
+
|
200
|
+
before do
|
201
|
+
append_test_load_path "simple_stuff"
|
202
|
+
require 'some_random_class'
|
203
|
+
end
|
204
|
+
|
205
|
+
after do
|
206
|
+
restore_load_path
|
207
|
+
end
|
208
|
+
|
209
|
+
it "doesn't exist" do
|
210
|
+
require 'some_random_class'
|
211
|
+
SomeRandomClass.should_not respond_to(:object_definition)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
describe ".has_object_definition?" do
|
217
|
+
it "returns false for all regular classes" do
|
218
|
+
Class.new.has_object_definition?.should be_false
|
219
|
+
end
|
220
|
+
|
221
|
+
it "returns true for classes that use construct_with" do
|
222
|
+
Class.new do construct_with :a end.has_object_definition?.should be_true
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
|
2
|
+
|
3
|
+
describe Conject::ClassFinder do
|
4
|
+
before do
|
5
|
+
append_test_load_path "simple_stuff"
|
6
|
+
require 'some_random_class'
|
7
|
+
end
|
8
|
+
|
9
|
+
after do
|
10
|
+
restore_load_path
|
11
|
+
end
|
12
|
+
|
13
|
+
it "returns the class implied by the given name" do
|
14
|
+
c = subject.find_class('some_random_class')
|
15
|
+
c.should_not be_nil
|
16
|
+
c.should == SomeRandomClass
|
17
|
+
end
|
18
|
+
|
19
|
+
it "can accept symbols for object names" do
|
20
|
+
c = subject.find_class(:some_random_class)
|
21
|
+
c.should_not be_nil
|
22
|
+
c.should == SomeRandomClass
|
23
|
+
end
|
24
|
+
|
25
|
+
it "raises an error if the name doesn't imply a regular class in the current runtime" do
|
26
|
+
lambda do
|
27
|
+
subject.find_class('something_undefined')
|
28
|
+
end.should raise_error(/could not find class.*something_undefined/i)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "raises an error for nil input" do
|
32
|
+
lambda do
|
33
|
+
subject.find_class('something_undefined')
|
34
|
+
end.should raise_error(/could not find class.*something_undefined/i)
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
|
2
|
+
|
3
|
+
|
4
|
+
describe Conject::CompositionError do
|
5
|
+
subject do
|
6
|
+
described_class.new construction_args
|
7
|
+
end
|
8
|
+
|
9
|
+
before do
|
10
|
+
append_test_load_path "simple_stuff"
|
11
|
+
require 'some_random_class'
|
12
|
+
end
|
13
|
+
|
14
|
+
after do
|
15
|
+
restore_load_path
|
16
|
+
end
|
17
|
+
|
18
|
+
let :construction_args do
|
19
|
+
{
|
20
|
+
:object_definition => object_definition,
|
21
|
+
:provided => []
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
let :object_definition do
|
26
|
+
Conject::ObjectDefinition.new(
|
27
|
+
:owner => SomeRandomClass,
|
28
|
+
:component_names => []
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "when missing one required component" do
|
33
|
+
before { object_definition.component_names << :an_object }
|
34
|
+
|
35
|
+
it "indicates the missing object" do
|
36
|
+
subject.message.should == "Wrong components when building new SomeRandomClass. Missing required object(s) [:an_object]."
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "when missing multiple required components" do
|
41
|
+
before do
|
42
|
+
object_definition.component_names << :an_object
|
43
|
+
object_definition.component_names << :another_object
|
44
|
+
end
|
45
|
+
|
46
|
+
it "indicates all missing objects" do
|
47
|
+
subject.message.should == "Wrong components when building new SomeRandomClass. Missing required object(s) [:an_object, :another_object]."
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "when an unexpected component is provided" do
|
52
|
+
before { construction_args[:provided] = [ :surprise ] }
|
53
|
+
|
54
|
+
it "calls out the unexpected object" do
|
55
|
+
subject.message.should == "Wrong components when building new SomeRandomClass. Unexpected object(s) provided [:surprise]."
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "when multiple unexpected components are provided" do
|
60
|
+
before { construction_args[:provided] = [ :surprise, :hello ] }
|
61
|
+
it "calls out all unexpected objects" do
|
62
|
+
subject.message.should == "Wrong components when building new SomeRandomClass. Unexpected object(s) provided [:surprise, :hello]."
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "when unexpected components are provided AND required components are missing" do
|
67
|
+
before do
|
68
|
+
object_definition.component_names << :part_one
|
69
|
+
object_definition.component_names << :part_two
|
70
|
+
object_definition.component_names << :part_three
|
71
|
+
construction_args[:provided] = [ :part_two, :surprise_one, :surprise_two ]
|
72
|
+
end
|
73
|
+
|
74
|
+
it "generates a message for both missing AND unexpected components" do
|
75
|
+
subject.message.should == "Wrong components when building new SomeRandomClass. Missing required object(s) [:part_one, :part_three]. Unexpected object(s) provided [:surprise_one, :surprise_two]."
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "without object_definition" do
|
80
|
+
it "has a vague message" do
|
81
|
+
construction_args.delete :object_definition
|
82
|
+
construction_args[:provided] = [ :one, :two ]
|
83
|
+
subject.message.should == "Failed to construct... something. Provided objects were: [:one, :two]"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "with nil construction args" do
|
88
|
+
let :construction_args do nil end
|
89
|
+
|
90
|
+
it "has a vague message" do
|
91
|
+
subject.message.should == "Failed to construct... something."
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "without any args supplied at all" do
|
96
|
+
let :construction_args do nil end
|
97
|
+
it "has a vague message" do
|
98
|
+
described_class.new.message.should == "Failed to construct... something."
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe "without an object_definition.owner" do
|
103
|
+
let :object_definition do
|
104
|
+
Conject::ObjectDefinition.new(
|
105
|
+
:component_names => [:a, :b]
|
106
|
+
# no owner
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "uses a placeholder name, but otherwise generates good info" do
|
111
|
+
subject.message.should == "Wrong components when building new object. Missing required object(s) [:a, :b]."
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "without provided object names" do
|
116
|
+
it "indicates which components are required but not what's missing" do
|
117
|
+
object_definition.component_names << :wanting
|
118
|
+
construction_args.delete :provided
|
119
|
+
# p object_definition
|
120
|
+
subject.message.should == "Wrong components when building new SomeRandomClass. Missing required object(s) [:wanting]."
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|