conject 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/.gitignore +4 -0
  2. data/.rvmrc +2 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +32 -0
  5. data/NOTES.txt +61 -0
  6. data/README.md +8 -0
  7. data/Rakefile +12 -0
  8. data/TODO +9 -0
  9. data/conject.gemspec +21 -0
  10. data/lib/conject.rb +40 -0
  11. data/lib/conject/borrowed_active_support_inflector.rb +525 -0
  12. data/lib/conject/class_ext_construct_with.rb +123 -0
  13. data/lib/conject/class_finder.rb +11 -0
  14. data/lib/conject/composition_error.rb +33 -0
  15. data/lib/conject/dependency_resolver.rb +16 -0
  16. data/lib/conject/extended_metaid.rb +33 -0
  17. data/lib/conject/object_context.rb +61 -0
  18. data/lib/conject/object_definition.rb +10 -0
  19. data/lib/conject/object_factory.rb +28 -0
  20. data/lib/conject/utilities.rb +8 -0
  21. data/lib/conject/version.rb +3 -0
  22. data/rake_tasks/rspec.rake +25 -0
  23. data/spec/acceptance/dev/README +7 -0
  24. data/spec/acceptance/regression/README +12 -0
  25. data/spec/acceptance/regression/basic_composition_spec.rb +29 -0
  26. data/spec/acceptance/regression/basic_object_creation_spec.rb +42 -0
  27. data/spec/acceptance/regression/nested_contexts_spec.rb +86 -0
  28. data/spec/conject/borrowed_active_support_inflector_spec.rb +28 -0
  29. data/spec/conject/class_ext_construct_with_spec.rb +226 -0
  30. data/spec/conject/class_finder_spec.rb +36 -0
  31. data/spec/conject/composition_error_spec.rb +124 -0
  32. data/spec/conject/dependency_resolver_spec.rb +32 -0
  33. data/spec/conject/extended_metaid_spec.rb +90 -0
  34. data/spec/conject/object_context_spec.rb +186 -0
  35. data/spec/conject/object_definition_spec.rb +31 -0
  36. data/spec/conject/object_factory_spec.rb +89 -0
  37. data/spec/conject/utilities_spec.rb +30 -0
  38. data/spec/spec_helper.rb +24 -0
  39. data/spec/support/SPEC_HELPERS_GO_HERE +0 -0
  40. data/spec/support/load_path_helpers.rb +27 -0
  41. data/spec/test_data/basic_composition/fence.rb +5 -0
  42. data/spec/test_data/basic_composition/front_desk.rb +2 -0
  43. data/spec/test_data/basic_composition/grass.rb +2 -0
  44. data/spec/test_data/basic_composition/guest.rb +5 -0
  45. data/spec/test_data/basic_composition/lobby.rb +5 -0
  46. data/spec/test_data/basic_composition/nails.rb +2 -0
  47. data/spec/test_data/basic_composition/tv.rb +2 -0
  48. data/spec/test_data/basic_composition/wood.rb +2 -0
  49. data/spec/test_data/simple_stuff/some_random_class.rb +2 -0
  50. data/spike/arity_funny_business_in_different_ruby_versions.rb +34 -0
  51. data/spike/depends_on_spike.rb +146 -0
  52. data/spike/donkey_fail.rb +48 -0
  53. data/spike/donkey_journey.rb +50 -0
  54. data/spike/go.rb +11 -0
  55. data/spike/metaid.rb +28 -0
  56. data/spike/object_definition.rb +125 -0
  57. data/spike/sample.rb +125 -0
  58. data/src/user_model.rb +4 -0
  59. data/src/user_presenter.rb +10 -0
  60. data/src/user_view.rb +3 -0
  61. 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