diy 1.1 → 1.1.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.
- data/History.txt +3 -0
- data/Manifest.txt +3 -0
- data/README.txt +20 -3
- data/Rakefile +5 -2
- data/lib/diy.rb +232 -194
- data/test/diy_test.rb +24 -14
- data/test/factory_test.rb +79 -0
- data/test/files/functions/attached_things_builder.rb +2 -0
- data/test/files/functions/method_extractor.rb +3 -0
- data/test/files/functions/objects.yml +18 -1
- data/test/files/functions/thing_builder.rb +4 -0
- data/test/files/functions/things_builder.rb +3 -0
- data/test/test_helper.rb +16 -0
- metadata +16 -7
data/History.txt
CHANGED
data/Manifest.txt
CHANGED
@@ -36,11 +36,14 @@ test/files/donkey/foo.rb
|
|
36
36
|
test/files/donkey/foo/bar/qux.rb
|
37
37
|
test/files/fud/objects.yml
|
38
38
|
test/files/fud/toy.rb
|
39
|
+
test/files/functions/attached_things_builder.rb
|
39
40
|
test/files/functions/invalid_method.yml
|
41
|
+
test/files/functions/method_extractor.rb
|
40
42
|
test/files/functions/nonsingleton_objects.yml
|
41
43
|
test/files/functions/objects.yml
|
42
44
|
test/files/functions/thing.rb
|
43
45
|
test/files/functions/thing_builder.rb
|
46
|
+
test/files/functions/things_builder.rb
|
44
47
|
test/files/gnu/objects.yml
|
45
48
|
test/files/gnu/thinger.rb
|
46
49
|
test/files/goat/base.rb
|
data/README.txt
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
== DIY
|
2
2
|
|
3
|
-
* http://rubyforge.org/projects/atomicobjectrb/
|
4
3
|
* http://atomicobjectrb.rubyforge.org/diy
|
4
|
+
* http://rubyforge.org/projects/atomicobjectrb/
|
5
5
|
|
6
6
|
== DESCRIPTION:
|
7
7
|
|
@@ -182,9 +182,26 @@ a per-object override (handled in the context YAML):
|
|
182
182
|
engine:
|
183
183
|
auto_require: false
|
184
184
|
|
185
|
+
=== Factories
|
186
|
+
|
187
|
+
It is possible to create factories automatically with DIY:
|
188
|
+
|
189
|
+
---
|
190
|
+
car_dealer:
|
191
|
+
compose: car_factory
|
192
|
+
|
193
|
+
car_factory:
|
194
|
+
builds: car
|
195
|
+
|
196
|
+
Then you can use the factory to easily build objects:
|
197
|
+
|
198
|
+
context = DIY::Context.from_file('context.yml')
|
199
|
+
context[:car_factory].create => <Car:0x81eb0>
|
200
|
+
|
185
201
|
=== Method Directive
|
186
|
-
|
187
|
-
|
202
|
+
|
203
|
+
This introduces the concept of first class methods. An object can now be constructed with a method object
|
204
|
+
bound to a particular object in the diy context.
|
188
205
|
|
189
206
|
---
|
190
207
|
trinket_builder:
|
data/Rakefile
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'hoe'
|
3
|
-
|
3
|
+
|
4
|
+
|
5
|
+
$: << "lib"
|
6
|
+
require 'diy.rb'
|
4
7
|
|
5
8
|
task :default => [ :test ]
|
6
9
|
|
@@ -9,7 +12,7 @@ Hoe.new('diy', DIY::VERSION) do |p|
|
|
9
12
|
p.author = 'Atomic Object'
|
10
13
|
p.email = 'dev@atomicobject.com'
|
11
14
|
p.summary = 'Constructor-based dependency injection container using YAML input.'
|
12
|
-
p.description = p.paragraphs_of('README.txt',
|
15
|
+
p.description = p.paragraphs_of('README.txt', 3).join("\n\n")
|
13
16
|
p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
|
14
17
|
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
15
18
|
p.test_globs = 'test/*_test.rb'
|
data/lib/diy.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
+
require 'diy/factory.rb'
|
1
2
|
require 'yaml'
|
2
3
|
require 'set'
|
3
4
|
|
4
5
|
module DIY #:nodoc:#
|
5
|
-
VERSION = '1.1'
|
6
|
-
|
6
|
+
VERSION = '1.1.1'
|
7
|
+
class Context
|
7
8
|
|
8
9
|
class << self
|
9
10
|
# Enable / disable automatic requiring of libraries. Default: true
|
@@ -13,118 +14,140 @@ module DIY #:nodoc:#
|
|
13
14
|
|
14
15
|
# Accepts a Hash defining the object context (usually loaded from objects.yml), and an additional
|
15
16
|
# Hash containing objects to inject into the context.
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
17
|
+
def initialize(context_hash, extra_inputs={})
|
18
|
+
raise "Nil context hash" unless context_hash
|
19
|
+
raise "Need a hash" unless context_hash.kind_of?(Hash)
|
20
|
+
[ "[]", "keys" ].each do |mname|
|
21
|
+
unless extra_inputs.respond_to?(mname)
|
22
|
+
raise "Extra inputs must respond to hash-like [] operator and methods #keys and #each"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# store extra inputs
|
27
|
+
if extra_inputs.kind_of?(Hash)
|
28
|
+
@extra_inputs= {}
|
29
|
+
extra_inputs.each { |k,v| @extra_inputs[k.to_s] = v } # smooth out the names
|
30
|
+
else
|
31
|
+
@extra_inputs = extra_inputs
|
32
|
+
end
|
32
33
|
|
33
34
|
collect_object_and_subcontext_defs context_hash
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
# init the cache
|
37
|
+
@cache = {}
|
38
|
+
@cache['this_context'] = self
|
39
|
+
end
|
39
40
|
|
40
41
|
|
41
42
|
# Convenience: create a new DIY::Context by loading from a String (or open file handle.)
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
43
|
+
def self.from_yaml(io_or_string, extra_inputs={})
|
44
|
+
raise "nil input to YAML" unless io_or_string
|
45
|
+
Context.new(YAML.load(io_or_string), extra_inputs)
|
46
|
+
end
|
46
47
|
|
47
48
|
# Convenience: create a new DIY::Context by loading from the named file.
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
def self.from_file(fname, extra_inputs={})
|
50
|
+
raise "nil file name" unless fname
|
51
|
+
self.from_yaml(File.read(fname), extra_inputs)
|
52
|
+
end
|
52
53
|
|
53
54
|
# Return a reference to the object named. If necessary, the object will
|
54
55
|
# be instantiated on first use. If the object is non-singleton, a new
|
55
56
|
# object will be produced each time.
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
57
|
+
def get_object(obj_name)
|
58
|
+
key = obj_name.to_s
|
59
|
+
obj = @cache[key]
|
60
|
+
unless obj
|
61
|
+
if extra_inputs_has(key)
|
62
|
+
obj = @extra_inputs[key]
|
63
|
+
else
|
64
|
+
case @defs[key]
|
65
|
+
when MethodDef
|
66
|
+
obj = construct_method(key)
|
67
|
+
when FactoryDef
|
68
|
+
obj = construct_factory(key)
|
69
|
+
@cache[key] = obj
|
70
|
+
else
|
71
|
+
obj = construct_object(key)
|
72
|
+
@cache[key] = obj if @defs[key].singleton?
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
obj
|
77
|
+
end
|
78
|
+
alias :[] :get_object
|
76
79
|
|
77
80
|
# Inject a named object into the Context. This must be done before the Context has instantiated the
|
78
81
|
# object in question.
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
82
|
+
def set_object(obj_name,obj)
|
83
|
+
key = obj_name.to_s
|
84
|
+
raise "object '#{key}' already exists in context" if @cache.keys.include?(key)
|
85
|
+
@cache[key] = obj
|
86
|
+
end
|
87
|
+
alias :[]= :set_object
|
85
88
|
|
86
89
|
# Provide a listing of object names
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
+
def keys
|
91
|
+
(@defs.keys.to_set + @extra_inputs.keys.to_set).to_a
|
92
|
+
end
|
90
93
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
94
|
+
# Instantiate and yield the named subcontext
|
95
|
+
def within(sub_context_name)
|
96
|
+
# Find the subcontext definitaion:
|
97
|
+
context_def = @sub_context_defs[sub_context_name.to_s]
|
98
|
+
raise "No sub-context named #{sub_context_name}" unless context_def
|
99
|
+
# Instantiate a new context using self as parent:
|
100
|
+
context = Context.new( context_def, self )
|
98
101
|
|
99
|
-
|
100
|
-
|
102
|
+
yield context
|
103
|
+
end
|
101
104
|
|
102
105
|
# Returns true if the context contains an object with the given name
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
106
|
+
def contains_object(obj_name)
|
107
|
+
key = obj_name.to_s
|
108
|
+
@defs.keys.member?(key) or extra_inputs_has(key)
|
109
|
+
end
|
107
110
|
|
108
111
|
# Every top level object in the Context is instantiated. This is especially useful for
|
109
112
|
# systems that have "floating observers"... objects that are never directly accessed, who
|
110
113
|
# would thus never be instantiated by coincedence. This does not build any subcontexts
|
111
114
|
# that may exist.
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
115
|
+
def build_everything
|
116
|
+
@defs.keys.each { |k| self[k] }
|
117
|
+
end
|
118
|
+
alias :build_all :build_everything
|
119
|
+
alias :preinstantiate_singletons :build_everything
|
117
120
|
|
118
|
-
|
121
|
+
private
|
119
122
|
|
120
123
|
def collect_object_and_subcontext_defs(context_hash)
|
121
|
-
|
122
|
-
|
124
|
+
@defs = {}
|
125
|
+
@sub_context_defs = {}
|
123
126
|
get_defs_from context_hash
|
124
127
|
end
|
125
128
|
|
126
129
|
def get_defs_from(hash, namespace=nil)
|
127
130
|
hash.each do |name,info|
|
131
|
+
# we modify the info hash below so it's important to have a new
|
132
|
+
# instance to play with
|
133
|
+
info = info.dup if info
|
134
|
+
|
135
|
+
# see if we are building a factory
|
136
|
+
if info and info.has_key?('builds')
|
137
|
+
unless info.has_key?('auto_require')
|
138
|
+
info['auto_require'] = self.class.auto_require
|
139
|
+
end
|
140
|
+
|
141
|
+
if namespace
|
142
|
+
info['builds'] = namespace.build_classname(info['builds'])
|
143
|
+
end
|
144
|
+
@defs[name] = FactoryDef.new({:name => name,
|
145
|
+
:target => info['builds'],
|
146
|
+
:library => info['library'],
|
147
|
+
:auto_require => info['auto_require']})
|
148
|
+
next
|
149
|
+
end
|
150
|
+
|
128
151
|
name = name.to_s
|
129
152
|
case name
|
130
153
|
when /^\+/
|
@@ -135,11 +158,12 @@ module DIY #:nodoc:#
|
|
135
158
|
# namespace: use a module(s) prefix for the classname of contained object defs
|
136
159
|
# NOTE: namespacing is NOT scope... it's just a convenient way to setup class names for a group of objects.
|
137
160
|
get_defs_from info, parse_namespace(name)
|
138
|
-
when /^method/
|
161
|
+
when /^method\s/
|
139
162
|
key_name = name.gsub(/^method\s/, "")
|
140
163
|
@defs[key_name] = MethodDef.new(:name => key_name,
|
141
164
|
:object => info['object'],
|
142
|
-
:method => info['method']
|
165
|
+
:method => info['method'],
|
166
|
+
:attach => info['attach'])
|
143
167
|
else
|
144
168
|
# Normal object def
|
145
169
|
info ||= {}
|
@@ -163,69 +187,83 @@ module DIY #:nodoc:#
|
|
163
187
|
end
|
164
188
|
end
|
165
189
|
|
166
|
-
|
167
190
|
def construct_method(key)
|
168
191
|
method_definition = @defs[key]
|
169
192
|
object = get_object(method_definition.object)
|
170
|
-
|
193
|
+
method = object.method(method_definition.method)
|
194
|
+
|
195
|
+
unless method_definition.attach.nil?
|
196
|
+
instance_var_name = "@__diy_#{method_definition.object}"
|
197
|
+
|
198
|
+
method_definition.attach.each do |object_key|
|
199
|
+
get_object(object_key).instance_eval do
|
200
|
+
instance_variable_set(instance_var_name, object)
|
201
|
+
eval %|def #{key}(*args)
|
202
|
+
#{instance_var_name}.#{method_definition.method}(*args)
|
203
|
+
end|
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
return method
|
171
209
|
rescue Exception => oops
|
172
210
|
build_and_raise_construction_error(key, oops)
|
173
211
|
end
|
174
212
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
require obj_def.library
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
213
|
+
def construct_object(key)
|
214
|
+
# Find the object definition
|
215
|
+
obj_def = @defs[key]
|
216
|
+
raise "No object definition for '#{key}'" unless obj_def
|
217
|
+
# If object def mentions a library, load it
|
218
|
+
require obj_def.library if obj_def.library
|
219
|
+
|
220
|
+
# Resolve all components for the object
|
221
|
+
arg_hash = {}
|
222
|
+
obj_def.components.each do |name,value|
|
223
|
+
case value
|
224
|
+
when Lookup
|
225
|
+
arg_hash[name.to_sym] = get_object(value.name)
|
226
|
+
when StringValue
|
227
|
+
arg_hash[name.to_sym] = value.literal_value
|
228
|
+
else
|
229
|
+
raise "Cannot cope with component definition '#{value.inspect}'"
|
230
|
+
end
|
231
|
+
end
|
232
|
+
# Get a reference to the class for the object
|
233
|
+
big_c = get_class_for_name_with_module_delimeters(obj_def.class_name)
|
234
|
+
# Make and return the instance
|
197
235
|
if obj_def.use_class_directly?
|
198
236
|
return big_c
|
199
237
|
elsif arg_hash.keys.size > 0
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
238
|
+
return big_c.new(arg_hash)
|
239
|
+
else
|
240
|
+
return big_c.new
|
241
|
+
end
|
242
|
+
rescue Exception => oops
|
243
|
+
build_and_raise_construction_error(key, oops)
|
244
|
+
end
|
245
|
+
|
246
|
+
def build_and_raise_construction_error(key, oops)
|
247
|
+
cerr = ConstructionError.new(key,oops)
|
248
|
+
cerr.set_backtrace(oops.backtrace)
|
249
|
+
raise cerr
|
250
|
+
end
|
251
|
+
|
252
|
+
def get_class_for_name_with_module_delimeters(class_name)
|
253
|
+
class_name.split(/::/).inject(Object) do |mod,const_name| mod.const_get(const_name) end
|
254
|
+
end
|
255
|
+
|
256
|
+
def extra_inputs_has(key)
|
257
|
+
if key.nil? or key.strip == ''
|
258
|
+
raise ArgumentError.new("Cannot lookup objects with nil keys")
|
259
|
+
end
|
260
|
+
@extra_inputs.keys.member?(key) or @extra_inputs.keys.member?(key.to_sym)
|
261
|
+
end
|
224
262
|
|
225
263
|
def parse_namespace(str)
|
226
264
|
Namespace.new(str)
|
227
265
|
end
|
228
|
-
|
266
|
+
end
|
229
267
|
|
230
268
|
class Namespace #:nodoc:#
|
231
269
|
def initialize(str)
|
@@ -248,69 +286,69 @@ module DIY #:nodoc:#
|
|
248
286
|
end
|
249
287
|
end
|
250
288
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
289
|
+
class Lookup #:nodoc:
|
290
|
+
attr_reader :name
|
291
|
+
def initialize(obj_name)
|
292
|
+
@name = obj_name
|
293
|
+
end
|
294
|
+
end
|
257
295
|
|
258
|
-
class MethodDef
|
259
|
-
attr_accessor :name, :object, :method
|
296
|
+
class MethodDef #:nodoc:
|
297
|
+
attr_accessor :name, :object, :method, :attach
|
260
298
|
|
261
299
|
def initialize(opts)
|
262
|
-
@name, @object, @method = opts[:name], opts[:object], opts[:method]
|
300
|
+
@name, @object, @method, @attach = opts[:name], opts[:object], opts[:method], opts[:attach]
|
263
301
|
end
|
264
302
|
end
|
265
303
|
|
266
|
-
|
267
|
-
|
268
|
-
|
304
|
+
class ObjectDef #:nodoc:
|
305
|
+
attr_accessor :name, :class_name, :library, :components
|
306
|
+
def initialize(opts)
|
269
307
|
name = opts[:name]
|
270
308
|
raise "Can't make an ObjectDef without a name" if name.nil?
|
271
309
|
|
272
310
|
info = opts[:info] || {}
|
273
311
|
info = info.clone
|
274
312
|
|
275
|
-
|
313
|
+
@components = {}
|
276
314
|
|
277
|
-
|
278
|
-
|
315
|
+
# Object name
|
316
|
+
@name = name
|
279
317
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
318
|
+
# Class name
|
319
|
+
@class_name = info.delete 'class'
|
320
|
+
@class_name ||= info.delete 'type'
|
321
|
+
@class_name ||= Infl.camelize(@name)
|
284
322
|
|
285
323
|
# Auto Require
|
286
324
|
@auto_require = info.delete 'auto_require'
|
287
325
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
326
|
+
# Library
|
327
|
+
@library = info.delete 'library'
|
328
|
+
@library ||= info.delete 'lib'
|
329
|
+
@library ||= Infl.underscore(@class_name) if @auto_require
|
330
|
+
|
331
|
+
# Use Class Directly
|
332
|
+
@use_class_directly = info.delete 'use_class_directly'
|
333
|
+
|
334
|
+
# Auto-compose
|
335
|
+
compose = info.delete 'compose'
|
336
|
+
if compose
|
337
|
+
case compose
|
338
|
+
when Array
|
339
|
+
auto_names = compose.map { |x| x.to_s }
|
340
|
+
when String
|
341
|
+
auto_names = compose.split(',').map { |x| x.to_s.strip }
|
342
|
+
when Symbol
|
343
|
+
auto_names = [ compose.to_s ]
|
344
|
+
else
|
345
|
+
raise "Cannot auto compose object #{@name}, bad 'compose' format: #{compose.inspect}"
|
346
|
+
end
|
347
|
+
end
|
348
|
+
auto_names ||= []
|
349
|
+
auto_names.each do |cname|
|
350
|
+
@components[cname] = Lookup.new(cname)
|
351
|
+
end
|
314
352
|
|
315
353
|
# Singleton status
|
316
354
|
if info['singleton'].nil?
|
@@ -320,12 +358,12 @@ module DIY #:nodoc:#
|
|
320
358
|
end
|
321
359
|
info.delete 'singleton'
|
322
360
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
361
|
+
# Remaining keys
|
362
|
+
info.each do |key,val|
|
363
|
+
@components[key.to_s] = Lookup.new(val.to_s)
|
364
|
+
end
|
327
365
|
|
328
|
-
|
366
|
+
end
|
329
367
|
|
330
368
|
def singleton?
|
331
369
|
@singleton
|
@@ -335,19 +373,19 @@ module DIY #:nodoc:#
|
|
335
373
|
@use_class_directly == true
|
336
374
|
end
|
337
375
|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
376
|
+
end
|
377
|
+
|
378
|
+
class ConstructionError < RuntimeError #:nodoc:#
|
379
|
+
def initialize(object_name, cause=nil)
|
380
|
+
object_name = object_name
|
381
|
+
cause = cause
|
382
|
+
m = "Failed to construct '#{object_name}'"
|
383
|
+
if cause
|
384
|
+
m << "\n ...caused by:\n >>> #{cause}"
|
385
|
+
end
|
386
|
+
super m
|
387
|
+
end
|
388
|
+
end
|
351
389
|
|
352
390
|
class NamespaceError < RuntimeError #:nodoc:#
|
353
391
|
end
|
data/test/diy_test.rb
CHANGED
@@ -540,7 +540,7 @@ class DIYTest < Test::Unit::TestCase
|
|
540
540
|
|
541
541
|
assert_not_nil build_thing, "should not be nil"
|
542
542
|
assert_kind_of(Method, build_thing)
|
543
|
-
|
543
|
+
assert_equal(build_thing, @diy['build_thing'])
|
544
544
|
end
|
545
545
|
|
546
546
|
def test_bounded_method_can_be_used
|
@@ -564,6 +564,12 @@ class DIYTest < Test::Unit::TestCase
|
|
564
564
|
assert_not_equal(@diy['build_thing'], @diy['thing_builder'].method(:build))
|
565
565
|
end
|
566
566
|
|
567
|
+
def test_composing_bounded_methods_into_other_objects
|
568
|
+
load_context "functions/objects.yml"
|
569
|
+
@diy.build_everything
|
570
|
+
assert_equal(@diy['build_thing'], @diy['things_builder'].build_thing)
|
571
|
+
end
|
572
|
+
|
567
573
|
def test_raises_construction_error_if_invalid_method_specified
|
568
574
|
load_context "functions/invalid_method.yml"
|
569
575
|
assert_raises DIY::ConstructionError do
|
@@ -571,22 +577,26 @@ class DIYTest < Test::Unit::TestCase
|
|
571
577
|
end
|
572
578
|
end
|
573
579
|
|
580
|
+
def test_can_optionally_attach_method_to_other_objects_in_context
|
581
|
+
load_context "functions/objects.yml"
|
582
|
+
@diy.build_everything
|
583
|
+
|
584
|
+
thing = @diy['attached_things_builder'].build_thing("the name", "flying")
|
585
|
+
assert_kind_of(Thing, thing)
|
586
|
+
assert_equal("the name", thing.name)
|
587
|
+
assert_equal("flying", thing.ability)
|
588
|
+
|
589
|
+
["attached_things_builder", "things_builder"].each do |key|
|
590
|
+
thing = @diy[key].build_default_thing
|
591
|
+
assert_kind_of(Thing, thing)
|
592
|
+
assert_equal("Thing", thing.name)
|
593
|
+
assert_equal("nothing", thing.ability)
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
574
597
|
#
|
575
598
|
# HELPERS
|
576
599
|
#
|
577
|
-
def path_to_test_file(fname)
|
578
|
-
path_to("/files/#{fname}")
|
579
|
-
end
|
580
|
-
|
581
|
-
def load_context(file_name)
|
582
|
-
hash = YAML.load(File.read(path_to_test_file(file_name)))
|
583
|
-
load_hash(hash)
|
584
|
-
end
|
585
|
-
|
586
|
-
def load_hash(hash)
|
587
|
-
@diy = DIY::Context.new(hash)
|
588
|
-
end
|
589
|
-
|
590
600
|
def check_dog_objects(context)
|
591
601
|
assert_not_nil context, "nil context"
|
592
602
|
names = %w|dog_presenter dog_model dog_view file_resolver|
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/test_helper"
|
2
|
+
require 'diy'
|
3
|
+
require 'fileutils'
|
4
|
+
include FileUtils
|
5
|
+
|
6
|
+
class FactoryTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def setup
|
9
|
+
# Add load paths:
|
10
|
+
%w|factory|.each do |p|
|
11
|
+
libdir = path_to_test_file(p)
|
12
|
+
$: << libdir unless $:.member?(libdir)
|
13
|
+
end
|
14
|
+
DIY::Context.auto_require = true # Restore default
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
#
|
19
|
+
# TESTS
|
20
|
+
#
|
21
|
+
|
22
|
+
def test_creates_factory
|
23
|
+
load_context "factory/factory.yml"
|
24
|
+
|
25
|
+
cat_factory = @diy.get_object(:cat_factory)
|
26
|
+
assert_not_nil cat_factory
|
27
|
+
|
28
|
+
cat = cat_factory.create('a', 'b')
|
29
|
+
|
30
|
+
assert cat.is_a?(Kitten)
|
31
|
+
assert_equal "meow", cat.meow
|
32
|
+
assert_equal 'a', cat.a
|
33
|
+
assert_equal 'b', cat.b
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_creates_factory_with_autorequire
|
37
|
+
load_context "factory/factory.yml"
|
38
|
+
|
39
|
+
dog_factory = @diy.get_object(:dog_factory)
|
40
|
+
assert_not_nil dog_factory
|
41
|
+
|
42
|
+
dog = dog_factory.create
|
43
|
+
|
44
|
+
assert dog.is_a?(Dog)
|
45
|
+
assert_equal "woof", dog.woof
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_creates_factory_with_subcontext
|
49
|
+
load_context "factory/factory.yml"
|
50
|
+
|
51
|
+
@diy.within :inny do |context|
|
52
|
+
bull_factory = context.get_object(:bull_factory)
|
53
|
+
beef = bull_factory.create
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_creates_factory_with_subcontext_and_namespace
|
58
|
+
load_context "factory/factory.yml"
|
59
|
+
|
60
|
+
@diy.within :congress do |context|
|
61
|
+
politician = context.get_object(:politician)
|
62
|
+
pork = politician.create
|
63
|
+
assert pork.is_a?(Farm::Pork)
|
64
|
+
assert_equal "money!", pork.oink
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_creates_factory_with_namespace
|
69
|
+
load_context "factory/factory.yml"
|
70
|
+
|
71
|
+
llama_factory = @diy.get_object(:llama_factory)
|
72
|
+
assert_not_nil llama_factory
|
73
|
+
|
74
|
+
llama = llama_factory.create
|
75
|
+
|
76
|
+
assert llama.is_a?(Farm::Llama)
|
77
|
+
assert_equal "?", llama.make_llama_noise
|
78
|
+
end
|
79
|
+
end
|
@@ -1,5 +1,22 @@
|
|
1
1
|
thing_builder:
|
2
2
|
|
3
|
+
method_extractor:
|
4
|
+
|
5
|
+
attached_things_builder:
|
6
|
+
|
3
7
|
method build_thing:
|
4
8
|
object: thing_builder
|
5
|
-
method: build
|
9
|
+
method: build
|
10
|
+
attach:
|
11
|
+
- attached_things_builder
|
12
|
+
|
13
|
+
method build_default_thing:
|
14
|
+
object: thing_builder
|
15
|
+
method: build_default
|
16
|
+
attach:
|
17
|
+
- attached_things_builder
|
18
|
+
- things_builder
|
19
|
+
|
20
|
+
things_builder:
|
21
|
+
compose:
|
22
|
+
- build_thing
|
data/test/test_helper.rb
CHANGED
@@ -36,4 +36,20 @@ class Test::Unit::TestCase
|
|
36
36
|
@_tracked_tests[msym] = true
|
37
37
|
end
|
38
38
|
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# HELPERS
|
42
|
+
#
|
43
|
+
def path_to_test_file(fname)
|
44
|
+
path_to("/files/#{fname}")
|
45
|
+
end
|
46
|
+
|
47
|
+
def load_context(file_name)
|
48
|
+
hash = YAML.load(File.read(path_to_test_file(file_name)))
|
49
|
+
load_hash(hash)
|
50
|
+
end
|
51
|
+
|
52
|
+
def load_hash(hash)
|
53
|
+
@diy = DIY::Context.new(hash)
|
54
|
+
end
|
39
55
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: diy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Atomic Object
|
@@ -9,11 +9,12 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2009-11-01 01:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: constructor
|
17
|
+
type: :runtime
|
17
18
|
version_requirement:
|
18
19
|
version_requirements: !ruby/object:Gem::Requirement
|
19
20
|
requirements:
|
@@ -23,14 +24,17 @@ dependencies:
|
|
23
24
|
version:
|
24
25
|
- !ruby/object:Gem::Dependency
|
25
26
|
name: hoe
|
27
|
+
type: :development
|
26
28
|
version_requirement:
|
27
29
|
version_requirements: !ruby/object:Gem::Requirement
|
28
30
|
requirements:
|
29
31
|
- - ">="
|
30
32
|
- !ruby/object:Gem::Version
|
31
|
-
version:
|
33
|
+
version: 2.3.3
|
32
34
|
version:
|
33
|
-
description:
|
35
|
+
description: |-
|
36
|
+
DIY (Dependency Injection in YAML) is a simple dependency injection library
|
37
|
+
which focuses on declarative composition of objects through constructor injection.
|
34
38
|
email: dev@atomicobject.com
|
35
39
|
executables: []
|
36
40
|
|
@@ -42,7 +46,6 @@ extra_rdoc_files:
|
|
42
46
|
- README.txt
|
43
47
|
- TODO.txt
|
44
48
|
- homepage/Notes.txt
|
45
|
-
- test/files/namespace/hello.txt
|
46
49
|
files:
|
47
50
|
- History.txt
|
48
51
|
- Manifest.txt
|
@@ -82,11 +85,14 @@ files:
|
|
82
85
|
- test/files/donkey/foo/bar/qux.rb
|
83
86
|
- test/files/fud/objects.yml
|
84
87
|
- test/files/fud/toy.rb
|
88
|
+
- test/files/functions/attached_things_builder.rb
|
85
89
|
- test/files/functions/invalid_method.yml
|
90
|
+
- test/files/functions/method_extractor.rb
|
86
91
|
- test/files/functions/nonsingleton_objects.yml
|
87
92
|
- test/files/functions/objects.yml
|
88
93
|
- test/files/functions/thing.rb
|
89
94
|
- test/files/functions/thing_builder.rb
|
95
|
+
- test/files/functions/things_builder.rb
|
90
96
|
- test/files/gnu/objects.yml
|
91
97
|
- test/files/gnu/thinger.rb
|
92
98
|
- test/files/goat/base.rb
|
@@ -132,6 +138,8 @@ files:
|
|
132
138
|
- test/test_helper.rb
|
133
139
|
has_rdoc: true
|
134
140
|
homepage:
|
141
|
+
licenses: []
|
142
|
+
|
135
143
|
post_install_message:
|
136
144
|
rdoc_options:
|
137
145
|
- --main
|
@@ -153,9 +161,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
153
161
|
requirements: []
|
154
162
|
|
155
163
|
rubyforge_project: atomicobjectrb
|
156
|
-
rubygems_version: 1.
|
164
|
+
rubygems_version: 1.3.5
|
157
165
|
signing_key:
|
158
|
-
specification_version:
|
166
|
+
specification_version: 3
|
159
167
|
summary: Constructor-based dependency injection container using YAML input.
|
160
168
|
test_files:
|
161
169
|
- test/diy_test.rb
|
170
|
+
- test/factory_test.rb
|