needle 0.6.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/doc/faq/faq.rb +143 -0
- data/doc/faq/faq.yml +75 -0
- data/doc/manual-html/chapter-1.html +26 -6
- data/doc/manual-html/chapter-2.html +39 -9
- data/doc/manual-html/chapter-3.html +177 -8
- data/doc/manual-html/chapter-4.html +108 -8
- data/doc/manual-html/chapter-5.html +28 -8
- data/doc/manual-html/chapter-6.html +28 -8
- data/doc/manual-html/chapter-7.html +29 -9
- data/doc/manual-html/chapter-8.html +176 -0
- data/doc/manual-html/index.html +26 -6
- data/doc/manual/manual.rb +1 -1
- data/doc/manual/manual.yml +7 -0
- data/doc/manual/parts/02_creating.txt +12 -2
- data/doc/manual/parts/03_conventional.txt +29 -0
- data/doc/manual/parts/03_locator.txt +60 -0
- data/doc/manual/parts/03_overview.txt +19 -0
- data/doc/manual/parts/04_overview.txt +9 -0
- data/doc/manual/parts/04_setup.txt +44 -0
- data/lib/needle/container.rb +68 -11
- data/lib/needle/registry.rb +25 -28
- data/lib/needle/service-point.rb +6 -1
- data/lib/needle/version.rb +1 -1
- data/test/models/model_test.rb +21 -1
- data/test/tc_container.rb +28 -1
- data/test/tc_registry.rb +22 -5
- data/test/tc_service_point.rb +9 -0
- metadata +11 -2
@@ -0,0 +1,176 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title>Needle Manual :: Chapter 8: Creating Libraries</title>
|
4
|
+
<link type="text/css" rel="stylesheet" href="manual.css" />
|
5
|
+
</head>
|
6
|
+
|
7
|
+
<body>
|
8
|
+
<div id="banner">
|
9
|
+
<table border='0' cellpadding='0' cellspacing='0' width='100%'>
|
10
|
+
<tr><td valign='top' align='left'>
|
11
|
+
<div class="title">
|
12
|
+
<span class="product">Needle—</span><br />
|
13
|
+
<span class="tagline">to the point --></span>
|
14
|
+
</div>
|
15
|
+
</td><td valign='middle' align='right'>
|
16
|
+
<div class="info">
|
17
|
+
Needle Version: <strong>0.9.0</strong><br />
|
18
|
+
Manual Last Updated: <strong>2004-10-28 15:34 GMT</strong>
|
19
|
+
</div>
|
20
|
+
</td></tr>
|
21
|
+
</table>
|
22
|
+
</div>
|
23
|
+
|
24
|
+
<table border='0' width='100%' cellpadding='0' cellspacing='0'>
|
25
|
+
<tr><td valign='top'>
|
26
|
+
|
27
|
+
<div id="navigation">
|
28
|
+
<h1>Needle Manual</h1>
|
29
|
+
|
30
|
+
<h2>Chapters</h2>
|
31
|
+
<ol type="I">
|
32
|
+
|
33
|
+
<li>
|
34
|
+
<a href="chapter-1.html">
|
35
|
+
Introduction
|
36
|
+
</a>
|
37
|
+
|
38
|
+
<ol type="1">
|
39
|
+
|
40
|
+
<li><a href="chapter-1.html#s1">What is Needle?</a></li>
|
41
|
+
|
42
|
+
<li><a href="chapter-1.html#s2">How Can It Help Me?</a></li>
|
43
|
+
|
44
|
+
<li><a href="chapter-1.html#s3">Alternatives</a></li>
|
45
|
+
|
46
|
+
<li><a href="chapter-1.html#s4">License Information</a></li>
|
47
|
+
|
48
|
+
<li><a href="chapter-1.html#s5">Support</a></li>
|
49
|
+
|
50
|
+
</ol>
|
51
|
+
</li>
|
52
|
+
|
53
|
+
<li>
|
54
|
+
<a href="chapter-2.html">
|
55
|
+
Registry
|
56
|
+
</a>
|
57
|
+
|
58
|
+
<ol type="1">
|
59
|
+
|
60
|
+
<li><a href="chapter-2.html#s1">Overview</a></li>
|
61
|
+
|
62
|
+
<li><a href="chapter-2.html#s2">Creating</a></li>
|
63
|
+
|
64
|
+
<li><a href="chapter-2.html#s3">Services</a></li>
|
65
|
+
|
66
|
+
<li><a href="chapter-2.html#s4">Namespaces</a></li>
|
67
|
+
|
68
|
+
</ol>
|
69
|
+
</li>
|
70
|
+
|
71
|
+
<li>
|
72
|
+
<a href="chapter-3.html">
|
73
|
+
Service Locator
|
74
|
+
</a>
|
75
|
+
|
76
|
+
<ol type="1">
|
77
|
+
|
78
|
+
<li><a href="chapter-3.html#s1">Overview</a></li>
|
79
|
+
|
80
|
+
<li><a href="chapter-3.html#s2">Conventional Architecture</a></li>
|
81
|
+
|
82
|
+
<li><a href="chapter-3.html#s3">Locator Pattern</a></li>
|
83
|
+
|
84
|
+
</ol>
|
85
|
+
</li>
|
86
|
+
|
87
|
+
<li>
|
88
|
+
<a href="chapter-4.html">
|
89
|
+
Dependency Injection
|
90
|
+
</a>
|
91
|
+
|
92
|
+
<ol type="1">
|
93
|
+
|
94
|
+
<li><a href="chapter-4.html#s1">Overview</a></li>
|
95
|
+
|
96
|
+
<li><a href="chapter-4.html#s2">Setup</a></li>
|
97
|
+
|
98
|
+
</ol>
|
99
|
+
</li>
|
100
|
+
|
101
|
+
<li>
|
102
|
+
<a href="chapter-5.html">
|
103
|
+
Interceptors
|
104
|
+
</a>
|
105
|
+
|
106
|
+
<ol type="1">
|
107
|
+
|
108
|
+
</ol>
|
109
|
+
</li>
|
110
|
+
|
111
|
+
<li>
|
112
|
+
<a href="chapter-6.html">
|
113
|
+
Service Models
|
114
|
+
</a>
|
115
|
+
|
116
|
+
<ol type="1">
|
117
|
+
|
118
|
+
</ol>
|
119
|
+
</li>
|
120
|
+
|
121
|
+
<li>
|
122
|
+
<a href="chapter-7.html">
|
123
|
+
Logging
|
124
|
+
</a>
|
125
|
+
|
126
|
+
<ol type="1">
|
127
|
+
|
128
|
+
</ol>
|
129
|
+
</li>
|
130
|
+
|
131
|
+
<li><strong>
|
132
|
+
<a href="chapter-8.html">
|
133
|
+
Creating Libraries
|
134
|
+
</a>
|
135
|
+
</strong> <big>←</big>
|
136
|
+
<ol type="1">
|
137
|
+
|
138
|
+
</ol>
|
139
|
+
</li>
|
140
|
+
|
141
|
+
</ol>
|
142
|
+
|
143
|
+
<h2>API Reference</h2>
|
144
|
+
|
145
|
+
<ul>
|
146
|
+
<li><a href="http://needle.rubyforge.org/api/index.html">Needle API</a></li>
|
147
|
+
</ul>
|
148
|
+
|
149
|
+
<h2>Tutorials</h2>
|
150
|
+
<ol>
|
151
|
+
|
152
|
+
</ol>
|
153
|
+
|
154
|
+
<p align="center"><strong>More To Come...</strong></p>
|
155
|
+
|
156
|
+
<div class="license">
|
157
|
+
<a href="http://creativecommons.org/licenses/by-sa/2.0/"><img alt="Creative Commons License" border="0" src="http://creativecommons.org/images/public/somerights" /></a><br />
|
158
|
+
This manual is licensed under a <a href="http://creativecommons.org/licenses/by-sa/2.0/">Creative Commons License</a>.
|
159
|
+
</div>
|
160
|
+
</div>
|
161
|
+
|
162
|
+
</td><td valign='top' width="100%">
|
163
|
+
|
164
|
+
<div id="content">
|
165
|
+
|
166
|
+
<h1>8. Creating Libraries</h1>
|
167
|
+
|
168
|
+
|
169
|
+
|
170
|
+
|
171
|
+
</div>
|
172
|
+
|
173
|
+
</td></tr>
|
174
|
+
</table>
|
175
|
+
</body>
|
176
|
+
</html>
|
data/doc/manual-html/index.html
CHANGED
@@ -14,8 +14,8 @@
|
|
14
14
|
</div>
|
15
15
|
</td><td valign='middle' align='right'>
|
16
16
|
<div class="info">
|
17
|
-
Needle Version: <strong>0.
|
18
|
-
Manual Last Updated: <strong>2004-10-
|
17
|
+
Needle Version: <strong>0.9.0</strong><br />
|
18
|
+
Manual Last Updated: <strong>2004-10-28 15:34 GMT</strong>
|
19
19
|
</div>
|
20
20
|
</td></tr>
|
21
21
|
</table>
|
@@ -70,27 +70,37 @@
|
|
70
70
|
|
71
71
|
<li>
|
72
72
|
<a href="chapter-3.html">
|
73
|
-
|
73
|
+
Service Locator
|
74
74
|
</a>
|
75
75
|
|
76
76
|
<ol type="1">
|
77
77
|
|
78
|
+
<li><a href="chapter-3.html#s1">Overview</a></li>
|
79
|
+
|
80
|
+
<li><a href="chapter-3.html#s2">Conventional Architecture</a></li>
|
81
|
+
|
82
|
+
<li><a href="chapter-3.html#s3">Locator Pattern</a></li>
|
83
|
+
|
78
84
|
</ol>
|
79
85
|
</li>
|
80
86
|
|
81
87
|
<li>
|
82
88
|
<a href="chapter-4.html">
|
83
|
-
|
89
|
+
Dependency Injection
|
84
90
|
</a>
|
85
91
|
|
86
92
|
<ol type="1">
|
87
93
|
|
94
|
+
<li><a href="chapter-4.html#s1">Overview</a></li>
|
95
|
+
|
96
|
+
<li><a href="chapter-4.html#s2">Setup</a></li>
|
97
|
+
|
88
98
|
</ol>
|
89
99
|
</li>
|
90
100
|
|
91
101
|
<li>
|
92
102
|
<a href="chapter-5.html">
|
93
|
-
|
103
|
+
Interceptors
|
94
104
|
</a>
|
95
105
|
|
96
106
|
<ol type="1">
|
@@ -100,7 +110,7 @@
|
|
100
110
|
|
101
111
|
<li>
|
102
112
|
<a href="chapter-6.html">
|
103
|
-
|
113
|
+
Service Models
|
104
114
|
</a>
|
105
115
|
|
106
116
|
<ol type="1">
|
@@ -110,6 +120,16 @@
|
|
110
120
|
|
111
121
|
<li>
|
112
122
|
<a href="chapter-7.html">
|
123
|
+
Logging
|
124
|
+
</a>
|
125
|
+
|
126
|
+
<ol type="1">
|
127
|
+
|
128
|
+
</ol>
|
129
|
+
</li>
|
130
|
+
|
131
|
+
<li>
|
132
|
+
<a href="chapter-8.html">
|
113
133
|
Creating Libraries
|
114
134
|
</a>
|
115
135
|
|
data/doc/manual/manual.rb
CHANGED
@@ -104,7 +104,7 @@ module Needle
|
|
104
104
|
end
|
105
105
|
end
|
106
106
|
|
107
|
-
YAML.add_private_type( 'file' ) { |type_id, value| File.read( value ) }
|
107
|
+
YAML.add_private_type( 'file' ) { |type_id, value| File.read( value ) rescue "" }
|
108
108
|
YAML.add_private_type( 'eval' ) { |type_id, value| eval( value ) }
|
109
109
|
|
110
110
|
YAML.add_domain_type( 'jamisbuck.org,2004', 'manual' ) do |taguri, val|
|
data/doc/manual/manual.yml
CHANGED
@@ -41,7 +41,14 @@ chapters:
|
|
41
41
|
- Services: !!file parts/02_services.txt
|
42
42
|
- Namespaces: !!file parts/02_namespaces.txt
|
43
43
|
|
44
|
+
- Service Locator:
|
45
|
+
- Overview: !!file parts/03_overview.txt
|
46
|
+
- Conventional Architecture: !!file parts/03_conventional.txt
|
47
|
+
- Locator Pattern: !!file parts/03_locator.txt
|
48
|
+
|
44
49
|
- Dependency Injection:
|
50
|
+
- Overview: !!file parts/04_overview.txt
|
51
|
+
- Setup: !!file parts/04_setup.txt
|
45
52
|
|
46
53
|
- Interceptors:
|
47
54
|
|
@@ -18,12 +18,22 @@ Alternatively, you can pass a block to @#new@:
|
|
18
18
|
|
19
19
|
The parameter to the block will be a reference to the registry. This allows you to register services with the registry as soon as it is created.
|
20
20
|
|
21
|
-
|
21
|
+
Another convenience method is @#define!@:
|
22
22
|
|
23
23
|
<pre>
|
24
|
-
registry = Needle::Registry.
|
24
|
+
registry = Needle::Registry.define! do
|
25
25
|
...
|
26
26
|
end
|
27
27
|
</pre>
|
28
28
|
|
29
29
|
This block accepts no parameters, and evaluates the block as if it were passed to @Registry#define!@ (see below).
|
30
|
+
|
31
|
+
There can be problems with using @define!@, however, since it uses @instance_eval@ to evaluate the block within the context of another object. If you find yourself running into scoping issues, you might want to consider using @#define@:
|
32
|
+
|
33
|
+
<pre>
|
34
|
+
registry = Needle::Registry.define do |b|
|
35
|
+
...
|
36
|
+
end
|
37
|
+
</pre>
|
38
|
+
|
39
|
+
This block accepts a single parameter--a "builder" object to aid in registering services--and evaluates the block as if it were passed to @Registry#define@ (see below).
|
@@ -0,0 +1,29 @@
|
|
1
|
+
A conventional architecture will have each component instantiate its own dependencies. For example, the @Application@ would do something like this:
|
2
|
+
|
3
|
+
<pre>
|
4
|
+
class Application
|
5
|
+
def initialize
|
6
|
+
@logger = Logger.new
|
7
|
+
@authenticator = Authenticator.new
|
8
|
+
@database = Database.new
|
9
|
+
@view = View.new
|
10
|
+
@session = Session.new
|
11
|
+
end
|
12
|
+
end
|
13
|
+
</pre>
|
14
|
+
|
15
|
+
However, the above is already flawed, because the @Authenticator@ and the @Session@ both need access to the @Database@, so you really need to make sure you instantiate things in the right order and pass them as parameters to the constructor of each object that needs them, like so:
|
16
|
+
|
17
|
+
<pre>
|
18
|
+
class Application
|
19
|
+
def initialize
|
20
|
+
@view = View.new
|
21
|
+
@logger = Logger.new
|
22
|
+
@database = Database.new( @logger )
|
23
|
+
@authenticator = Authenticator.new( @logger, @database )
|
24
|
+
@session = Session.new( @logger, @database )
|
25
|
+
end
|
26
|
+
end
|
27
|
+
</pre>
|
28
|
+
|
29
|
+
The problem with this is that if you later decide that @View@ needs to access the database, you need to rearrange the order of how things are instantiated in the @Application@ constructor.
|
@@ -0,0 +1,60 @@
|
|
1
|
+
The _service locator_ pattern makes things a _little_ easier. Instead of instantiating everything in the constructor of the @Application@, you can create a factory method somewhere that returns the new @Application@ instance. Then, inside of this factory method, you assign each new object to collection, and pass that collection to each constructor.
|
2
|
+
|
3
|
+
<pre>
|
4
|
+
require 'needle'
|
5
|
+
|
6
|
+
def create_application
|
7
|
+
locator = Needle::Registry.new
|
8
|
+
|
9
|
+
locator.register( :view ) { View.new(locator) }
|
10
|
+
locator.register( :logger ) { Logger.new(locator) }
|
11
|
+
locator.register( :database ) { Database.new(locator) }
|
12
|
+
locator.register( :authenticator ) {Authenticator.new(locator) }
|
13
|
+
locator.register( :session ) { Session.new(locator) }
|
14
|
+
locator.register( :app ) { Application.new(locator) }
|
15
|
+
|
16
|
+
locator[:app]
|
17
|
+
end
|
18
|
+
|
19
|
+
class Application
|
20
|
+
def initialize( locator )
|
21
|
+
@view = locator[:view]
|
22
|
+
@logger = locator[:logger]
|
23
|
+
@database = locator[:database]
|
24
|
+
@authenticator = locator[:authenticator]
|
25
|
+
@session = locator[:session]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Session
|
30
|
+
def initialize( locator )
|
31
|
+
@database = locator[:database]
|
32
|
+
@logger = locator[:logger]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
...
|
37
|
+
</pre>
|
38
|
+
|
39
|
+
This has the benefit of allowing each object to construct itself _� la carte_ from the objects in the locator. Also, each object no longer cares what class implements each service--it only cares that each object implements the methods it will attempt to invoke on that object.
|
40
|
+
|
41
|
+
Also, because Needle defers the instantiation of each service until the service is actually requested, we can actually register each item with the locator in any arbitrary order. All that is happening is the block is associated with the symbol, so that when the service is requested, the corresponding block is invoked. What is more, by default each service is then cached, so that it is only instantiated once.
|
42
|
+
|
43
|
+
Thus, when we get the @:app@ service (on the last line), the @Application@ constructor is invoked, passing the locator to the constructor. Inside the constructor, @Application@ retrieves each of its dependencies from the locator, causing each of them to be instantiated in turn. By this means, everything is initialized and constructed when the @create_application@ method returns.
|
44
|
+
|
45
|
+
In the interest of brevity, the @create_application@ could have been written like this, using a "builder" object (called @b@ in the example below) to help register the services:
|
46
|
+
|
47
|
+
<pre>
|
48
|
+
def create_application
|
49
|
+
locator = Needle::Registry.define do |b|
|
50
|
+
b.view { View.new(locator) }
|
51
|
+
b.logger { Logger.new(locator) }
|
52
|
+
b.database { Database.new(locator) }
|
53
|
+
b.authenticator {Authenticator.new(locator) }
|
54
|
+
b.session { Session.new(locator) }
|
55
|
+
b.app { Application.new(locator) }
|
56
|
+
end
|
57
|
+
|
58
|
+
locator[:app]
|
59
|
+
end
|
60
|
+
</pre>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
The _service locator_ design pattern can be considered a subset of dependency injection. Because it is simpler, it is as good of a place to start teaching DI as any.
|
2
|
+
|
3
|
+
To demonstrate both techniques, we'll pretend we're going to write an online forum application. To start, let's come up with a rough design by cataloging the components we'll need.
|
4
|
+
|
5
|
+
* @Logger@. This will be used to write messages to a file.
|
6
|
+
* @Authenticator@. This will be used to validate whether a user is who they say they are.
|
7
|
+
* @Database@. This encapsulates access to the database that will store our forum data.
|
8
|
+
* @Session@. This represents a single user's session.
|
9
|
+
* @View@. The presentation manager, used to render pages to the user.
|
10
|
+
* @Application@. The controller that ties it all together.
|
11
|
+
|
12
|
+
(Of course, a _real_ online forum application would be significantly more complex, but the above components will do for our puroses.)
|
13
|
+
|
14
|
+
The dependencies between these components are:
|
15
|
+
|
16
|
+
* @Authenticator@ _has_ @Database@ (for querying user authentication information) and @Logger@
|
17
|
+
* @Database@ _has_ @Logger@ (for indicating database accesses and query times)
|
18
|
+
* @Session@ _has_ @Database@ (for storing session information) and @Logger@
|
19
|
+
* @Application@ _has_ @Database@, @View@, @Session@, and @Authenticator@, and @Logger@
|
@@ -0,0 +1,9 @@
|
|
1
|
+
The service locator works well when there are few dependencies, and the dependency graph is not very deep. However, it has a few drawbacks:
|
2
|
+
|
3
|
+
# It requires each object to accept the locator as a parameter, and to know how to use it. This is problematic if you want to use existing classes that were created without knowledge of a locator. (I.e., @Logger@, in the Ruby library).
|
4
|
+
|
5
|
+
# It requires each object to know what the services are named, in the locator. If you ever decide to change the name of a service in the locator, you may have to change lots of code to comply with the change.
|
6
|
+
|
7
|
+
# For deep dependency graphs, it can become cumbersome to have to pass the locator to each constructor.
|
8
|
+
|
9
|
+
This is where _dependency injection_ comes in. It allows you to define how each service is initialized, including setting dependencies (either via constructor parameters or via property accessors). In fact, it can do a lot more than that, even allowing you to specify how the lifecycle of the service should be managed and hooking "interceptors" onto the service to filter method invocations.
|
@@ -0,0 +1,44 @@
|
|
1
|
+
Setting up for DI is very similar to the setup for a service locator, but instead of passing the locator (we'll call it a _registry_ now), we only pass (or set) the dependencies that the service itself needs.
|
2
|
+
|
3
|
+
<pre>
|
4
|
+
require 'needle'
|
5
|
+
|
6
|
+
def create_application
|
7
|
+
registry = Needle::Registry.define do |b|
|
8
|
+
b.view { View.new }
|
9
|
+
b.logger { Logger.new }
|
10
|
+
b.database { Database.new( b.logger ) }
|
11
|
+
b.authenticator { Authenticator.new(b.logger, b.database) }
|
12
|
+
b.session { Session.new(b.logger, b.database) }
|
13
|
+
|
14
|
+
b.app do
|
15
|
+
app = Application.new
|
16
|
+
app.logger = b.logger
|
17
|
+
app.view = b.view
|
18
|
+
app.database = b.database
|
19
|
+
app.authenticator = b.authenticator
|
20
|
+
app.session = b.session
|
21
|
+
app
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
registry[:app]
|
26
|
+
end
|
27
|
+
|
28
|
+
class Application
|
29
|
+
attr_writer :view, :logger, :database, :authenticator, :session
|
30
|
+
end
|
31
|
+
|
32
|
+
class Session
|
33
|
+
def initialize( logger, database )
|
34
|
+
@database = database
|
35
|
+
@logger = logger
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
...
|
40
|
+
</pre>
|
41
|
+
|
42
|
+
The @create_application@ method is now (necessarily) a little more complex, since it now contains all of the initialization logic for each service in the application. However, look how much simpler this made the other classes, especially the @Application@ class.
|
43
|
+
|
44
|
+
Now, each class no longer even needs to care that it is being initialized via another container. All it knows is that when it is created, it will be given each of its dependencies (either as constructor parameters or as property accessors).
|