diy 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,13 @@
1
+ == 1.0.1 / 2007-12-02
2
+
3
+ * Added 'using_namespace' directive for assuming a module for a group of object defs
4
+ * Added 'use_class_directly' option for configuring an ObjectDef. Instead of instantiating an instance
5
+ of the specified class, the class itself is referenced. (Good for injecting ActiveRecord classes into
6
+ other components in the guise of factories.
7
+ * Added DIY::Context.auto_require boolean setting. When false, the library of a
8
+ class is not autoloaded ahead of object construction. Is true by default.
9
+ * 'auto_require' can, if neccessary, be set in the YAML config for an individual object.
10
+
1
11
  == 1.0.0 / 2007-11-19
2
12
 
3
13
  * Released!
data/README.txt CHANGED
@@ -1,126 +1,186 @@
1
- diy
1
+ == DIY
2
2
 
3
3
  * http://rubyforge.org/projects/atomicobjectrb/
4
4
  * http://atomicobjectrb.rubyforge.org/diy
5
5
 
6
6
  == DESCRIPTION:
7
7
 
8
- DIY (Dependency Injection in Yaml) is a simple dependency injection library
8
+ DIY (Dependency Injection in YAML) is a simple dependency injection library
9
9
  which focuses on declarative composition of objects through constructor injection.
10
10
 
11
- Currently, all objects that get components put into them must have a
12
- constructor that gets a hash with symbols as keys.
13
- Best used with constructor.rb
14
-
15
- Auto-naming and auto-library support is done.
11
+ == INSTALL:
16
12
 
17
- == FEATURES/PROBLEMS:
18
-
19
- * Constructor-based dependency injection container using YAML input.
13
+ * gem install diy
20
14
 
21
15
  == SYNOPSIS:
22
16
 
23
- === A Simple Context
17
+ === Common Usage
24
18
 
25
- The context is a hash specified in in a yaml file. Each top-level key identifies
26
- an object. When the context is created and queried for an object, by default,
27
- the context will require a file with the same name:
19
+ Author a YAML file that describes your objects and how they fit together.
20
+ This means you're building a Hash whose keys are the object names, and whose
21
+ values are Hashes that define the object.
28
22
 
29
- require 'foo'
23
+ The following context defines an automobile engine:
30
24
 
31
- Next, by default, it will call new on a class from the camel-cased name of the key:
25
+ context.yml:
26
+ ---
27
+ engine:
28
+ compose: throttle, block
29
+ throttle:
30
+ compose: cable, pedal
31
+ block:
32
+ cable:
33
+ pedal:
34
+
35
+ In your code, use DIY to load the YAML, then access its parts:
36
+
37
+ context = DIY::Context.from_file('context.yml')
38
+ context[:engine] => <Engine:0x81eb0>
39
+
40
+ This approach assumes:
41
+
42
+ * You've got classes for Engine, Throttle, Block, Cable and Pedal
43
+ * They're defined in engine.rb, throttle.rb, etc
44
+ * The library files are in your load-path
45
+ * Engine and Throttle both have a constructor that accepts a Hash. The Hash
46
+ will contain keys 'throttle', 'block' (for Engine) and 'cable, 'pedal' (for Throttle)
47
+ and the values will be references to their respective objects.
48
+ * Block, Cable and Pedal all have default constructors that accept no arguments
49
+
50
+ Sample code for Engine's constructor:
51
+
52
+ class Engine
53
+ def initialize(components)
54
+ @throttle = components['throttle']
55
+ @block = components['block']
56
+ end
57
+ end
32
58
 
33
- Foo.new
59
+ Writing code like that is repetetive; that's why we created the Constructor gem, which lets you
60
+ specify object components using the "constructor" class method:
34
61
 
35
- foo.rb:
36
- class Foo; end
62
+ * http://atomicobjectrb.rubyforge.org/constructor
37
63
 
38
- context.yml:
39
- ---
40
- foo:
41
- bar:
64
+ Using constructor, you can write Engine like this:
42
65
 
43
- c = DIY::Context.from_file('context.yml')
44
- c[:foo] => <Foo:0x81eb0>
66
+ class Engine
67
+ constructor :throttle, :block
68
+ end
45
69
 
46
- === Specifying Ruby File to Require
70
+ === Special Cases
47
71
 
48
- If the file the class resides in isn't named after they key:
72
+ If your object has a lot of components (or they have big names) you can specify an array of component names
73
+ as opposed to a comma-separated list:
49
74
 
50
- fun_stuff.rb:
51
- class Foo; end
52
-
53
- context.yml:
54
- ---
55
- foo:
56
- lib: fun_stuff
57
- bar:
75
+ engine:
76
+ compose:
77
+ - throttle
78
+ - block
58
79
 
59
- === Constructor Arguments
80
+ Sometimes you won't be able to rely on DIY's basic assumptions about class names and library files.
60
81
 
61
- DIY allows specification of constructor arguments as hash key-value pairs
62
- using the <tt>compose</tt> directive.
82
+ * You can specify the 'class' option
83
+ * You can specify the 'library' option. If you do not, the library is inferred from the class name.
84
+ (Eg, My::Train::Station will be sought in "my/train/station.rb"
63
85
 
64
- foo.rb:
65
- class Foo
66
- def initialize(args)
67
- @bar = args[:bar]
68
- @other = args[:other]
69
- end
70
- end
86
+ engine:
87
+ class: FourHorse::Base
88
+ library: general_engines/base
89
+ compose: throttle, block
90
+
91
+ If the Hash coming into your constructor needs to have some keys that do not exactly match the official
92
+ object names, you can specify them one-by-one:
93
+
94
+ engine:
95
+ the_throttle: throttle
96
+ the_block: block
97
+
98
+ === Non-singleton objects
71
99
 
72
- context.yml:
73
- ---
100
+ Non-singletons are named objects that provide a new instance every time you ask for them.
101
+ By default, DIY considers all objects to be singletons. To override, use the "singleton" setting and
102
+ set it to false:
103
+
74
104
  foo:
75
- compose: bar, other
76
- bar:
77
- other:
105
+ singleton: false
106
+
107
+ === Sub-Contexts
108
+
109
+ Sub-contexts are useful for creating isolated object networks that may need to be instantiated
110
+ zero or many times in your application. Objects defined in subcontexts can reference "upward" to
111
+ their surroundings, as well as objects in the subcontext itself.
112
+
113
+ If you wanted to be able to make more than one Engine from the preceding examples, you might try:
114
+
115
+ ---
116
+ epa_regulations:
117
+
118
+ +automotive_plant:
119
+ engine:
120
+ compose: block, throttle, epa_regulations
121
+ block:
122
+ throttle:
78
123
 
79
- === Using DIY with constructor.rb:
124
+ Each time you delve into the automotive_plant, you get a solar system of the defined objects.
125
+ In this context, the objects are singleton-like. The next time you invoke the subcontext, however,
126
+ you'll be working with a fresh set of objects... another solar system with the same layout, so to speak.
80
127
 
81
- foo.rb:
82
- class Foo
83
- constructor :bar, :other
128
+ Subcontexts are not initialized until you call upon them, which you do using the "within" method:
129
+
130
+ context = DIY::Context.from_file('context.yml')
131
+ context.within('automotive_plant') do |plant|
132
+ puts plant[:engine]
84
133
  end
85
134
 
86
- If the constructor argument names don't match up with the object keys
87
- in the context, they can be mapped explicitly.
135
+ === Direct Class References
88
136
 
89
- foo.rb:
90
- class Foo
91
- constructor :bar, :other
92
- end
137
+ Occasionally you will have a class at your disposal that you'd like to provide directly as components
138
+ to other objects (as opposed to getting _instances_ of that class, you want to reference the class itself, eg,
139
+ to use its factory methods). Enter the "use_class_directly" flag:
93
140
 
94
- context.yml:
95
141
  ---
96
- foo:
97
- bar: my_bar
98
- other: the_other_one
99
- my_bar:
100
- the_other_one:
142
+ customer_order_finder:
143
+ class: CustomerOrder
144
+ use_class_directly: true
101
145
 
102
- === Non-singleton objects
103
-
104
- Non-singletons will be re-instantiated each time they are needed.
146
+ This can be handy in Rails when you'd like to use some of class methods on an ActiveRecord subclass, but
147
+ you'd like to avoid direct ActiveRecord class usage in your code. In this case, the customer_order_finder
148
+ is actually the CustomerOrder class, and so, it has methods like "find" and "destroy_all".
105
149
 
106
- context.yml:
107
- ---
108
- foo:
109
- singleton: false
150
+ === Namespace Convenience
110
151
 
111
- bar:
152
+ If you find yourself writing context entries like this:
112
153
 
154
+ ---
113
155
  engine:
114
- compose: foo, bar
156
+ class: Car::Parts::Engine
157
+ throttle:
158
+ class: Car::Parts::Block
159
+ cable:
160
+ class: Car::Parts::Cable
115
161
 
116
- == REQUIREMENTS:
162
+ You can set the "assumed" module for a group of objects like this:
117
163
 
118
- * rubygems
119
- * works best with constructor
164
+ ---
165
+ using_namespace Car Parts:
166
+ engine:
120
167
 
121
- == INSTALL:
168
+ throttle:
122
169
 
123
- * gem install diy
170
+ block:
171
+
172
+ === Preventing auto-requiring of library files
173
+
174
+ Normally, DIY will "require" the library for an object just before it instantiates the object.
175
+ If this is not desired (in Rails, auto-require can lead to library double-load issues), you
176
+ can deactivate auto-require. There is a global default setting (handled in code) and
177
+ a per-object override (handled in the context YAML):
178
+
179
+ DIY::Context.auto_require = false
180
+
181
+ ---
182
+ engine:
183
+ auto_require: false
124
184
 
125
185
  == LICENSE:
126
186
 
data/Rakefile CHANGED
@@ -15,3 +15,12 @@ Hoe.new('diy', DIY::VERSION) do |p|
15
15
  p.test_globs = 'test/*_test.rb'
16
16
  p.extra_deps << ['constructor', '>= 1.0.0']
17
17
  end
18
+
19
+ load "../tools/tasks/homepage.rake"
20
+
21
+ load "../tools/tasks/release_tagging.rake"
22
+ ReleaseTagging.new do |t|
23
+ t.package = "diy"
24
+ t.version = DIY::VERSION
25
+ end
26
+
data/lib/diy.rb CHANGED
@@ -1,9 +1,18 @@
1
1
  require 'yaml'
2
2
  require 'set'
3
3
 
4
- module DIY
5
- VERSION = '1.0.0'
4
+ module DIY #:nodoc:#
5
+ VERSION = '1.0.1'
6
6
  class Context
7
+
8
+ class << self
9
+ # Enable / disable automatic requiring of libraries. Default: true
10
+ attr_accessor :auto_require
11
+ end
12
+ @auto_require = true
13
+
14
+ # Accepts a Hash defining the object context (usually loaded from objects.yml), and an additional
15
+ # Hash containing objects to inject into the context.
7
16
  def initialize(context_hash, extra_inputs={})
8
17
  raise "Nil context hash" unless context_hash
9
18
  raise "Need a hash" unless context_hash.kind_of?(Hash)
@@ -21,25 +30,7 @@ module DIY
21
30
  @extra_inputs = extra_inputs
22
31
  end
23
32
 
24
- # Collect object and subcontext definitions
25
- @defs = {}
26
- @sub_context_defs = {}
27
- context_hash.each do |name,info|
28
- name = name.to_s
29
- case name
30
- when /^\+/
31
- # subcontext
32
- @sub_context_defs[name.gsub(/^\+/,'')] = info
33
-
34
- else
35
- # Normal singleton object def
36
- if extra_inputs_has(name)
37
- raise ConstructionError.new(name, "Object definition conflicts with parent context")
38
- end
39
- @defs[name] = ObjectDef.new(:name => name, :info => info)
40
- end
41
- end
42
-
33
+ collect_object_and_subcontext_defs context_hash
43
34
 
44
35
  # init the cache
45
36
  @cache = {}
@@ -47,16 +38,21 @@ module DIY
47
38
  end
48
39
 
49
40
 
41
+ # Convenience: create a new DIY::Context by loading from a String (or open file handle.)
50
42
  def self.from_yaml(io_or_string, extra_inputs={})
51
43
  raise "nil input to YAML" unless io_or_string
52
44
  Context.new(YAML.load(io_or_string), extra_inputs)
53
45
  end
54
46
 
47
+ # Convenience: create a new DIY::Context by loading from the named file.
55
48
  def self.from_file(fname, extra_inputs={})
56
49
  raise "nil file name" unless fname
57
50
  self.from_yaml(File.read(fname), extra_inputs)
58
51
  end
59
52
 
53
+ # Return a reference to the object named. If necessary, the object will
54
+ # be instantiated on first use. If the object is non-singleton, a new
55
+ # object will be produced each time.
60
56
  def get_object(obj_name)
61
57
  key = obj_name.to_s
62
58
  obj = @cache[key]
@@ -73,6 +69,8 @@ module DIY
73
69
  end
74
70
  alias :[] :get_object
75
71
 
72
+ # Inject a named object into the Context. This must be done before the Context has instantiated the
73
+ # object in question.
76
74
  def set_object(obj_name,obj)
77
75
  key = obj_name.to_s
78
76
  raise "object '#{key}' already exists in context" if @cache.keys.include?(key)
@@ -80,6 +78,7 @@ module DIY
80
78
  end
81
79
  alias :[]= :set_object
82
80
 
81
+ # Provide a listing of object names
83
82
  def keys
84
83
  (@defs.keys.to_set + @extra_inputs.keys.to_set).to_a
85
84
  end
@@ -95,11 +94,16 @@ module DIY
95
94
  yield context
96
95
  end
97
96
 
97
+ # Returns true if the context contains an object with the given name
98
98
  def contains_object(obj_name)
99
99
  key = obj_name.to_s
100
100
  @defs.keys.member?(key) or extra_inputs_has(key)
101
101
  end
102
102
 
103
+ # Every top level object in the Context is instantiated. This is especially useful for
104
+ # systems that have "floating observers"... objects that are never directly accessed, who
105
+ # would thus never be instantiated by coincedence. This does not build any subcontexts
106
+ # that may exist.
103
107
  def build_everything
104
108
  @defs.keys.each { |k| self[k] }
105
109
  end
@@ -108,13 +112,56 @@ module DIY
108
112
 
109
113
  private
110
114
 
115
+ def collect_object_and_subcontext_defs(context_hash)
116
+ @defs = {}
117
+ @sub_context_defs = {}
118
+ get_defs_from context_hash
119
+ end
120
+
121
+ def get_defs_from(hash, namespace=nil)
122
+ hash.each do |name,info|
123
+ name = name.to_s
124
+ case name
125
+ when /^\+/
126
+ # subcontext
127
+ @sub_context_defs[name.gsub(/^\+/,'')] = info
128
+
129
+ when /^using_namespace/
130
+ # namespace: use a module(s) prefix for the classname of contained object defs
131
+ # NOTE: namespacing is NOT scope... it's just a convenient way to setup class names for a group of objects.
132
+ get_defs_from info, parse_namespace(name)
133
+
134
+ else
135
+ # Normal object def
136
+ info ||= {}
137
+ if extra_inputs_has(name)
138
+ raise ConstructionError.new(name, "Object definition conflicts with parent context")
139
+ end
140
+ unless info.has_key?('auto_require')
141
+ info['auto_require'] = self.class.auto_require
142
+ end
143
+ if namespace
144
+ if info['class']
145
+ info['class'] = namespace.build_classname(info['class'])
146
+ else
147
+ info['class'] = namespace.build_classname(name)
148
+ end
149
+ end
150
+
151
+ @defs[name] = ObjectDef.new(:name => name, :info => info)
152
+
153
+ end
154
+ end
155
+ end
156
+
157
+
111
158
  def construct_object(key)
112
159
  # Find the object definition
113
160
  obj_def = @defs[key]
114
161
  raise "No object definition for '#{key}'" unless obj_def
115
162
 
116
163
  # If object def mentions a library, load it
117
- require obj_def.library if obj_def.library
164
+ require search_for_file(obj_def.library) if obj_def.library
118
165
 
119
166
  # Resolve all components for the object
120
167
  arg_hash = {}
@@ -131,7 +178,9 @@ module DIY
131
178
  # Get a reference to the class for the object
132
179
  big_c = get_class_for_name_with_module_delimeters(obj_def.class_name)
133
180
  # Make and return the instance
134
- if arg_hash.keys.size > 0
181
+ if obj_def.use_class_directly?
182
+ return big_c
183
+ elsif arg_hash.keys.size > 0
135
184
  return big_c.new(arg_hash)
136
185
  else
137
186
  return big_c.new
@@ -142,6 +191,15 @@ module DIY
142
191
  raise cerr
143
192
  end
144
193
 
194
+ def search_for_file(path_suffix)
195
+ path_suffix = path_suffix + '.rb' unless path_suffix =~ /\.rb$/
196
+ $LOAD_PATH.each do |root|
197
+ path = File.join(root, path_suffix)
198
+ return path if File.file? path
199
+ end
200
+ raise ConstructionError, "no such file to load -- #{path_suffix}"
201
+ end
202
+
145
203
  def get_class_for_name_with_module_delimeters(class_name)
146
204
  class_name.split(/::/).inject(Object) do |mod,const_name| mod.const_get(const_name) end
147
205
  end
@@ -152,8 +210,33 @@ module DIY
152
210
  end
153
211
  @extra_inputs.keys.member?(key) or @extra_inputs.keys.member?(key.to_sym)
154
212
  end
213
+
214
+ def parse_namespace(str)
215
+ Namespace.new(str)
216
+ end
155
217
  end
156
218
 
219
+ class Namespace #:nodoc:#
220
+ def initialize(str)
221
+ # 'using_namespace Animal Reptile'
222
+ parts = str.split(/\s+/)
223
+ raise "Namespace definitions must begin with 'using_namespace'" unless parts[0] == 'using_namespace'
224
+ parts.shift
225
+
226
+ if parts.length > 0 and parts[0] =~ /::/
227
+ parts = parts[0].split(/::/)
228
+ end
229
+
230
+ raise NamespaceError, "Namespace needs to indicate a module" if parts.empty?
231
+
232
+ @module_nest = parts
233
+ end
234
+
235
+ def build_classname(name)
236
+ [ @module_nest, Infl.camelize(name) ].flatten.join("::")
237
+ end
238
+ end
239
+
157
240
  class Lookup #:nodoc:
158
241
  attr_reader :name
159
242
  def initialize(obj_name)
@@ -178,13 +261,19 @@ module DIY
178
261
  # Class name
179
262
  @class_name = info.delete 'class'
180
263
  @class_name ||= info.delete 'type'
181
- @class_name ||= camelize(@name)
264
+ @class_name ||= Infl.camelize(@name)
265
+
266
+ # Auto Require
267
+ @auto_require = info.delete 'auto_require'
182
268
 
183
269
  # Library
184
270
  @library = info.delete 'library'
185
271
  @library ||= info.delete 'lib'
186
- @library ||= underscore(@class_name)
272
+ @library ||= Infl.underscore(@class_name) if @auto_require
187
273
 
274
+ # Use Class Directly
275
+ @use_class_directly = info.delete 'use_class_directly'
276
+
188
277
  # Auto-compose
189
278
  compose = info.delete 'compose'
190
279
  if compose
@@ -223,20 +312,14 @@ module DIY
223
312
  @singleton
224
313
  end
225
314
 
226
- private
227
- # Ganked this from Inflector:
228
- def camelize(lower_case_and_underscored_word)
229
- lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
230
- end
231
- # Ganked this from Inflector:
232
- def underscore(camel_cased_word)
233
- camel_cased_word.to_s.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').downcase
234
- end
315
+ def use_class_directly?
316
+ @use_class_directly == true
317
+ end
318
+
235
319
  end
236
320
 
237
- # Exception raised when an object can't be created which is defined in the context.
238
- class ConstructionError < RuntimeError
239
- def initialize(object_name, cause=nil) #:nodoc:
321
+ class ConstructionError < RuntimeError #:nodoc:#
322
+ def initialize(object_name, cause=nil)
240
323
  object_name = object_name
241
324
  cause = cause
242
325
  m = "Failed to construct '#{object_name}'"
@@ -246,4 +329,18 @@ module DIY
246
329
  super m
247
330
  end
248
331
  end
332
+
333
+ class NamespaceError < RuntimeError #:nodoc:#
334
+ end
335
+
336
+ module Infl #:nodoc:#
337
+ # Ganked this from Inflector:
338
+ def self.camelize(lower_case_and_underscored_word)
339
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
340
+ end
341
+ # Ganked this from Inflector:
342
+ def self.underscore(camel_cased_word)
343
+ camel_cased_word.to_s.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').downcase
344
+ end
345
+ end
249
346
  end
@@ -7,35 +7,13 @@ class DIYTest < Test::Unit::TestCase
7
7
 
8
8
  def setup
9
9
  # Add load paths:
10
- %w|gnu dog cat yak donkey goat horse fud non_singleton|.each do |p|
10
+ %w|gnu dog cat yak donkey goat horse fud non_singleton namespace|.each do |p|
11
11
  libdir = path_to_test_file(p)
12
12
  $: << libdir unless $:.member?(libdir)
13
13
  end
14
+ DIY::Context.auto_require = true # Restore default
14
15
  end
15
16
 
16
- #
17
- # HELPERS
18
- #
19
- def path_to_test_file(fname)
20
- path_to("/files/#{fname}")
21
- end
22
-
23
- def load_context(file_name)
24
- hash = YAML.load(File.read(path_to_test_file(file_name)))
25
- load_hash(hash)
26
- end
27
-
28
- def load_hash(hash)
29
- @diy = DIY::Context.new(hash)
30
- end
31
-
32
- def check_dog_objects(context)
33
- assert_not_nil context, "nil context"
34
- names = %w|dog_presenter dog_model dog_view file_resolver|
35
- names.each do |n|
36
- assert context.contains_object(n), "Context had no object '#{n}'"
37
- end
38
- end
39
17
 
40
18
  #
41
19
  # TESTS
@@ -85,6 +63,12 @@ class DIYTest < Test::Unit::TestCase
85
63
  assert_not_nil @diy['thinger'], "Should have got my thinger (which is hiding in a couple modules)"
86
64
  end
87
65
 
66
+ def test_use_class_directly
67
+ load_hash 'thinger' => {'class' => "DiyTesting::Bar::Foo", 'lib' => 'foo', 'use_class_directly' => true}
68
+ @diy.build_everything
69
+ assert_equal DiyTesting::Bar::Foo, @diy['thinger'], "Should be the class 'object'"
70
+ end
71
+
88
72
  def test_classname_inside_a_module_derives_the_namespaced_classname_from_the_underscored_object_def_key
89
73
  load_hash 'foo/bar/qux' => nil
90
74
  @diy.build_everything
@@ -413,6 +397,14 @@ class DIYTest < Test::Unit::TestCase
413
397
  assert_same thinger, @diy[:thinger]
414
398
  end
415
399
 
400
+ def test_should_be_able_to_turn_off_auto_require_for_all_objects
401
+ DIY::Context.auto_require = false
402
+ load_context 'horse/objects.yml'
403
+
404
+ exception = assert_raise(DIY::ConstructionError) { @diy['holder_thing'] }
405
+ assert_match(/uninitialized constant/, exception.message)
406
+ end
407
+
416
408
  def test_should_cause_non_singletons_to_be_rebuilt_every_time_they_are_accessed
417
409
  load_context 'non_singleton/objects.yml'
418
410
 
@@ -479,4 +471,90 @@ class DIYTest < Test::Unit::TestCase
479
471
  assert thread_spinner1.object_id != cat.thread_spinner.object_id, "cat's thread spinner matched the other spinner; should not be so"
480
472
  end
481
473
  end
474
+
475
+ def test_should_provide_syntax_for_using_namespace
476
+ # This test exercises single and triple-level namespaces for nested
477
+ # modules, and their interaction with other namespaced-objects.
478
+ load_context "namespace/objects.yml"
479
+
480
+ %w{road sky cat bird lizard turtle}.each do |obj|
481
+ assert @diy.contains_object(obj), "Context had no object '#{obj}'"
482
+ end
483
+
484
+ road = @diy['road']
485
+ sky = @diy['sky']
486
+ cat = @diy['cat']
487
+ bird = @diy['bird']
488
+ lizard = @diy['lizard']
489
+ turtle = @diy['turtle']
490
+
491
+ assert_same road, cat.road, "Cat has wrong Road"
492
+ assert_same sky, bird.sky, "Bird has wrong Sky"
493
+ assert_same bird, lizard.bird, "Lizard has wrong Bird"
494
+ end
495
+
496
+ def test_should_combine_a_given_class_name_with_the_namespace
497
+ load_context "namespace/class_name_combine.yml"
498
+ assert_not_nil @diy['garfield'], "No garfield"
499
+ assert_kind_of Animal::Cat, @diy['garfield'], "Garfield wrong"
500
+ end
501
+
502
+ def test_should_let_you_use_namespaces_in_subcontexts
503
+ load_context "namespace/subcontext.yml"
504
+ @diy.build_everything
505
+ %w{road sky cat turtle}.each do |obj|
506
+ assert @diy.contains_object(obj), "Main context had no object '#{obj}'"
507
+ end
508
+ sky = @diy['sky']
509
+
510
+ @diy.within("aviary") do |subc|
511
+ assert subc.contains_object("bird"), "Sub context didn't have 'bird'"
512
+ assert subc.contains_object("lizard"), "Sub context didn't have 'lizard'"
513
+ bird = subc['bird']
514
+ lizard = subc['lizard']
515
+ assert_same sky, bird.sky, "Bird has wrong Sky"
516
+ assert_same bird, lizard.bird, "Lizard has wrong Bird"
517
+ end
518
+ end
519
+
520
+ def test_should_raise_for_namespace_w_no_modules_named
521
+ ex = assert_raises DIY::NamespaceError do
522
+ load_context "namespace/no_module_specified.yml"
523
+ end
524
+ assert_equal "Namespace needs to indicate a module", ex.message
525
+ end
526
+
527
+ def test_should_raise_for_namespace_whose_modules_dont_exist
528
+ load_context "namespace/bad_module_specified.yml"
529
+ ex = assert_raises DIY::ConstructionError do
530
+ @diy['bird']
531
+ end
532
+ assert_match(/failed to construct/i, ex.message)
533
+ assert_match(/no such file to load -- fuzzy_creature\/bird/, ex.message)
534
+ end
535
+
536
+ #
537
+ # HELPERS
538
+ #
539
+ def path_to_test_file(fname)
540
+ path_to("/files/#{fname}")
541
+ end
542
+
543
+ def load_context(file_name)
544
+ hash = YAML.load(File.read(path_to_test_file(file_name)))
545
+ load_hash(hash)
546
+ end
547
+
548
+ def load_hash(hash)
549
+ @diy = DIY::Context.new(hash)
550
+ end
551
+
552
+ def check_dog_objects(context)
553
+ assert_not_nil context, "nil context"
554
+ names = %w|dog_presenter dog_model dog_view file_resolver|
555
+ names.each do |n|
556
+ assert context.contains_object(n), "Context had no object '#{n}'"
557
+ end
558
+ end
559
+
482
560
  end
@@ -1,4 +1,3 @@
1
- require 'constructor'
2
1
  class Cat
3
2
  constructor :heritage, :food, :strict => true, :accessors => true
4
3
  end
@@ -1,4 +1,3 @@
1
- require 'constructor'
2
1
  class DogModel
3
2
  constructor :file_resolver, :other_thing, :strict => true, :accessors => true
4
3
  end
@@ -1,4 +1,3 @@
1
- require 'constructor'
2
1
  class DogPresenter
3
2
  constructor :model, :view, :strict => true, :accessors => true
4
3
  end
@@ -1,4 +1,3 @@
1
- require 'constructor'
2
1
 
3
2
  class Toy
4
3
  constructor :widget, :trinket, :accessors => true, :strict => true
@@ -1,5 +1,4 @@
1
1
 
2
- require 'constructor'
3
2
 
4
3
  class Thinger
5
4
  constructor :injected
@@ -1,5 +1,4 @@
1
1
  require 'base'
2
- require 'constructor'
3
2
  class Plane < Base
4
3
  constructor :wings, :strict => true
5
4
  def setup
@@ -1,4 +1,3 @@
1
- require 'constructor'
2
1
  class HolderThing
3
2
  constructor :thing_held, :strict => true, :accessors => true
4
3
  end
@@ -1,4 +1,3 @@
1
- require 'constructor'
2
1
  class FatCat
3
2
  constructor :thread_spinner, :tick, :yard, :accessors => true
4
3
  end
@@ -1,4 +1,3 @@
1
- require 'constructor'
2
1
  class Pig
3
2
  constructor :thread_spinner, :yard, :accessors => true
4
3
  end
@@ -1,4 +1,3 @@
1
- require 'constructor'
2
1
  class ThreadSpinner
3
2
  constructor :air, :accessors => true
4
3
  end
@@ -1,4 +1,3 @@
1
- require 'constructor'
2
1
  class Tick
3
2
  constructor :thread_spinner, :accessors => true
4
3
  end
@@ -1,4 +1,3 @@
1
- require 'constructor'
2
1
  class CoreModel
3
2
  constructor :data_source, :strict => true
4
3
  end
@@ -1,4 +1,3 @@
1
- require 'constructor'
2
1
  class CorePresenter
3
2
  constructor :model, :view, :strict => true
4
3
  end
@@ -1,4 +1,3 @@
1
- require 'constructor'
2
1
  class FringeModel
3
2
  constructor :connected, :accessors => true, :strict => true
4
3
  end
@@ -1,4 +1,3 @@
1
- require 'constructor'
2
1
  class FringePresenter
3
2
  constructor :fringe_model, :fringe_view, :strict => true
4
3
  end
@@ -1,4 +1,3 @@
1
- require 'constructor'
2
1
  class GiantSquid
3
2
  constructor :fringe_view, :core_model, :krill, :accessors => true
4
3
  end
@@ -6,6 +6,7 @@ require 'fileutils'
6
6
  require 'find'
7
7
  require 'yaml'
8
8
  require 'ostruct'
9
+ require "#{here}/constructor"
9
10
 
10
11
  class Test::Unit::TestCase
11
12
  include FileUtils
metadata CHANGED
@@ -1,33 +1,45 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.9.0
3
- specification_version: 1
4
2
  name: diy
5
3
  version: !ruby/object:Gem::Version
6
- version: 1.0.0
7
- date: 2007-11-20 00:00:00 -05:00
8
- summary: Constructor-based dependency injection container using YAML input.
9
- require_paths:
10
- - lib
11
- email: dev@atomicobject.com
12
- homepage:
13
- rubyforge_project: atomicobjectrb
14
- description: "== DESCRIPTION: DIY (Dependency Injection in Yaml) is a simple dependency injection library which focuses on declarative composition of objects through constructor injection. Currently, all objects that get components put into them must have a constructor that gets a hash with symbols as keys. Best used with constructor.rb Auto-naming and auto-library support is done."
15
- autorequire:
16
- default_executable:
17
- bindir: bin
18
- has_rdoc: true
19
- required_ruby_version: !ruby/object:Gem::Version::Requirement
20
- requirements:
21
- - - ">"
22
- - !ruby/object:Gem::Version
23
- version: 0.0.0
24
- version:
25
- platform: ruby
26
- signing_key:
27
- cert_chain:
28
- post_install_message:
4
+ version: 1.0.1
5
+ platform: ""
29
6
  authors:
30
7
  - Atomic Object
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2007-12-03 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: constructor
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.0.0
23
+ version:
24
+ - !ruby/object:Gem::Dependency
25
+ name: hoe
26
+ version_requirement:
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 1.3.0
32
+ version:
33
+ description: "== DESCRIPTION: DIY (Dependency Injection in YAML) is a simple dependency injection library which focuses on declarative composition of objects through constructor injection. == INSTALL: * gem install diy"
34
+ email: dev@atomicobject.com
35
+ executables: []
36
+
37
+ extensions: []
38
+
39
+ extra_rdoc_files:
40
+ - History.txt
41
+ - Manifest.txt
42
+ - README.txt
31
43
  files:
32
44
  - History.txt
33
45
  - Manifest.txt
@@ -82,37 +94,32 @@ files:
82
94
  - test/files/yak/my_objects.yml
83
95
  - test/files/yak/sub_sub_context_test.yml
84
96
  - test/test_helper.rb
85
- test_files:
86
- - test/diy_test.rb
97
+ has_rdoc: true
98
+ homepage:
99
+ post_install_message:
87
100
  rdoc_options:
88
101
  - --main
89
102
  - README.txt
90
- extra_rdoc_files:
91
- - History.txt
92
- - Manifest.txt
93
- - README.txt
94
- executables: []
95
-
96
- extensions: []
97
-
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: "0"
110
+ version:
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: "0"
116
+ version:
98
117
  requirements: []
99
118
 
100
- dependencies:
101
- - !ruby/object:Gem::Dependency
102
- name: constructor
103
- version_requirement:
104
- version_requirements: !ruby/object:Gem::Version::Requirement
105
- requirements:
106
- - - ">="
107
- - !ruby/object:Gem::Version
108
- version: 1.0.0
109
- version:
110
- - !ruby/object:Gem::Dependency
111
- name: hoe
112
- version_requirement:
113
- version_requirements: !ruby/object:Gem::Version::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: 1.3.0
118
- version:
119
+ rubyforge_project: atomicobjectrb
120
+ rubygems_version: 0.9.5
121
+ signing_key:
122
+ specification_version: 2
123
+ summary: Constructor-based dependency injection container using YAML input.
124
+ test_files:
125
+ - test/diy_test.rb