diy 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +10 -0
- data/README.txt +140 -80
- data/Rakefile +9 -0
- data/lib/diy.rb +134 -37
- data/test/diy_test.rb +102 -24
- data/test/files/cat/cat.rb +0 -1
- data/test/files/dog/dog_model.rb +0 -1
- data/test/files/dog/dog_presenter.rb +0 -1
- data/test/files/fud/toy.rb +0 -1
- data/test/files/gnu/thinger.rb +0 -1
- data/test/files/goat/plane.rb +0 -1
- data/test/files/horse/holder_thing.rb +0 -1
- data/test/files/non_singleton/fat_cat.rb +0 -1
- data/test/files/non_singleton/pig.rb +0 -1
- data/test/files/non_singleton/thread_spinner.rb +0 -1
- data/test/files/non_singleton/tick.rb +0 -1
- data/test/files/yak/core_model.rb +0 -1
- data/test/files/yak/core_presenter.rb +0 -1
- data/test/files/yak/fringe_model.rb +0 -1
- data/test/files/yak/fringe_presenter.rb +0 -1
- data/test/files/yak/giant_squid.rb +0 -1
- data/test/test_helper.rb +1 -0
- metadata +61 -54
data/History.txt
CHANGED
@@ -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
|
-
|
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
|
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
|
-
|
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
|
-
|
18
|
-
|
19
|
-
* Constructor-based dependency injection container using YAML input.
|
13
|
+
* gem install diy
|
20
14
|
|
21
15
|
== SYNOPSIS:
|
22
16
|
|
23
|
-
===
|
17
|
+
=== Common Usage
|
24
18
|
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
23
|
+
The following context defines an automobile engine:
|
30
24
|
|
31
|
-
|
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
|
-
|
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
|
-
|
36
|
-
class Foo; end
|
62
|
+
* http://atomicobjectrb.rubyforge.org/constructor
|
37
63
|
|
38
|
-
|
39
|
-
---
|
40
|
-
foo:
|
41
|
-
bar:
|
64
|
+
Using constructor, you can write Engine like this:
|
42
65
|
|
43
|
-
|
44
|
-
|
66
|
+
class Engine
|
67
|
+
constructor :throttle, :block
|
68
|
+
end
|
45
69
|
|
46
|
-
===
|
70
|
+
=== Special Cases
|
47
71
|
|
48
|
-
If
|
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
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
---
|
55
|
-
foo:
|
56
|
-
lib: fun_stuff
|
57
|
-
bar:
|
75
|
+
engine:
|
76
|
+
compose:
|
77
|
+
- throttle
|
78
|
+
- block
|
58
79
|
|
59
|
-
|
80
|
+
Sometimes you won't be able to rely on DIY's basic assumptions about class names and library files.
|
60
81
|
|
61
|
-
|
62
|
-
|
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
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
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
|
-
|
82
|
-
|
83
|
-
|
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
|
-
|
87
|
-
in the context, they can be mapped explicitly.
|
135
|
+
=== Direct Class References
|
88
136
|
|
89
|
-
|
90
|
-
|
91
|
-
|
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
|
-
|
97
|
-
|
98
|
-
|
99
|
-
my_bar:
|
100
|
-
the_other_one:
|
142
|
+
customer_order_finder:
|
143
|
+
class: CustomerOrder
|
144
|
+
use_class_directly: true
|
101
145
|
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
-
|
107
|
-
---
|
108
|
-
foo:
|
109
|
-
singleton: false
|
150
|
+
=== Namespace Convenience
|
110
151
|
|
111
|
-
|
152
|
+
If you find yourself writing context entries like this:
|
112
153
|
|
154
|
+
---
|
113
155
|
engine:
|
114
|
-
|
156
|
+
class: Car::Parts::Engine
|
157
|
+
throttle:
|
158
|
+
class: Car::Parts::Block
|
159
|
+
cable:
|
160
|
+
class: Car::Parts::Cable
|
115
161
|
|
116
|
-
|
162
|
+
You can set the "assumed" module for a group of objects like this:
|
117
163
|
|
118
|
-
|
119
|
-
|
164
|
+
---
|
165
|
+
using_namespace Car Parts:
|
166
|
+
engine:
|
120
167
|
|
121
|
-
|
168
|
+
throttle:
|
122
169
|
|
123
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
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
|
-
|
238
|
-
|
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
|
data/test/diy_test.rb
CHANGED
@@ -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
|
data/test/files/cat/cat.rb
CHANGED
data/test/files/dog/dog_model.rb
CHANGED
data/test/files/fud/toy.rb
CHANGED
data/test/files/gnu/thinger.rb
CHANGED
data/test/files/goat/plane.rb
CHANGED
data/test/test_helper.rb
CHANGED
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.
|
7
|
-
|
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
|
-
|
86
|
-
|
97
|
+
has_rdoc: true
|
98
|
+
homepage:
|
99
|
+
post_install_message:
|
87
100
|
rdoc_options:
|
88
101
|
- --main
|
89
102
|
- README.txt
|
90
|
-
|
91
|
-
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
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
|