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