conject 0.1.6 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,7 @@
1
+ Conject v0.1.7
2
+ * Added :class and :specialize object configurations
3
+ * Fixed a bug in the error handler that catches exceptions in object constructors
4
+
1
5
  Conject v0.1.6
2
6
  * Object#contextual_name provides the string name of an object as it is keyed in its context
3
7
 
@@ -25,7 +29,7 @@ Conject v0.0.9
25
29
  * Classes with module namespaces can now refer to collaborators in construct_with by omitting the full path corresponding to the object namespace
26
30
 
27
31
  Conject v0.0.8
28
- * peer_objects lets you declare within one object that if some subcontext should request an object, that object should be instantiated in THIS context
32
+ * object_peers lets you declare within one object that if some subcontext should request an object, that object should be instantiated in THIS context
29
33
 
30
34
  Conject v0.0.7
31
35
  (re-release due to release automation fail on my part)
data/README.md CHANGED
@@ -78,7 +78,7 @@ object peers within the class definition of that owner object. This ensures tha
78
78
  objects to be instantiated in those subcontexts as well.
79
79
 
80
80
  class Game
81
- peer_objects :missile_coordinator, :wind_affector
81
+ object_peers: :missile_coordinator, :wind_affector
82
82
  end
83
83
 
84
84
  In this example, the instantiation of a Game instance in an object context will cause missile coordinator and wind affector to be "anchored"
@@ -104,3 +104,26 @@ This is more or less expressive shorthand for:
104
104
  ...EXCEPT that it's a lazy approach: using 'is' means nothing actually gets built or set in the context before it
105
105
  is requested or composed into another object.
106
106
 
