needle 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/doc/LICENSE-BSD +27 -0
- data/doc/LICENSE-GPL +280 -0
- data/doc/LICENSE-RUBY +56 -0
- data/doc/README +70 -0
- data/doc/manual/chapter.erb +18 -0
- data/doc/manual/index.erb +29 -0
- data/doc/manual/manual.css +192 -0
- data/doc/manual/manual.rb +240 -0
- data/doc/manual/manual.yml +48 -0
- data/doc/manual/page.erb +86 -0
- data/doc/manual/parts/01_license.txt +5 -0
- data/doc/manual/parts/01_support.txt +1 -0
- data/doc/manual/parts/01_use_cases.txt +141 -0
- data/doc/manual/parts/01_what_is_needle.txt +1 -0
- data/doc/manual/parts/02_creating.txt +9 -0
- data/doc/manual/parts/02_namespaces.txt +47 -0
- data/doc/manual/parts/02_overview.txt +3 -0
- data/doc/manual/parts/02_services.txt +44 -0
- data/doc/manual/tutorial.erb +30 -0
- data/doc/manual-html/chapter-1.html +354 -0
- data/doc/manual-html/chapter-2.html +310 -0
- data/doc/manual-html/chapter-3.html +154 -0
- data/doc/manual-html/chapter-4.html +154 -0
- data/doc/manual-html/chapter-5.html +154 -0
- data/doc/manual-html/chapter-6.html +154 -0
- data/doc/manual-html/chapter-7.html +154 -0
- data/doc/manual-html/index.html +177 -0
- data/doc/manual-html/manual.css +192 -0
- data/lib/needle/container.rb +318 -0
- data/lib/needle/errors.rb +32 -0
- data/lib/needle/include-exclude.rb +116 -0
- data/lib/needle/interceptor-chain.rb +162 -0
- data/lib/needle/interceptor.rb +189 -0
- data/lib/needle/log-factory.rb +207 -0
- data/lib/needle/logger.rb +161 -0
- data/lib/needle/logging-interceptor.rb +62 -0
- data/lib/needle/models/prototype-deferred.rb +41 -0
- data/lib/needle/models/prototype.rb +39 -0
- data/lib/needle/models/proxy.rb +84 -0
- data/lib/needle/models/singleton-deferred.rb +57 -0
- data/lib/needle/models/singleton.rb +56 -0
- data/lib/needle/models.rb +44 -0
- data/lib/needle/registry.rb +110 -0
- data/lib/needle/service-point.rb +109 -0
- data/lib/needle/version.rb +28 -0
- data/lib/needle.rb +54 -0
- data/test/ALL-TESTS.rb +21 -0
- data/test/models/tc_prototype.rb +53 -0
- data/test/models/tc_prototype_deferred.rb +54 -0
- data/test/models/tc_proxy.rb +51 -0
- data/test/models/tc_singleton.rb +53 -0
- data/test/models/tc_singleton_deferred.rb +54 -0
- data/test/tc_container.rb +246 -0
- data/test/tc_interceptor.rb +92 -0
- data/test/tc_interceptor_chain.rb +181 -0
- data/test/tc_logger.rb +181 -0
- data/test/tc_models.rb +44 -0
- data/test/tc_registry.rb +34 -0
- data/test/tc_service_point.rb +100 -0
- metadata +107 -0
@@ -0,0 +1,240 @@
|
|
1
|
+
# --------------------------------------------------------------------------
|
2
|
+
# Portions of this code were taken from the "poignant.rb" script created
|
3
|
+
# by whytheluckystiff, which generates "Why's (Poignant) Guide to Ruby".
|
4
|
+
# The original script may be obtained from the poignant CVS repository,
|
5
|
+
# at http://poignant.rubyforge.org.
|
6
|
+
#
|
7
|
+
# This script is distributed under the by-sa/1.0 Creative Commons license.
|
8
|
+
# --------------------------------------------------------------------------
|
9
|
+
|
10
|
+
require 'erb'
|
11
|
+
require 'fileutils'
|
12
|
+
require 'yaml'
|
13
|
+
require 'redcloth'
|
14
|
+
|
15
|
+
module Needle
|
16
|
+
module Manual
|
17
|
+
|
18
|
+
class Manual
|
19
|
+
attr_accessor :product, :meta, :chapters, :tutorials, :examples, :recent_updates
|
20
|
+
|
21
|
+
def Manual.load( file_name )
|
22
|
+
File.open( file_name ) { |file| YAML.load( file ) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Meta
|
27
|
+
attr_accessor :copyright, :author, :email
|
28
|
+
end
|
29
|
+
|
30
|
+
class Product
|
31
|
+
attr_accessor :name, :tagline, :version, :logo, :urls, :project
|
32
|
+
end
|
33
|
+
|
34
|
+
class Sidebar
|
35
|
+
attr_accessor :title, :content
|
36
|
+
end
|
37
|
+
|
38
|
+
class Chapter
|
39
|
+
attr_accessor :index, :title, :sections
|
40
|
+
|
41
|
+
def initialize( index, title, sections )
|
42
|
+
@index = index
|
43
|
+
@title = title
|
44
|
+
|
45
|
+
section_index = 0
|
46
|
+
@sections = ( sections || [] ).collect do |section|
|
47
|
+
section_index += 1
|
48
|
+
if section.respond_to? :keys
|
49
|
+
Section.new( section_index, section.keys.first, section.values.first )
|
50
|
+
else
|
51
|
+
section_index -= 1
|
52
|
+
Section.new( section_index, nil, section )
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def page_title
|
58
|
+
"Chapter #{index}: #{title}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class Tutorial
|
63
|
+
attr_accessor :index, :title, :brief, :intro, :steps, :summary
|
64
|
+
|
65
|
+
def initialize( index, title, brief, intro, steps, summary )
|
66
|
+
@index = index
|
67
|
+
@title = title
|
68
|
+
@brief = RedCloth.new( brief )
|
69
|
+
@intro = RedCloth.new( intro ) if intro
|
70
|
+
@summary = RedCloth.new( summary ) if summary
|
71
|
+
@steps = steps.map { |step| RedCloth.new( step ) }
|
72
|
+
end
|
73
|
+
|
74
|
+
def page_title
|
75
|
+
"Tutorial #{index}: #{title}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class Example
|
80
|
+
attr_accessor :index, :title, :brief, :intro, :design, :implementation, :summary
|
81
|
+
|
82
|
+
def initialize( index, title, brief, intro, design, implementation, summary )
|
83
|
+
@index = index
|
84
|
+
@title = title
|
85
|
+
@brief = RedCloth.new( brief )
|
86
|
+
@intro = RedCloth.new( intro )
|
87
|
+
@design = RedCloth.new( design )
|
88
|
+
@implementation = RedCloth.new( implementation )
|
89
|
+
@summary = RedCloth.new( summary )
|
90
|
+
end
|
91
|
+
|
92
|
+
def page_title
|
93
|
+
"Example #{index}: #{title}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class Section
|
98
|
+
attr_accessor :index, :title, :content
|
99
|
+
|
100
|
+
def initialize( index, title, content )
|
101
|
+
@index = index
|
102
|
+
@title = RedCloth.new( title ).to_html.gsub( %r{</?p>}, "" ) if title
|
103
|
+
@content = RedCloth.new( content )
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
YAML.add_private_type( 'file' ) { |type_id, value| File.read( value ) }
|
108
|
+
YAML.add_private_type( 'eval' ) { |type_id, value| eval( value ) }
|
109
|
+
|
110
|
+
YAML.add_domain_type( 'jamisbuck.org,2004', 'manual' ) do |taguri, val|
|
111
|
+
index = 0
|
112
|
+
|
113
|
+
val['chapters'].collect! do |chapter|
|
114
|
+
index += 1
|
115
|
+
Chapter.new( index, chapter.keys.first, chapter.values.first )
|
116
|
+
end
|
117
|
+
|
118
|
+
index = 0
|
119
|
+
( val['tutorials'] ||= [] ).collect! do |tutorial|
|
120
|
+
index += 1
|
121
|
+
content = tutorial.values.first
|
122
|
+
Tutorial.new( index, tutorial.keys.first, content['brief'], content['intro'],
|
123
|
+
content['steps'], content['summary'] )
|
124
|
+
end
|
125
|
+
|
126
|
+
index = 0
|
127
|
+
( val['examples'] ||= [] ).collect! do |example|
|
128
|
+
index += 1
|
129
|
+
content = example.values.first
|
130
|
+
Example.new( index, example.keys.first, content['brief'], content['intro'], content['design'],
|
131
|
+
content['implementation'], content['summary'] )
|
132
|
+
end
|
133
|
+
|
134
|
+
YAML.object_maker( Manual, val )
|
135
|
+
end
|
136
|
+
|
137
|
+
YAML.add_domain_type( 'jamisbuck.org,2004', 'meta' ) do |taguri, val|
|
138
|
+
YAML.object_maker( Meta, val )
|
139
|
+
end
|
140
|
+
|
141
|
+
YAML.add_domain_type( 'jamisbuck.org,2004', 'product' ) do |taguri, val|
|
142
|
+
version = val["version"]
|
143
|
+
if version.respond_to?( :type_id )
|
144
|
+
if version.type_id == "version"
|
145
|
+
if version.value =~ %r{(.*)/(.*)}
|
146
|
+
require_file, constant = $1, $2
|
147
|
+
else
|
148
|
+
constant = version.value
|
149
|
+
end
|
150
|
+
|
151
|
+
require require_file if require_file
|
152
|
+
val["version"] = eval(constant)
|
153
|
+
else
|
154
|
+
raise "Unexpected type: #{val.type_id}"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
YAML.object_maker( Product, val )
|
158
|
+
end
|
159
|
+
|
160
|
+
YAML.add_domain_type( 'jamisbuck.org,2004', 'sidebar' ) do |taguri, val|
|
161
|
+
YAML.object_maker( Sidebar,
|
162
|
+
'title' => val.keys.first,
|
163
|
+
'content'=> RedCloth.new( val.values.first ) )
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
if __FILE__ == $0
|
170
|
+
|
171
|
+
def log_action( action )
|
172
|
+
$stderr.puts action
|
173
|
+
end
|
174
|
+
|
175
|
+
unless ( output_path = ARGV[0] )
|
176
|
+
$stderr.puts "Usage: #{$0} [/path/to/save/html]"
|
177
|
+
exit
|
178
|
+
end
|
179
|
+
|
180
|
+
Dir.mkdir output_path rescue nil
|
181
|
+
|
182
|
+
log_action "Loading manual.yml..."
|
183
|
+
manual = Needle::Manual::Manual.load( 'manual.yml' )
|
184
|
+
|
185
|
+
# force these to be defined at the TOPLEVEL_BINDING
|
186
|
+
object = nil
|
187
|
+
guts = nil
|
188
|
+
|
189
|
+
page = File.open( "page.erb" ) { |file| ERB.new( file.read ) }
|
190
|
+
page.filename = "page.erb"
|
191
|
+
|
192
|
+
template = File.open( "index.erb" ) { |file| ERB.new( file.read ) }
|
193
|
+
template.filename = "index.erb"
|
194
|
+
|
195
|
+
File.open( File.join( output_path, "index.html" ), "w" ) do |file|
|
196
|
+
guts = template.result
|
197
|
+
file << page.result
|
198
|
+
end
|
199
|
+
|
200
|
+
template = File.open( "chapter.erb" ) { |file| ERB.new( file.read ) }
|
201
|
+
template.filename = "chapter.erb"
|
202
|
+
|
203
|
+
manual.chapters.each do |object|
|
204
|
+
log_action "Processing chapter ##{object.index}..."
|
205
|
+
File.open( File.join( output_path, "chapter-#{object.index}.html" ), "w" ) do |file|
|
206
|
+
guts = template.result
|
207
|
+
file << page.result
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
template = File.open( "tutorial.erb" ) { |file| ERB.new( file.read ) }
|
212
|
+
template.filename = "tutorial.erb"
|
213
|
+
|
214
|
+
manual.tutorials.each do |object|
|
215
|
+
log_action "Processing tutorial ##{object.index}..."
|
216
|
+
File.open( File.join( output_path, "tutorial-#{object.index}.html" ), "w" ) do |file|
|
217
|
+
guts = template.result
|
218
|
+
file << page.result
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# template = File.open( "example.erb" ) { |file| ERB.new( file.read ) }
|
223
|
+
# template.filename = "example.erb"
|
224
|
+
|
225
|
+
# manual.examples.each do |object|
|
226
|
+
# log_action "Processing example ##{object.index}..."
|
227
|
+
# File.open( File.join( output_path, "example-#{object.index}.html" ), "w" ) do |file|
|
228
|
+
# guts = template.result
|
229
|
+
# file << page.result
|
230
|
+
# end
|
231
|
+
# end
|
232
|
+
|
233
|
+
log_action "Copying style sheets..."
|
234
|
+
FileUtils.cp Dir["*.css"], output_path
|
235
|
+
|
236
|
+
log_action "Copying images..."
|
237
|
+
FileUtils.cp Dir["img/*.jpg"], output_path
|
238
|
+
|
239
|
+
log_action "Done!"
|
240
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
--- !jamisbuck.org,2004/^manual
|
2
|
+
|
3
|
+
# This content is made available under the Attribution-ShareAlike 2.0
|
4
|
+
# license from the Create Commons:
|
5
|
+
#
|
6
|
+
# http://creativecommons.org/licenses/by-sa/2.0/
|
7
|
+
|
8
|
+
meta: !^meta
|
9
|
+
copyright: 2004
|
10
|
+
author: Jamis Buck
|
11
|
+
email: jgb3@email.byu.edu
|
12
|
+
|
13
|
+
product: !^product
|
14
|
+
name: Needle
|
15
|
+
tagline: to the point -->
|
16
|
+
version: !!eval require "../../lib/needle/version"; Needle::Version::STRING
|
17
|
+
#logo: needle.png
|
18
|
+
urls:
|
19
|
+
- Project Page: http://rubyforge.org/projects/needle
|
20
|
+
- User Manual: http://needle.rubyforge.org
|
21
|
+
- API Documentation: http://needle.rubyforge.org/api
|
22
|
+
- Needle Wiki: http://needle.rubyforge.org/wiki/wiki.pl
|
23
|
+
|
24
|
+
recent_updates:
|
25
|
+
|
26
|
+
chapters:
|
27
|
+
|
28
|
+
- Introduction:
|
29
|
+
- What is Needle?: !!file parts/01_what_is_needle.txt
|
30
|
+
- How Can It Help Me?: !!file parts/01_use_cases.txt
|
31
|
+
- License Information: !!file parts/01_license.txt
|
32
|
+
- Support: !!file parts/01_support.txt
|
33
|
+
|
34
|
+
- Registry:
|
35
|
+
- Overview: !!file parts/02_overview.txt
|
36
|
+
- Creating: !!file parts/02_creating.txt
|
37
|
+
- Services: !!file parts/02_services.txt
|
38
|
+
- Namespaces: !!file parts/02_namespaces.txt
|
39
|
+
|
40
|
+
- Dependency Injection:
|
41
|
+
|
42
|
+
- Interceptors:
|
43
|
+
|
44
|
+
- Service Models:
|
45
|
+
|
46
|
+
- Logging:
|
47
|
+
|
48
|
+
- Creating Libraries:
|
data/doc/manual/page.erb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title><%= manual.product.name %> Manual<% if object %> :: <%= object.page_title %><% end %></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"><%= manual.product.name %>—</span><br />
|
13
|
+
<span class="tagline"><%= manual.product.tagline %></span>
|
14
|
+
</div>
|
15
|
+
</td><td valign='middle' align='right'>
|
16
|
+
<div class="info">
|
17
|
+
<%= manual.product.name %> Version: <strong><%= manual.product.version %></strong><br />
|
18
|
+
Manual Last Updated: <strong><%= Time.now.gmtime.strftime('%Y-%m-%d %H:%M %Z') %></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><%= manual.product.name %> Manual</h1>
|
29
|
+
|
30
|
+
<h2>Chapters</h2>
|
31
|
+
<ol type="I">
|
32
|
+
<% manual.chapters.each do |c| %>
|
33
|
+
<li><%= "<strong>" if c == object %>
|
34
|
+
<a href="chapter-<%= c.index %>.html">
|
35
|
+
<%= c.title %>
|
36
|
+
</a>
|
37
|
+
<%= "</strong> <big>←</big>" if c == object %>
|
38
|
+
<ol type="1">
|
39
|
+
<% c.sections.each do |s|
|
40
|
+
next unless s.title %>
|
41
|
+
<li><a href="chapter-<%= c.index %>.html#s<%= s.index %>"><%= s.title %></a></li>
|
42
|
+
<% end %>
|
43
|
+
</ol>
|
44
|
+
</li>
|
45
|
+
<% end %>
|
46
|
+
</ol>
|
47
|
+
|
48
|
+
<h2>API Reference</h2>
|
49
|
+
|
50
|
+
<ul>
|
51
|
+
<li><a href="http://needle.rubyforge.org/api/index.html">Needle API</a></li>
|
52
|
+
</ul>
|
53
|
+
|
54
|
+
<h2>Tutorials</h2>
|
55
|
+
<ol>
|
56
|
+
<% manual.tutorials.each do |t| %>
|
57
|
+
<li><%= "<strong>" if t == object %>
|
58
|
+
<a href="tutorial-<%= t.index %>.html">
|
59
|
+
<%= t.title %>
|
60
|
+
</a>
|
61
|
+
<%= "</strong> <big>←</big>" if t == object %><br />
|
62
|
+
<%= t.brief.to_html %>
|
63
|
+
</li>
|
64
|
+
<% end %>
|
65
|
+
</ol>
|
66
|
+
|
67
|
+
<p align="center"><strong>More To Come...</strong></p>
|
68
|
+
|
69
|
+
<div class="license">
|
70
|
+
<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 />
|
71
|
+
This manual is licensed under a <a href="http://creativecommons.org/licenses/by-sa/2.0/">Creative Commons License</a>.
|
72
|
+
</div>
|
73
|
+
</div>
|
74
|
+
|
75
|
+
</td><td valign='top' width="100%">
|
76
|
+
|
77
|
+
<div id="content">
|
78
|
+
|
79
|
+
<%= guts %>
|
80
|
+
|
81
|
+
</div>
|
82
|
+
|
83
|
+
</td></tr>
|
84
|
+
</table>
|
85
|
+
</body>
|
86
|
+
</html>
|
@@ -0,0 +1,5 @@
|
|
1
|
+
Needle is made available under either the BSD license, or the same license Ruby (which, by extension, also allows the GPL as a permissable license as well). You can view the full text of any of these licenses in the @doc@ subdirectory of the Needle distrubtion. The texts of the BSD and GPL licenses are also available online: "BSD":http://www.opensource.org/licenses/bsd-license.php and "GPL":http://www.opensource.org/licenses/gpl-license.php.
|
2
|
+
|
3
|
+
This manual (in any form, be it source or otherwise) and the scripts and templates used to generate it, are all distributed under the "Creative Commons":http://creativecommons.org "Attribution-ShareAlike":http://creativecommons.org/licenses/by-sa/2.0 license.
|
4
|
+
|
5
|
+
If you desire permission to use either Needle or the manual in a manner incompatible with these licenses, please contact the copyright holder ("Jamis Buck":mailto:jgb3@email.byu.edu) in order to negotiate a more compatible license.
|
@@ -0,0 +1 @@
|
|
1
|
+
Mailing lists, bug trackers, feature requests, and public forums are available (courtesy of "RubyForge":http://rubyforge.org) at Needle's RubyForge project page. Just direct your browser to "http://rubyforge.org/projects/needle":http://rubyforge.org/projects/needle.
|
@@ -0,0 +1,141 @@
|
|
1
|
+
So, what can Needle do for you? Ultimately, it can reduce the amount of code that you have to write, simplifying many common programming tasks for you. This has the two-fold benefit of both decreasing application development time, and of decreasing the effort needed to maintain your application.
|
2
|
+
|
3
|
+
But what, _specifically_, can Needle do for you?
|
4
|
+
|
5
|
+
Try these on for size:
|
6
|
+
|
7
|
+
* "Log Method Execution":#logexec
|
8
|
+
* "Reference Another Service":#refsvc
|
9
|
+
* "Unit Testing":#unittest
|
10
|
+
* "Lifecycle Management":#lifecycle
|
11
|
+
|
12
|
+
(Thanks to Howard Lewis Ship for his "HiveMind":http://jakarta.apache.org/hivemind documentation, from which some of the above bullet points were adapted.)
|
13
|
+
|
14
|
+
|
15
|
+
h3. Log Method Execution <a name="#logexec"></a>
|
16
|
+
|
17
|
+
Needle has an integrated logging framework, and the ability to log execution trace information without modifying a single line of code in your classes. This means that you can easily see what methods get called, with what arguments, and what the return values are, all without having to physically modify any of your classes.
|
18
|
+
|
19
|
+
Consider the following code, demonstrating how this would be done without Needle:
|
20
|
+
|
21
|
+
<pre>
|
22
|
+
def foo( arg1, arg2 )
|
23
|
+
@log.debug( "in foo with #{arg1} and #{arg2}" ) if @log.debug?
|
24
|
+
...
|
25
|
+
result = the_result_of_the_method
|
26
|
+
@log.debug( "finishing foo with #{result}" ) if @log.debug
|
27
|
+
return result
|
28
|
+
rescue Exception => e
|
29
|
+
@log.debug( "foo raised exception #{e.message} (#{e.class})" ) if @log.debug?
|
30
|
+
raise
|
31
|
+
end
|
32
|
+
</pre>
|
33
|
+
|
34
|
+
Now, multiply that by the number of methods in your class... the logging messages quickly overpower the rest of the code, and detract from the flow of your program. This makes your program harder to debug, test, and maintain.
|
35
|
+
|
36
|
+
Now, consider the same method using Needle's integrated logging framework...
|
37
|
+
|
38
|
+
<pre>
|
39
|
+
def foo( arg1, arg2 )
|
40
|
+
...
|
41
|
+
return the_result_of_the_method
|
42
|
+
end
|
43
|
+
</pre>
|
44
|
+
|
45
|
+
Then, when you define the service that you want to add the logging to:
|
46
|
+
|
47
|
+
<pre>
|
48
|
+
registry.register( :service_name_here ) { |reg| ... }
|
49
|
+
registry.intercept( :service_name_here ).with! { logging_interceptor }
|
50
|
+
</pre>
|
51
|
+
|
52
|
+
That's right. There's no explicit logging code in there. Instead, you just tell Needle that the methods of the class should be logged, and away it goes. This has the added benefit of allowing your objects to be unit tested, without spewing log messages everywhere.
|
53
|
+
|
54
|
+
h3. Reference Another Service <a name="#refsvc"></a>
|
55
|
+
|
56
|
+
Invariably in a large application services will reference other services. This is typically accomplished through something like this:
|
57
|
+
|
58
|
+
<pre>
|
59
|
+
class Component
|
60
|
+
...
|
61
|
+
def foo( parms )
|
62
|
+
@service ||= lookup_service
|
63
|
+
@service.do_something( parms )
|
64
|
+
end
|
65
|
+
|
66
|
+
def lookup_service
|
67
|
+
...
|
68
|
+
end
|
69
|
+
...
|
70
|
+
end
|
71
|
+
</pre>
|
72
|
+
|
73
|
+
Whether the lookup is done lazily, as shown above, or when the class is first instantiated is irrelevant. The point is that you either have to implement a bunch of code to look up a service based on some criteria, or you hard code the class of the service (which creates tight coupling and makes things like unit testing harder).
|
74
|
+
|
75
|
+
With Needle, you just declare a setter for the service, and then tell Needle that the class depends on the other service:
|
76
|
+
|
77
|
+
<pre>
|
78
|
+
class Component
|
79
|
+
attr_writer :service
|
80
|
+
...
|
81
|
+
def foo( parms )
|
82
|
+
@service.do_something( parms )
|
83
|
+
end
|
84
|
+
...
|
85
|
+
end
|
86
|
+
|
87
|
+
registry.register( :component ) do |reg|
|
88
|
+
c = Component.new
|
89
|
+
c.service = reg.some_other_service
|
90
|
+
c
|
91
|
+
end
|
92
|
+
</pre>
|
93
|
+
|
94
|
+
Then, when your service is instantiated, Needle will automatically look for and instantiate the dependencies for you. This makes for cleaner code, and looser coupling between services.
|
95
|
+
|
96
|
+
h3. Unit Testing <a name="#unittest"></a>
|
97
|
+
|
98
|
+
Large applications can prove troublesome to unit test exhaustively, especially if there is any kind of tight coupling between components. Such coupling of components can make it difficult to test them separately.
|
99
|
+
|
100
|
+
Needle, by its very nature, encourages loose coupling of components. Also, because dependencies are never instantiated in code, but are instead accepted via setters or constructor arguments, it is trivial to replace those dependencies with mock objects at unit test time.
|
101
|
+
|
102
|
+
Consider this tightly coupled example:
|
103
|
+
|
104
|
+
<pre>
|
105
|
+
def foo( args )
|
106
|
+
@some_dependency ||= MyNewDependency.new
|
107
|
+
@some_dependency.do_something(args)
|
108
|
+
end
|
109
|
+
</pre>
|
110
|
+
|
111
|
+
It is impossible to test the method @#foo@ without also testing the MyNewDependency class. However, if the @@some_dependency@ object is made a property that is set externally, you can replace it at test time with a blank:
|
112
|
+
|
113
|
+
<pre>
|
114
|
+
attr_writer :some_dependency
|
115
|
+
|
116
|
+
def foo( args )
|
117
|
+
@some_dependency.do_something( args )
|
118
|
+
end
|
119
|
+
</pre>
|
120
|
+
|
121
|
+
The unit test would become something like this:
|
122
|
+
|
123
|
+
<pre>
|
124
|
+
def test_foo
|
125
|
+
@obj.some_dependecy = MyMockDependency.new
|
126
|
+
@obj.foo( args )
|
127
|
+
assert @obj.is_in_some_state
|
128
|
+
end
|
129
|
+
</pre>
|
130
|
+
|
131
|
+
h3. Lifecycle Management <a name="#lifecycle"></a>
|
132
|
+
|
133
|
+
Singleton objects are a fact of life in complex systems. The singleton design pattern is powerful and useful. However, using the Singleton mixin, or declaring methods at the class level, can make your code difficult to unit test since the state of such objects cannot be easily reset.
|
134
|
+
|
135
|
+
Needle has a solution. You can tell Needle to treat a service as either a _prototype_ service (meaning it will be instantiated every time you ask for it, like calling @#new@), or a _singleton_ service (meaning it will only be instantiated once, and the same instance will be returned for subsequent requests).
|
136
|
+
|
137
|
+
Your object is still just a plain ol' ordinary Ruby object, but Needle has effectively transformed it into a singleton. This means you can unit test it as if it were nothing special, but when it is used in your application it will act like a singleton.
|
138
|
+
|
139
|
+
Lifecycle management also means that you can control _when_ a service is instantiated. The _prototype_ and _singleton_ models will always be instantiated as soon as they are requested. Sometimes, though, you don't want that--you'd like the instantiation to be deferred as late as possible.
|
140
|
+
|
141
|
+
With Needle, you can indicate that a service should use deferred instantiation. This will cause the service to not actually be instantiated until a method is actually invoked on it. Using this model, you can have services depend on themselves, or other forms of cyclical dependencies.
|
@@ -0,0 +1 @@
|
|
1
|
+
Needle is a dependency injection (also, inversion of control) container for "Ruby":http://www.ruby-lang.org.
|
@@ -0,0 +1,9 @@
|
|
1
|
+
Creating a registry is as simple as calling @Needle::Registry.new@. This will give you a new registry object, bootstrapped to contain a few general services.
|
2
|
+
|
3
|
+
<pre>
|
4
|
+
require 'needle'
|
5
|
+
|
6
|
+
registry = Needle::Registry.new
|
7
|
+
</pre>
|
8
|
+
|
9
|
+
Once you have the reference to the registry, you can register services with it, create new namespaces in it, and so forth.
|
@@ -0,0 +1,47 @@
|
|
1
|
+
Namespaces allow you to organize your services. The feature has many different applications, including:
|
2
|
+
|
3
|
+
# Third-parties may distribute Needle-enabled libraries without worrying about their choice of service names conflicting with the service names of their clients.
|
4
|
+
# Developers may organize complex applications into modules, and the service definitions may be stored in the registry to reflect that organization.
|
5
|
+
# Services deeper in the hierarchy may override services higher up.
|
6
|
+
|
7
|
+
Creating a namespace is as easy as invoking the @#namespace@ method of the registry (or of another namespace):
|
8
|
+
|
9
|
+
<pre>
|
10
|
+
registry.namespace :stuff
|
11
|
+
</pre>
|
12
|
+
|
13
|
+
This would create a new namespace in the registry called @:stuff@. The application may then proceed to register services inside that namespace:
|
14
|
+
|
15
|
+
<pre>
|
16
|
+
registry.stuff.register( :foo ) { Bar.new }
|
17
|
+
...
|
18
|
+
svc = registry.stuff.foo
|
19
|
+
</pre>
|
20
|
+
|
21
|
+
Here's a tip: _namespaces are just a special kind of service._ This means that you can access namespaces in the same ways that you can access services:
|
22
|
+
|
23
|
+
<pre>
|
24
|
+
svc = registry[:stuff][:foo]
|
25
|
+
</pre>
|
26
|
+
|
27
|
+
h3. Convenience Methods
|
28
|
+
|
29
|
+
Because it is often the case that you will be creating a namespace and then immediately registering services on it, you can pass a block to @namespace@. The block will receive a reference to the new namespace:
|
30
|
+
|
31
|
+
<pre>
|
32
|
+
registry.namespace :stuff do |spc|
|
33
|
+
spc.register( :foo ) { Bar.new }
|
34
|
+
...
|
35
|
+
end
|
36
|
+
</pre>
|
37
|
+
|
38
|
+
And, to mirror the @register!@ method, there is also a @namespace!@ method. This method creates a new namespace and then does a @register!@ call on that namespace.
|
39
|
+
|
40
|
+
<pre>
|
41
|
+
registry.namespace! :stuff do
|
42
|
+
foo { Bar.new }
|
43
|
+
...
|
44
|
+
end
|
45
|
+
</pre>
|
46
|
+
|
47
|
+
The above code would create a new namespace called @:stuff@ in the registry, and would then proceed to register a service called @:foo@ in the new namespace.
|
@@ -0,0 +1,3 @@
|
|
1
|
+
The registry is at the heart of any dependency-injected application or library. All services are registered with the registry, so that when an application needs an instance of a particular service, it may obtain that service reference by querying the registry.
|
2
|
+
|
3
|
+
In order to use Needle, you only really _need_ to understand how to create and manipulate registry objects.
|
@@ -0,0 +1,44 @@
|
|
1
|
+
Registering services with a Needle registry is very straightforward. The simplest way to do it is:
|
2
|
+
|
3
|
+
<pre>
|
4
|
+
registry.register( :foo ) { Bar.new }
|
5
|
+
</pre>
|
6
|
+
|
7
|
+
The above will register a new service with the registry, naming it @:foo@. When @:foo@ is requested from the registry, a new instance of @Bar@ will be instantiated and returned.
|
8
|
+
|
9
|
+
You get services from the registry in either of two ways:
|
10
|
+
|
11
|
+
<pre>
|
12
|
+
# Treating the registry as a Hash
|
13
|
+
svc = registry[:foo]
|
14
|
+
|
15
|
+
# Treating the service as a property of the registry
|
16
|
+
svc = registry.foo
|
17
|
+
</pre>
|
18
|
+
|
19
|
+
h3. Convenience Methods
|
20
|
+
|
21
|
+
Because you will often need to register many services with a registry at once, a convenience method has been provided to make this use case lean and mean. Just call @registry!@, passing a block that accepts no parameters. This block will be evaluated in a new context, with any unrecognized method call being interpreted as a new service registration of that name:
|
22
|
+
|
23
|
+
<pre>
|
24
|
+
registry.register! do
|
25
|
+
foo { Bar.new }
|
26
|
+
bar { Foo.new }
|
27
|
+
...
|
28
|
+
end
|
29
|
+
</pre>
|
30
|
+
|
31
|
+
The above will register two new services with the registry, @:foo@ and @:bar@.
|
32
|
+
|
33
|
+
h3. Default Lifecycle
|
34
|
+
|
35
|
+
By default, a service is only instantiated once per registry. This means that (using the above example) if the service @:foo@ were queried twice, the registry would return the same object for both queries:
|
36
|
+
|
37
|
+
<pre>
|
38
|
+
svc1 = registry.foo
|
39
|
+
svc2 = registry.foo
|
40
|
+
|
41
|
+
p svc1.object_id == svc2.object_id #=> true
|
42
|
+
</pre>
|
43
|
+
|
44
|
+
You can change this behavior, with _service models_. See the chapter on Service Models for more information.
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<h1>Tutorial #<%= object.index %>. <%= object.title %></h1>
|
2
|
+
|
3
|
+
<p>The sources for this tutorial may be found in the <tt>tutorial/<%= "%02d" % object.index %></tt>
|
4
|
+
directory of the Copland distribution.</p>
|
5
|
+
|
6
|
+
<% if object.intro %>
|
7
|
+
|
8
|
+
<h2>Introduction</h2>
|
9
|
+
|
10
|
+
<%= object.intro.to_html %>
|
11
|
+
|
12
|
+
<% end %>
|
13
|
+
|
14
|
+
<h2>Steps</h2>
|
15
|
+
|
16
|
+
<ol>
|
17
|
+
<% object.steps.each do |step| %>
|
18
|
+
|
19
|
+
<li><%= step.to_html %></li>
|
20
|
+
|
21
|
+
<% end %>
|
22
|
+
</ol>
|
23
|
+
|
24
|
+
<% if object.summary %>
|
25
|
+
|
26
|
+
<h2>Summary</h2>
|
27
|
+
|
28
|
+
<%= object.summary.to_html %>
|
29
|
+
|
30
|
+
<% end %>
|