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.
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