107
+ # Specialization via Composition#
108
+
109
+ To declare objects as instances of a specific class, you may specify the :class of the object via #configure_objects.
110
+ Furthermore, you can specialize the instance by indicating which specific objects you would like to supply as the object's collaborators:
111
+
112
+ context.configure_objects({
113
+ adventure_time: {
114
+ class: Team,
115
+ specialize: {
116
+ hero: "finn",
117
+ sidekick: "jake" }},
118
+ dark_knight: {
119
+ class: Team,
120
+ specialize: {
121
+ hero: "batman",
122
+ sidekick: "robin" }}
123
+ })
124
+
125
+ The keys in :specialize match some or all of the keys defined in the class (in this case, the Team class is defined to be #construct_with :hero, :sidekick, :clock.
126
+ The values are object names within the ObjectContext; they will be resolved in the usual manner.
127
+ Components that a Class wants to be built with, but which you omit from the :specialize Hash, will be resolved in the usual way.
128
+
129
+ Using :class and :specialize is NOT THE SAME as building objects within a subcontext. Objects mentioned here are expected to be found and kept within the defining ObjectContext.
@@ -9,9 +9,10 @@ module Conject
9
9
  # This method assumes the Class has_object_defintion? (Client code should
10
10
  # determine that before invoking this method.)
11
11
  #
12
- def resolve_for_class(klass, object_context)
12
+ def resolve_for_class(klass, object_context, remapping=nil)
13
+ remapping ||= {}
13
14
  klass.object_definition.component_names.inject({}) do |obj_map, name|
14
- obj_map[name] = search_for(klass, object_context, name)
15
+ obj_map[name] = search_for(klass, object_context, remapping[name.to_sym] || remapping[name.to_s] || name)
15
16
  obj_map
16
17
  end
17
18
  end
@@ -8,12 +8,16 @@ module Conject
8
8
  config = object_context.get_object_config(name)
9
9
  lambda_constructor = config[:construct]
10
10
  alias_for = config[:is]
11
+ klass = config[:class]
12
+ raise "#{klass.inspect} is not a Class" if klass and !(Class === klass)
11
13
  if alias_for
14
+ # This object is defined as simply being another name for a different object
12
15
  begin
13
16
  object = object_context.get(alias_for)
14
17
  rescue Exception => ex
15
18
  raise "(When attempting to fill alias '#{name}' with actual object '#{alias_for}') #{ex.message}"
16
19
  end
20
+
17
21
  elsif lambda_constructor
18
22
  case lambda_constructor.arity
19
23
  when 0
@@ -25,8 +29,12 @@ module Conject
25
29
  else
26
30
  raise "Constructor lambda takes 0, 1 or 2 params; this lambda takes #{lambda_constructor.arity}"
27
31
  end
32
+
33
+ elsif klass
34
+ # This object has a specified class
35
+ object = type_1_constructor(klass, name, object_context, config[:specialize]) # Specify class
28
36
  else
29
- object = type_1_constructor(name, object_context)
37
+ object = type_1_constructor(nil, name, object_context, config[:specialize]) # Let class be determined by name
30
38
  end
31
39
 
32
40
  # Stuff an internal back reference to the object context into the new object:
@@ -42,8 +50,8 @@ module Conject
42
50
  # - Assume we're looking for a class to create an instance with
43
51
  # - it may or may not have a declared list of named objects it needs to be constructed with
44
52
  #
45
- def type_1_constructor(name, object_context)
46
- klass = class_finder.find_class(name)
53
+ def type_1_constructor(klass, name, object_context, overrides=nil)
54
+ klass ||= class_finder.find_class(name)
47
55
 
48
56
  if !klass.object_peers.empty?
49
57
  anchor_object_peers object_context, klass.object_peers
@@ -51,7 +59,7 @@ module Conject
51
59
 
52
60
  constructor_func = nil
53
61
  if klass.has_object_definition?
54
- object_map = dependency_resolver.resolve_for_class(klass, object_context)
62
+ object_map = dependency_resolver.resolve_for_class(klass, object_context, overrides)
55
63
  constructor_func = lambda do klass.new(object_map) end
56
64
 
57
65
  elsif Utilities.has_zero_arg_constructor?(klass)
@@ -67,7 +75,8 @@ module Conject
67
75
  begin
68
76
  object = constructor_func.call
69
77
  rescue Exception => ex
70
- origin = "#{e.message}\n\t#{e.backtrace.join("\n\t")}"
78
+ origin = "#{ex.message}\n\t#{ex.backtrace.join("\n\t")}"
79
+ name ||= "(no name)"
71
80
  raise "Error while constructing object '#{name}' of class #{klass}: #{origin}"
72
81
  end
73
82
  end
@@ -1,3 +1,3 @@
1
1
  module Conject
2
- VERSION = "0.1.6"
2
+ VERSION = "0.1.7"
3
3
  end
@@ -0,0 +1,60 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper")
2
+
3
+ describe "object specialization" do
4
+ subject { new_object_context }
5
+
6
+ before do
7
+ append_test_load_path "specialization"
8
+ require 'finn'
9
+ require 'jake'
10
+ require 'batman'
11
+ require 'robin'
12
+ require 'clock'
13
+ require 'team'
14
+ require 'floor'
15
+ end
16
+
17
+ after do
18
+ restore_load_path
19
+ end
20
+
21
+
22
+ it "lets you define an object by class and a subset of specialized components" do
23
+ subject.configure_objects({
24
+ adventure_time: {
25
+ class: Team,
26
+ specialize: {
27
+ hero: "finn",
28
+ sidekick: "jake" }},
29
+ dark_knight: {
30
+ class: Team,
31
+ specialize: {
32
+ hero: "batman",
33
+ sidekick: "robin" }}
34
+ })
35
+
36
+ adventure_time = subject[:adventure_time]
37
+ adventure_time.class.should == Team
38
+ adventure_time.hero.should be
39
+ adventure_time.hero.should == subject[:finn]
40
+ adventure_time.hero.floor.should == subject[:floor]
41
+ adventure_time.sidekick.should be
42
+ adventure_time.sidekick.should == subject[:jake]
43
+ adventure_time.sidekick.floor.should == subject[:floor]
44
+ adventure_time.clock.should be
45
+ adventure_time.clock.should == subject[:clock]
46
+
47
+ dark_knight = subject[:dark_knight]
48
+ dark_knight.class.should == Team
49
+ dark_knight.hero.should be
50
+ dark_knight.hero.should == subject[:batman]
51
+ dark_knight.hero.floor.should == subject[:floor]
52
+ dark_knight.sidekick.should be
53
+ dark_knight.sidekick.should == subject[:robin]
54
+ dark_knight.sidekick.floor.should == subject[:floor]
55
+ dark_knight.clock.should be
56
+ dark_knight.clock.should == subject[:clock]
57
+ end
58
+
59
+ end
60
+
@@ -10,7 +10,7 @@ describe Conject::DependencyResolver do
10
10
  @objects = objects
11
11
  end
12
12
  def get(name)
13
- @objects[name]
13
+ @objects[name.to_sym] || @object[name.to_s]
14
14
  end
15
15
  end
16
16
 
@@ -20,7 +20,7 @@ describe Conject::DependencyResolver do
20
20
  end
21
21
  end
22
22
 
23
- let :oc_objects do { cow: "the cow", dog: "the dog" } end
23
+ let :oc_objects do { cow: "the cow", dog: "the dog", bessy: "the bessy", rover: "the rover" } end
24
24
 
25
25
  let :object_context do StubbedObjectContext.new(oc_objects) end
26
26
 
@@ -37,6 +37,22 @@ describe Conject::DependencyResolver do
37
37
  }
