needle 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/benchmarks/instantiation.rb +39 -0
  2. data/doc/faq/faq.yml +137 -102
  3. data/doc/manual-html/chapter-1.html +15 -19
  4. data/doc/manual-html/chapter-2.html +52 -23
  5. data/doc/manual-html/chapter-3.html +7 -9
  6. data/doc/manual-html/chapter-4.html +6 -8
  7. data/doc/manual-html/chapter-5.html +16 -19
  8. data/doc/manual-html/chapter-6.html +31 -8
  9. data/doc/manual-html/chapter-7.html +19 -8
  10. data/doc/manual-html/chapter-8.html +8 -11
  11. data/doc/manual-html/chapter-9.html +7 -6
  12. data/doc/manual-html/index.html +5 -3
  13. data/doc/manual/manual.yml +2 -1
  14. data/doc/manual/parts/01_alternatives.txt +2 -1
  15. data/doc/manual/parts/02_services.txt +33 -0
  16. data/doc/manual/parts/logging_logfactory.txt +9 -0
  17. data/doc/manual/parts/models_models.txt +4 -0
  18. data/doc/manual/parts/models_pipelines.txt +1 -0
  19. data/lib/needle/container.rb +64 -19
  20. data/lib/needle/definition-context.rb +34 -6
  21. data/lib/needle/lifecycle/multiton.rb +64 -0
  22. data/lib/needle/lifecycle/singleton.rb +2 -2
  23. data/lib/needle/lifecycle/threaded.rb +4 -3
  24. data/lib/needle/pipeline/collection.rb +10 -1
  25. data/lib/needle/pipeline/element.rb +6 -0
  26. data/lib/needle/registry.rb +18 -5
  27. data/lib/needle/service-point.rb +21 -10
  28. data/lib/needle/version.rb +1 -1
  29. data/test/lifecycle/tc_multiton.rb +43 -0
  30. data/test/lifecycle/tc_singleton.rb +18 -2
  31. data/test/lifecycle/tc_threaded.rb +12 -6
  32. data/test/pipeline/tc_collection.rb +26 -2
  33. data/test/services.rb +8 -0
  34. data/test/tc_container.rb +82 -0
  35. data/test/tc_definition_context.rb +63 -7
  36. data/test/tc_registry.rb +13 -1
  37. data/test/tc_service_point.rb +49 -0
  38. metadata +4 -2
@@ -14,8 +14,8 @@
14
14
  </div>
15
15
  </td><td valign='middle' align='right'>
16
16
  <div class="info">
17
- Needle Version: <strong>1.1.0</strong><br />
18
- Manual Last Updated: <strong>2004-11-11 17:31 GMT</strong>
17
+ Needle Version: <strong>1.2.0</strong><br />
18
+ Manual Last Updated: <strong>2004-11-18 15:36 GMT</strong>
19
19
  </div>
20
20
  </td></tr>
21
21
  </table>
@@ -226,6 +226,7 @@
226
226
 
227
227
  <p>Here&#8217;s a contrived example. Suppose you want each namespace to keep track of the precise time that it was created.</p>
228
228
 
229
+
229
230
  <pre>
230
231
  class TimeTrackerNamespace &lt; Needle::Container
231
232
  attr_reader :birth_date
@@ -242,9 +243,7 @@
242
243
  reg.namespace :hello
243
244
  p reg.hello.birth_date
244
245
  </pre>
245
-
246
- <p>In general, you&#8217;ll be better off having your custom implementation extend <code>Needle::Container</code>, although the only <em>real</em> requirement is that your implementation publish the same interface as the default namespace implementation.<br />
247
- </p>
246
+ <p>In general, you&#8217;ll be better off having your custom implementation extend <code>Needle::Container</code>, although the only <em>real</em> requirement is that your implementation publish the same interface as the default namespace implementation.</p>
248
247
  </div>
249
248
 
250
249
 
