diy 1.0.0 → 1.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.
@@ -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