38
38
  end
39
39
 
40
+ it "uses optional remapping to look for different object names" do
41
+ remapping = {dog: :rover}
42
+ subject.resolve_for_class(klass, object_context, remapping).should == {
43
+ cow: "the cow",
44
+ dog: "the rover"
45
+ }
46
+ end
47
+
48
+ it "optional remapping is string/symbol insensitive" do
49
+ remapping = {dog: "rover", cow: :bessy}
50
+ subject.resolve_for_class(klass, object_context, remapping).should == {
51
+ cow: "the bessy",
52
+ dog: "the rover"
53
+ }
54
+ end
55
+
40
56
  describe "when the class is in a module" do
41
57
  before do
42
58
  class_finder.stub(:get_module_path).with(klass).and_return("a/module/path")
@@ -50,8 +66,6 @@ describe Conject::DependencyResolver do
50
66
  dog: "the relative dog",
51
67
  }
52
68
  end
53
-
54
-
55
69
  end
56
70
  end
57
71
 
@@ -17,18 +17,18 @@ describe Conject::ObjectFactory do
17
17
 
18
18
  let :object_context do mock(:object_context) end
19
19
 
20
- let :my_object_class do mock(:my_object_class) end
20
+ let :my_object_class do Class.new end
21
21
  let :my_object do mock(:my_object) end
22
22
  let :my_objects_components do mock(:my_objects_components) end
23
23
 
24
24
  describe "#construct_new" do
25
25
  describe "for Type 1 object construction" do
26
+ let(:object_config) do {} end
26
27
  before do
27
- object_context.stub(:get_object_config).and_return({})
28
+ object_context.stub(:get_object_config).and_return(object_config)
28
29
  my_object_class.stub(:class_def_private) # the effect of this is tested in acceptance tests
29
30
  my_object_class.stub(:object_peers).and_return([]) # the effect of this is tested in acceptance tests
30
31
 
31
- class_finder.should_receive(:find_class).with(my_object_name).and_return my_object_class
32
32
  Conject::Utilities.stub(:has_zero_arg_constructor?).and_return true
33
33
  end
34
34
 
@@ -36,18 +36,58 @@ describe Conject::ObjectFactory do
36
36
  before do
37
37
  my_object_class.should_receive(:has_object_definition?).and_return true
38
38
  end
39
+ describe "and no specialization has been made" do
40
+ before do class_finder.should_receive(:find_class).with(my_object_name).and_return my_object_class end
41
+ it "finds the object definition, pulls its deps, and instantiates a new instance" do
42
+ dependency_resolver.should_receive(:resolve_for_class).with(my_object_class, object_context, nil).and_return my_objects_components
43
+ my_object_class.should_receive(:new).with(my_objects_components).and_return(my_object)
44
+
45
+ subject.construct_new(my_object_name, object_context).should == my_object
46
+ end
47
+ end
39
48
 