@@ -261,12 +260,13 @@
261
260
 
262
261
  <p>It is this wrapper object that allows interceptor definitions to be done using method chaining:</p>
263
262
 
263
+
264
264
  <pre>
265
265
  reg.intercept( :foo ).with { ... }.with_options(...)
266
266
  </pre>
267
-
268
267
  <p>If you wish to add custom, domain-specific functionality to the interceptor wrapper, you can register your own implementation of the <code>:interceptor_impl_factory</code>. Consider the following contrived example, where an &#8220;only_if&#8221; clause is given to determine when the interceptor should be invoked.</p>
269
268
 
269
+
270
270
  <pre>
271
271
  class OnlyIfInterceptor &lt; Needle::Interceptor
272
272
  def only_if( &#38;block )
@@ -313,6 +313,7 @@
313
313
 
314
314
  <p>Consider the following contrived example, where you want to provide a convenient way to register services of type Hash.</p>
315
315
 
316
+
316
317
  <pre>
317
318
  class MyDefinitionContext &lt; Needle::DefinitionContext
318
319
  def register_hash( name, opts={} )
@@ -14,8 +14,8 @@
14
14
  </div>
15
15
  </td><td valign='middle' align='right'>
16
16
  <div class="info">
17
- Needle Version: <strong>1.1.0</strong><br />
18
- Manual Last Updated: <strong>2004-11-11 17:31 GMT</strong>
17
+ Needle Version: <strong>1.2.0</strong><br />
18
+ Manual Last Updated: <strong>2004-11-18 15:36 GMT</strong>
19
19
  </div>
20
20
  </td></tr>
21
21
  </table>
@@ -234,7 +234,9 @@
234
234
  <table border='0' cellpadding='0' cellspacing='0' align='center'><tr><td>
235
235
  <ul>
236
236
 
237
- <li>Added chapter on "Customizing Needle"</li>
237
+ <li>Described parameterized services</li>
238
+
239
+ <li>Added multiton service models to list of available models</li>
238
240
 
239
241
  </ul>
240
242
  </table>
@@ -23,7 +23,8 @@ product: !^product
23
23
  - Needle Wiki: http://needle.rubyforge.org/wiki/wiki.pl
24
24
 
25
25
  recent_updates:
26
- - Added chapter on "Customizing Needle"
26
+ - Described parameterized services
27
+ - Added multiton service models to list of available models
27
28
 
28
29
  chapters:
29
30
 
@@ -3,8 +3,9 @@ Needle is not the only fish in the dependency-injection pond, even when it comes
3
3
  * "Copland":http://copland.rubyforge.org. Copland aims to be an "application framework", taking something of a heavy-weight approach to DI. In so doing, it provides functionality that Needle does not, but at the cost of performance. It also uses external (YAML) configuration files. It is inspired by a Java framework ("HiveMind":http://jakarta.apache.org/hivemind), and so has a vaguely Java-ish flavor to it.
4
4
  * "Rico":http://www.picocontainer.org/Rico. Rico is another project inspired by a Java project ("PicoContainer":http://www.picocontainer.org). It is very lean, and appears to be experimental.
5
5
  * "Tudura":http://sourceforge.jp/projects/nihohi/. I do not have any information on this project, as the information is all in Japanese. If someone with more information about Tudura would like to step forward, I'd be happy to post a summary here.
6
+ * "MinDI":http://redshift.sourceforge.net/mindi/ is a recent contender that takes a very novel approach to dependency injection. Instead of instantiating a container and adding services to it, you declare a class, mixin a DI module, and add services to the class as methods, thereby making the class the container.
6
7
 
7
- There is, at the time of this writing, at least one other project on RubyForge devoted to DI, although it has no public releases yet.
8
+ There is, at the time of this writing, at least one other project on RubyForge devoted to DI ("Pith":http://rubyforge.org/projects/pith), although it has no public releases yet.
8
9
 
9
10
  So, which one should you choose? It comes down to an issue of personal preference, mostly, but also one of what you are wanting to accomplish. Needle excels at providing an unobtrusive, light-weight container for managing your dependencies. The cost of it being light-weight is that there is functionality it does not provide, which other containers may. If you really need that missing functionality, you are required to either implement it yourself, or select a different container.
10
11
 
@@ -54,3 +54,36 @@ By default, a service is only instantiated once per registry. This means that (u
54
54
  </pre>
55
55
 
56
56
  You can change this behavior, with _service models_. See the chapter on Service Models for more information.
57
+
58
+ h3. Parameterized Services
59
+
60
+ Needle also supports _parameterized services_. These are services that, when requested, require contextual information to be passed as a parameter so that the service can be correctly initialized.
61
+
62
+ Consider the following example, in which some hypothetical @Printer@ class represents any of a number of printers, based on a parameter given to its constructor.
63
+
64
+ <pre>
65
+ registry.register( :printer, :model => :multiton ) do |c,p,name|
66
+ Printer.new( c.log_for( p ), name )
67
+ end
68
+
69
+ mono = registry.printer( :monochrome )
70
+ color = registry.printer( :color )
71
+ </pre>
72
+
73
+ There are a few things to note about the above example:
74
+
75
+ # The @:multiton@ model is explicitly requested. This is necessary because the default service model (@:singleton@) does not allow parameterized services. Most of the time, you'll use the multiton service model with parameterized services, but you don't have to. You could also use any of the prototype models as well.
76
+
77
+ # The constructor block for the @:printer@ service takes three parameters, @c@, @p@, and @name@. The first two parameters, @c@ and @p@, represent the container and the service point, respectively. Any parameters after those two are the contextual parameters given when the service is requested. In this case, there is only one contextual parameter: the name of the printer.
78
+
79
+ # Notice the first parameter to the Printer constructor: @c.log_for(p)@. This is itself invoking a parameterized service, named @:log_for@, and passing @p@ as the contextual information. This will return a new logger handle for the service point @p@ (i.e., the current service point).
80
+
81
+ # See how the printer service is requested on the last two lines. In this case, the @#printer@ message is sent to the registry with a single parameter. You can also request the service in two other ways:
82
+
83
+ <pre>
84
+ dot_matrix = registry[ :printer, :dot_matrix ]
85
+ ink_jet = registry.get( :printer, :ink_jet )
86
+ </pre>
87
+
88
+ Choose the style that works best for you.
89
+
@@ -29,3 +29,12 @@ As a convenience, if the value passed to @#get@ responds to either @fullname@ or
29
29
  Foo.new( c.logs.get( p ) )
30
30
  end
31
31
  </pre>
32
+
33
+ As a further convenience, there is a @:log_for@ service that is parameterized. Just pass the name of the log to retreive (or the service point instance) and it will return the log handle directly:
34
+
35
+ <pre>
36
+ reg.register( :foo ) do |c,p|
37
+ Foo.new( c.log_for( p ) )
38
+ end
39
+ </pre>
40
+
@@ -4,6 +4,10 @@ h3. Standard Service Models:
4
4
 
5
5
  table{border: 1px solid black}.
6
6
  |_<. Name|_<. Pipeline|_<. Effect|
7
+ |^. @:multiton@|^. @:multiton@|The returned value will be unique for each unique parameter set given to the service.|
8
+ |^. @:multiton_deferred@|^. @:multiton@, @:deferred@|As @:multiton@, but a proxy is returned, deferring the instantiation of the service itself until a method is invoked on it.|
9
+ |^. @:multiton_initialize@|^. @:multiton@, @:initialize@|As @:multiton@, but invoke an initialization method on every service instance as soon as they are created.|
10
+ |^. @:multiton_deferred_initialize@|^. @:multiton@, @:deferred@, @:initialize@|As @:multiton@, but a proxy is returned, deferring the instantiation of the service itself until a method is invoked on it. When the service is instantiated, an initialization method will be invoked on it.|
7
11
  |^. @:prototype@|^. (empty)|Immediately instantiate the service, at every request.|
8
12
  |^. @:prorotype_deferred@|^. @:deferred@|Return a proxy, that will instantiate the service the first time a method is invoked on the proxy. A new proxy instance will be returned for each request.|
9
13
  |^. @:prototype_initialize@|^. @:initialize@|Immediately instantiate the service, and invoke an initialization method, at every request.|
@@ -9,6 +9,7 @@ There are five standard pipeline elements available in Needle (although you may
9
9
  * @deferred@: this will always return a proxy that wraps subsequent pipeline elements, causing the subsequent elements to be executed only when a method is invoked on the proxy (at which point the method is then delegated to the resulting service).
10
10
  * @initialize@: this will invoke a method on the resulting service (defaults to @initialize_service@, though it can be changed). It is used for doing final initialization of services (for services that need it).
11
11
  * @interceptor@: this element is used to implement the proxy that wraps the interceptors around the service. It is only attached to the pipeline when an interceptor is attached to a service.
12
+ * @multiton@: this element enforces a multiton guard on the service. This means that the service will only be instantiated once for each unique set of parameters given to the service.
12
13
  * @singleton@: this is a multiplicity guard that ensures a service is instantiated only once per process.
13
14
  * @threaded@: this is like the @singleton@ element, but it ensures that a service is instantiated no more than once _per thread_.
14
15
 
@@ -36,7 +36,15 @@ module Needle
36
36
  # The name of this container. May be +nil+.
37
37
  attr_reader :name
38
38
 
39
- # Create a new empty container with the given parent and name.
39
+ # A hash of default options to use when registering services. These
40
+ # defaults also apply to namespaces, so when specifying a new default
41
+ # service model (for instance) there may be unexpected side-effects with
42
+ # the namespaces that are created.
43
+ attr_reader :defaults
44
+
45
+ # Create a new empty container with the given parent and name. If a parent
46
+ # is given, this container will inherit the defaults of the parent at the
47
+ # time the container was created.
40
48
  def initialize( parent=nil, name=nil )
41
49
  @root = nil
42
50
  @builder = nil
@@ -44,6 +52,8 @@ module Needle
44
52
  @name = name
45
53
  @parent = parent
46
54
  @service_points = Hash.new
55
+
56
+ @defaults = ( parent.nil? ? Hash.new : parent.defaults.dup )
47
57
  end
48
58
 
49
59
  # Returns the root of the current hierarchy. If the container is the
@@ -55,11 +65,19 @@ module Needle
55
65
  @root = parent.root
56
66
  end
57
67
 
68
+ # Returns +true+ if this container either is the given container or is
69
+ # descended from the given container, and +false+ otherwise.
70
+ def descended_from?( container )
71
+ return true if self == container
72
+ return false unless parent
73
+ parent.descended_from? container
74
+ end
75
+
58
76
  # Return the fully qualified name of this container, which is the
59
77
  # container's name and all parent's names up to the root container,
60
78
  # catenated together with dot characters, i.e., "one.two.three".
61
79
  def fullname
62
- parent_name = ( @parent ? @parent.fullname : nil )
80
+ parent_name = ( parent ? parent.fullname : nil )
63
81
  return @name.to_s unless parent_name
64
82
  "#{parent_name}.#{@name}"
65
83
  end
@@ -120,7 +138,7 @@ module Needle
120
138
 
121
139
  name = name.to_s.intern unless name.is_a?( Symbol )
122
140
  @service_points[ name ] =
123
- ServicePoint.new( self, name, opts, &callback )
141
+ ServicePoint.new( self, name, @defaults.merge( opts ), &callback )
124
142
 
125
143
  self
126
144
  end
@@ -270,7 +288,7 @@ module Needle
270
288
  # otherwise +nil+ is returned.
271
289
  def find_definition( name )
272
290
  point = @service_points[ name ]
273
- point = @parent.find_definition( name ) if @parent unless point
291
+ point = parent.find_definition( name ) if parent unless point
274
292
  point
275
293
  end
276
294
 
@@ -279,13 +297,19 @@ module Needle
279
297
  # If the named service does not exist, ServiceNotFound is raised.
280
298
  #
281
299
  # Note that this returns the instantiated service, not the service point.
282
- def []( name )
300
+ #
301
+ # Also, if any pipeline element in the instantiation pipeline does not
302
+ # support extra parameters when extra parameters have been given, then an
303
+ # error will be raised.
304
+ def get( name, *args )
283
305
  point = find_definition( name )
284
306
  raise ServiceNotFound, "#{fullname}.#{name}" unless point
285
307
 
286
- point.instance
308
+ point.instance( *args )
287
309
  end
288
310
 
311
+ alias :[] :get
312
+
289
313
  # Returns +true+ if this container includes a service point with the given
290
314
  # name. Returns +false+ otherwise.
291
315
  def has_key?( name )
@@ -296,7 +320,7 @@ module Needle
296
320
  # service point with the given name. Returns +false+ otherwise.
297
321
  def knows_key?( name )
298
322
  return true if has_key?( name )
299
- return @parent.knows_key?( name ) if @parent
323
+ return parent.knows_key?( name ) if parent
300
324
  false
301
325
  end
302
326
 
@@ -351,17 +375,9 @@ module Needle
351
375
  #
352
376
  # container.register( :add ) { Adder.new }
353
377
  # p container.add == container[:add] # => true
354
- #
355
- # This also allows you to register new services in the container by
356
- # sending the container a message with an attached block.
357
- #
358
- # Usage:
359
- #
360
- # container.foo { Bar.new }
361
- # p container.foo
362
- def method_missing( sym, *args, &block )
363
- if block.nil? && args.empty? && knows_key?( sym )
364
- self[sym]
378
+ def method_missing( sym, *args )
379
+ if knows_key?( sym )
380
+ get( sym, *args )
365
381
  else
366
382
  super
367
383
  end
@@ -372,7 +388,36 @@ module Needle
372
388
  # this case, #has_key? is used instead of #knows_key? so that subcontainers
373
389
  # may be used as proper hashes by their parents.
374
390
  def respond_to?( sym )
375
- @service_points.has_key?( sym ) || super
391
+ has_key?( sym ) || super
392
+ end
393
+
394
+ # Specifies a set of default options to use temporarily. The options are
395
+ # merged with the current set of defaults for the container. The original
396
+ # options are returned, and may be restored by invoking #use again with
397
+ # the hash that is returned. If a block is given, the registry will be
398
+ # yielded to it and the options automatically restored when the block
399
+ # returns.
400
+ def use( opts, &block ) # :yield: self
401
+ use! @defaults.merge( opts ), &block
402
+ end
403
+
404
+ # Specifies a set of default options to use temporarily. The original
405
+ # options are returned. This differs from #use in that it will completely
406
+ # replace the original options, instead of merging the parameters with
407
+ # the originals.
408
+ def use!( opts )
409
+ original = @defaults
410
+ @defaults = opts
411
+
412
+ if block_given?
413
+ begin
414
+ yield self
415
+ ensure
416
+ use! original
417
+ end
418
+ end
419
+
420
+ return original
376
421
  end
377
422
 
378
423
  end
@@ -30,7 +30,7 @@ module Needle
30
30
  protected_instance_methods +
31
31
  public_instance_methods -
32
32
  [ "instance_eval", "__id__", "__send__", "initialize", "remove_const",
33
- "method_missing", "method", "class", "inspect", "to_s", "instance_variables" ]
33
+ "method_missing", "method", "class", "inspect", "to_s", "instance_variables", "block_given?" ]
34
34
  ).
35
35
  each { |m| undef_method m }
36
36
 
@@ -76,6 +76,38 @@ module Needle
76
76
  @container.__send__( :require, *parms )
77
77
  end
78
78
 
79
+ # Delegate to Container#use on the current container, but yields the
80
+ # definition context instead of the container.
81
+ def use( opts, &block )
82
+ use! @container.defaults.merge( opts ), &block
83
+ end
84
+
85
+ # Delegate to Container#use! on the current container, but yields the
86
+ # definition context instead of the container.
87
+ def use!( opts )
88
+ original = @container.use!( opts )
89
+
90
+ if block_given?
91
+ begin
92
+ yield self
93
+ ensure
94
+ @container.use! original
95
+ end
96
+ end
97
+
98
+ original
99
+ end
100
+
101
+ # Delegates to Container#has_key?.
102
+ def has_key?( name )
103
+ @container.has_key?( name )
104
+ end
105
+
106
+ # Delegates to Container#knows_key?.
107
+ def knows_key?( name )
108
+ @container.knows_key?( name )
109
+ end
110
+
79
111
  # Any method invocation with no block and no parameters is interpreted to
80
112
  # be a service reference on the wrapped container, and delegates to
81
113
  # Container#[]. If the block is not given but the args are not empty, a
@@ -85,11 +117,7 @@ module Needle
85
117
  # all parameters in place.
86
118
  def method_missing( sym, *args, &block )
87
119
  if block.nil?
88
- if args.empty?
89
- @container[sym]
90
- else
91
- super
92
- end
120
+ @container.get( sym, *args )
93
121
  else
94
122
  @container.register( sym, *args, &block )
95
123
  end
@@ -0,0 +1,64 @@
1
+ #--
2
+ # =============================================================================
3
+ # Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
4
+ # All rights reserved.
5
+ #
6
+ # This source file is distributed as part of the Needle dependency injection
7
+ # library for Ruby. This file (and the library as a whole) may be used only as
8
+ # allowed by either the BSD license, or the Ruby license (or, by association
9
+ # with the Ruby license, the GPL). See the "doc" subdirectory of the Needle
10
+ # distribution for the texts of these licenses.
11
+ # -----------------------------------------------------------------------------
12
+ # needle website : http://needle.rubyforge.org
13
+ # project website: http://rubyforge.org/projects/needle
14
+ # =============================================================================
15
+ #++
16
+
17
+ require 'needle/pipeline/element'
18
+ require 'needle/thread'
19
+
20
+ module Needle
21
+ module Lifecycle
22
+
23
+ # The instantiation pipeline element that enforces the multiton
24
+ # multiplicity. "Multiton" multiplicity is like singleton multiplicity,
25
+ # except that the guarded instance is unique for each unique set of
26
+ # arguments passed to the multiton.
27
+ class Multiton < Needle::Pipeline::Element
28
+ set_default_priority 100
29
+
30
+ # Creates the mutex to use and initializes the cache.
31
+ def initialize_element
32
+ @mutex = QueryableMutex.new
33
+ @cached = Hash.new
34
+ @is_cached = Hash.new( false )
35
+ end
36
+
37
+ # Returns the cached reference for the given arguments, if it has been
38
+ # previously cached. Otherwise, invokes the next element in the pipeline
39
+ # and caches the result. The cached reference is returned.
40
+ def call( container, point, *args )
41
+ unless @is_cached[ args ]
42
+ @mutex.synchronize do
43
+ unless @is_cached[ args ]
44
+ @cached[ args ] = succ.call( container, point, *args )
45
+ @is_cached[ args ] = true
46
+ end
47
+ end
48
+ end
49
+
50
+ @cached[ args ]
51
+ end
52
+
53
+ # Resets the caches for this multiton object.
54
+ def reset!
55
+ @mutex.synchronize do
56
+ @cached = Hash.new
57
+ @is_cached = Hash.new( false )
58
+ end
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+ end