40
- it "finds the object definition, pulls its deps, and instantiates a new instance" do
41
- dependency_resolver.should_receive(:resolve_for_class).with(my_object_class, object_context).and_return my_objects_components
42
- my_object_class.should_receive(:new).with(my_objects_components).and_return(my_object)
49
+ describe "when specialization is provided" do
50
+ let(:overrides) do { remapped: "names" } end
51
+ let(:object_config) do { :class => my_object_class, :specialize => overrides } end
43
52
 
44
- subject.construct_new(my_object_name, object_context).should == my_object
53
+ before do
54
+ class_finder.should_not_receive(:find_class)#.with(my_object_name).and_return my_object_class
55
+ end
56
+
57
+ it "uses the specified class and remapped objects to reolve and build the object" do
58
+ dependency_resolver.should_receive(:resolve_for_class).with(my_object_class, object_context, overrides).and_return my_objects_components
59
+ my_object_class.should_receive(:new).with(my_objects_components).and_return(my_object)
60
+ subject.construct_new(my_object_name, object_context).should == my_object
61
+ end
45
62
  end
63
+
64
+ describe "when :class is configured" do
65
+ let(:object_config) do { :class => my_object_class } end
66
+
67
+ before do
68
+ class_finder.should_not_receive(:find_class)#.with(my_object_name).and_return my_object_class
69
+ end
70
+
71
+ it "uses the specified class to build the object" do
72
+ dependency_resolver.should_receive(:resolve_for_class).with(my_object_class, object_context, nil).and_return my_objects_components
73
+ my_object_class.should_receive(:new).with(my_objects_components).and_return(my_object)
74
+ subject.construct_new(my_object_name, object_context).should == my_object
75
+ end
76
+
77
+ end
78
+
46
79
  end
80
+ describe "when :class is not a Class" do
81
+ let(:object_config) do { :class => "Oops! not a Class" } end
82
+ it "raises" do
83
+ lambda do subject.construct_new(my_object_name, object_context) end.should raise_error(/is not a Class/)
84
+ end
85
+ end
47
86
 
48
87
  describe "when target class has no object definition" do
49
88
  before do
50
89
  my_object_class.should_receive(:has_object_definition?).and_return false
90
+ class_finder.should_receive(:find_class).with(my_object_name).and_return my_object_class
51
91
  end
52
92
 
53
93
  it "creates a new instance of the class without any arguments" do
@@ -60,6 +100,7 @@ describe Conject::ObjectFactory do
60
100
  before do
61
101
  my_object_class.should_receive(:has_object_definition?).and_return false
62
102
  Conject::Utilities.stub(:has_zero_arg_constructor?).and_return false
103
+ class_finder.should_receive(:find_class).with(my_object_name).and_return my_object_class
63
104
  end
64
105
 
65
106
  it "raises a CompositionError" do
@@ -67,9 +108,23 @@ describe Conject::ObjectFactory do
67
108
  subject.construct_new(my_object_name, object_context)
68
109
  end.should raise_error(ArgumentError)
69
110
  end
111
+
112
+ end
113
+
114
+ describe "when target constructor raises an error" do
115
+ it "re-raises with an explanation" do
116
+ class_finder.should_receive(:find_class).with(my_object_name).and_return my_object_class
117
+ my_object_class.stub(:has_object_definition?).and_return false
118
+ my_object_class.should_receive(:new).and_raise("BOOM")
119
+ lambda do
120
+ subject.construct_new(my_object_name, object_context)
121
+ end.should raise_error(/Error while constructing object '#{my_object_name}'.*BOOM/)
122
+ end
70
123
  end
71
124
  end
72
125
 
126
+
127
+
73
128
  describe "for custom lambda construction" do
74
129
  let(:object_config) do { :construct => lambda do "The Object" end } end
75
130
  let(:object_config2) do { :construct => lambda do |object_context| { :the_oc => object_context } end } end
@@ -0,0 +1,4 @@
1
+ class Batman
2
+ construct_with :floor
3
+ public :floor
4
+ end
@@ -0,0 +1,2 @@
1
+ class Clock
2
+ end
@@ -0,0 +1,4 @@
1
+ class Finn
2
+ construct_with :floor
3
+ public :floor
4
+ end
@@ -0,0 +1,2 @@
1
+ class Floor
2
+ end
@@ -0,0 +1,4 @@
1
+ class Jake
2
+ construct_with :floor
3
+ public :floor
4
+ end
@@ -0,0 +1,4 @@
1
+ class Robin
2
+ construct_with :floor
3
+ public :floor
4
+ end
@@ -0,0 +1,5 @@
1
+ class Team
2
+ construct_with :hero, :sidekick, :clock
3
+
4
+ public :hero, :sidekick, :clock
5
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: conject
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-23 00:00:00.000000000 Z
12
+ date: 2013-02-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -133,6 +133,7 @@ files:
133
133
  - spec/acceptance/regression/nested_contexts_spec.rb
134
134
  - spec/acceptance/regression/non_singleton_spec.rb
135
135
  - spec/acceptance/regression/object_peers_spec.rb
136
+ - spec/acceptance/regression/specialize_spec.rb
136
137
  - spec/conject/class_ext_construct_with_spec.rb
137
138
  - spec/conject/class_finder_spec.rb
138
139
  - spec/conject/composition_error_spec.rb
@@ -183,6 +184,13 @@ files:
183
184
  - spec/test_data/object_peers/game.rb
184
185
  - spec/test_data/simple_stuff/class_that_uses_context_in_init.rb
185
186
  - spec/test_data/simple_stuff/some_random_class.rb
187
+ - spec/test_data/specialization/batman.rb
188
+ - spec/test_data/specialization/clock.rb
189
+ - spec/test_data/specialization/finn.rb
190
+ - spec/test_data/specialization/floor.rb
191
+ - spec/test_data/specialization/jake.rb
192
+ - spec/test_data/specialization/robin.rb
193
+ - spec/test_data/specialization/team.rb
186
194
  - src/user_model.rb
187
195
  - src/user_presenter.rb
188
196
  - src/user_view.rb
@@ -200,7 +208,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
200
208
  version: '0'
201
209
  segments:
202
210
  - 0
203
- hash: -2147591316143027162
211
+ hash: -2747476053760078928
204
212
  required_rubygems_version: !ruby/object:Gem::Requirement
205
213
  none: false
206
214
  requirements:
@@ -209,7 +217,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
209
217
  version: '0'
210
218
  segments:
211
219
  - 0
212
- hash: -2147591316143027162
220
+ hash: -2747476053760078928
213
221
  requirements: []
214
222
  rubyforge_project:
215
223
  rubygems_version: 1.8.24
@@ -232,6 +240,7 @@ test_files:
232
240
  - spec/acceptance/regression/nested_contexts_spec.rb
233
241
  - spec/acceptance/regression/non_singleton_spec.rb
234
242
  - spec/acceptance/regression/object_peers_spec.rb
243
+ - spec/acceptance/regression/specialize_spec.rb
235
244
  - spec/conject/class_ext_construct_with_spec.rb
236
245
  - spec/conject/class_finder_spec.rb
237
246
  - spec/conject/composition_error_spec.rb
@@ -282,3 +291,10 @@ test_files:
282
291
  - spec/test_data/object_peers/game.rb
283
292
  - spec/test_data/simple_stuff/class_that_uses_context_in_init.rb
284
293
  - spec/test_data/simple_stuff/some_random_class.rb
294
+ - spec/test_data/specialization/batman.rb
295
+ - spec/test_data/specialization/clock.rb
296
+ - spec/test_data/specialization/finn.rb
297
+ - spec/test_data/specialization/floor.rb
298
+ - spec/test_data/specialization/jake.rb
299
+ - spec/test_data/specialization/robin.rb
300
+ - spec/test_data/specialization/team.rb