needle 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -35,11 +35,11 @@ module Needle
35
35
  # Returns the cached reference, if it has been previously cached.
36
36
  # Otherwise, invokes the next element in the pipeline and caches the
37
37
  # result. The cached reference is returned.
38
- def call( *args )
38
+ def call( container, point )
39
39
  unless @is_cached
40
40
  @mutex.synchronize do
41
41
  unless @is_cached
42
- @cached = succ.call( *args )
42
+ @cached = succ.call( container, point )
43
43
  @is_cached = true
44
44
  end
45
45
  end
@@ -33,12 +33,12 @@ module Needle
33
33
  # Returns the cached reference, if it has been previously cached for
34
34
  # the current thread. Otherwise, invokes the next element in the pipeline
35
35
  # and caches the result. The cached reference is returned.
36
- def call( *args )
36
+ def call( container, point )
37
37
  cache = service_cache
38
38
  name = service_point.fullname
39
39
 
40
40
  unless cache.has_key?( name )
41
- service = succ.call( *args )
41
+ service = succ.call( container, point )
42
42
  cache[ name ] = service
43
43
  end
44
44
 
@@ -46,7 +46,8 @@ module Needle
46
46
  end
47
47
 
48
48
  # Resets the cached singleton instance, so that the next time it is
49
- # requested it is re-constructed.
49
+ # requested it is re-constructed. Only the cache for the current thread
50
+ # and service point is reset.
50
51
  def reset!
51
52
  cache = service_cache
52
53
  cache.delete service_point.fullname
@@ -20,7 +20,7 @@ module Needle
20
20
  module Pipeline
21
21
 
22
22
  # Represents a collection of pipeline elements. Elements may be added to
23
- # the pipeline, and then returned as a single object representing thechain
23
+ # the pipeline, and then returned as a single object representing the chain
24
24
  # of pipeline elements.
25
25
  class Collection
26
26
 
@@ -105,6 +105,15 @@ module Needle
105
105
  @elements.find { |el| name == el.name }
106
106
  end
107
107
 
108
+ # Clears the state for each named element. If no elements are named, all
109
+ # elements are cleared.
110
+ def reset!( *args )
111
+ @elements.each do |element|
112
+ element.reset! if args.empty? || args.include?( element.name )
113
+ end
114
+ self
115
+ end
116
+
108
117
  # Builds a linked list of the elements, with the last element being the
109
118
  # block (or block-like) object given by the parameter. Higher-priority
110
119
  # elements will be closer to the head of the list.
@@ -78,6 +78,12 @@ module Needle
78
78
  raise NotImplementedError
79
79
  end
80
80
 
81
+ # Invoked by Pipeline::Collection#reset!. Subclasses of Element that
82
+ # save any kind of state should override this method to clear that
83
+ # state on demand.
84
+ def reset!
85
+ end
86
+
81
87
  alias :[] :call
82
88
  end
83
89
 
@@ -20,6 +20,7 @@ require 'needle/errors'
20
20
  require 'needle/interceptor'
21
21
  require 'needle/lifecycle/deferred'
22
22
  require 'needle/lifecycle/initialize'
23
+ require 'needle/lifecycle/multiton'
23
24
  require 'needle/lifecycle/singleton'
24
25
  require 'needle/lifecycle/threaded'
25
26
  require 'needle/log-factory'
@@ -125,11 +126,16 @@ module Needle
125
126
  :initialize => Needle::Lifecycle::Initialize,
126
127
  :deferred => Needle::Lifecycle::Deferred,
127
128
  :interceptor => Needle::Pipeline::InterceptorElement,
129
+ :multiton => Needle::Lifecycle::Multiton,
128
130
  :threaded => Needle::Lifecycle::Threaded
129
131
  )
130
132
 
131
133
  register( :service_models, :pipeline=>[:singleton] ) { Hash.new }
132
134
  self[:service_models].update(
135
+ :multiton => [ :multiton ],
136
+ :multiton_initialize => [ :multiton, :initialize ],
137
+ :multiton_deferred => [ :multiton, :deferred ],
138
+ :multiton_deferred_initialize => [ :multiton, :deferred, :initialize ],
133
139
  :prototype => [],
134
140
  :prototype_initialize => [ :initialize ],
135
141
  :prototype_deferred => [ :deferred ],
@@ -137,7 +143,7 @@ module Needle
137
143
  :singleton => [ :singleton ],
138
144
  :singleton_initialize => [ :singleton, :initialize ],
139
145
  :singleton_deferred => [ :singleton, :deferred ],
140
- :singleton_deferred_initialize => [ :singleton, :deferred, :initialize ],
146
+ :singleton_deferred_initialize => [ :singleton, :deferred, :initialize],
141
147
  :threaded => [ :threaded ],
142
148
  :threaded_initialize => [ :threaded, :initialize ],
143
149
  :threaded_deferred => [ :threaded, :deferred ],
@@ -145,11 +151,18 @@ module Needle
145
151
  )
146
152
 
147
153
  register( :definition_context_factory ) { DefinitionContext }
148
- register( :namespace_impl_factory ) { Container }
149
- register( :interceptor_impl_factory ) { Interceptor }
150
154
 
151
- register( :logs ) { LogFactory.new( opts[:logs] || {} ) }
152
- register( :logging_interceptor ) { LoggingInterceptor }
155
+ define do |b|
156
+ b.namespace_impl_factory { Container }
157
+ b.interceptor_impl_factory { Interceptor }
158
+
159
+ b.logs { LogFactory.new( opts[:logs] || {} ) }
160
+
161
+ # a parameterized version of c[:logs] for easier access
162
+ b.log_for( :model => :prototype ) { |c,p,name| c[:logs].get(name) }
163
+
164
+ b.logging_interceptor { LoggingInterceptor }
165
+ end
153
166
  end
154
167
  private :bootstrap
155
168
 
@@ -20,7 +20,7 @@ require 'needle/thread'
20
20
  module Needle
21
21
 
22
22
  # A "service point" is a definition of a service. Just as a class defines the
23
- # behavior of an object, so does a service point describes a service. In
23
+ # behavior of an object, so does a service point describe a service. In
24
24
  # particular, a service point also knows how to instantiate a service.
25
25
  #
26
26
  # A ServicePoint should never be directly instantiated. Instead, define
@@ -57,7 +57,9 @@ module Needle
57
57
  @callback = callback
58
58
  @pipeline = Needle::Pipeline::Collection.new self
59
59
  @chain = nil
60
- @mutex = QueryableMutex.new
60
+
61
+ @chain_mutex = QueryableMutex.new
62
+ @element_mutex = QueryableMutex.new
61
63
 
62
64
  if opts[:pipeline]
63
65
  elements = opts[:pipeline]
@@ -66,6 +68,7 @@ module Needle
66
68
  elements = @container[:service_models][model]
67
69
  end
68
70
 
71
+ elements.concat [ *opts[:include] ] if opts[:include]
69
72
  elements.each { |element| @pipeline.add element, opts }
70
73
  end
71
74
 
@@ -84,26 +87,34 @@ module Needle
84
87
  # Adds the given interceptor definition to this service point. The
85
88
  # parameter should act like an instance of Interceptor.
86
89
  def interceptor( interceptor )
87
- element = @pipeline.get( :interceptor )
88
- unless element
89
- @chain = nil
90
- @pipeline.add( :interceptor )
90
+ @element_mutex.synchronize do
91
91
  element = @pipeline.get( :interceptor )
92
+ unless element
93
+ @pipeline.add( :interceptor )
94
+ element = @pipeline.get( :interceptor )
95
+ end
96
+ element.interceptors << interceptor
97
+ @pipeline.reset!
98
+ @chain = nil
92
99
  end
93
- element.interceptors << interceptor
94
100
  end
95
101
 
96
102
  # Return the service instance represented by this service point. Depending
97
103
  # on the style of lifecycle management chosen for this service point, this
98
104
  # may or may not be a new instance for every invocation of this method.
99
- def instance
105
+ #
106
+ # Any arguments to this method will be passed through to the chain, which
107
+ # may cause an error if there is an element in the pipeline that does not
108
+ # accept additional arguments. Regardless, the first two parameters to
109
+ # the chain will always be the container and the service point.
110
+ def instance( *args )
100
111
  unless @chain
101
- @mutex.synchronize do
112
+ @chain_mutex.synchronize do
102
113
  @chain = @pipeline.chain_to( @callback ) unless @chain
103
114
  end
104
115
  end
105
116
 
106
- @chain.call( @container, self )
117
+ @chain.call( @container, self, *args )
107
118
  end
108
119
 
109
120
  end
@@ -18,7 +18,7 @@ module Needle
18
18
  module Version
19
19
 
20
20
  MAJOR = 1
21
- MINOR = 1
21
+ MINOR = 2
22
22
  TINY = 0
23
23
 
24
24
  # The version of the Needle library in use.
@@ -0,0 +1,43 @@
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
+ $:.unshift "../../lib"
18
+
19
+ require "needle/lifecycle/multiton"
20
+ require "test/unit"
21
+
22
+ class TC_Lifecycle_Multiton < Test::Unit::TestCase
23
+
24
+ def test_multiplicity
25
+ element = Needle::Lifecycle::Multiton.new( nil )
26
+ element.succ = Proc.new { |c,p,x| "value: #{x}" }
27
+ p1 = element.call( nil, nil, :v )
28
+ p2 = element.call( nil, nil, :v )
29
+ assert_same p1, p2
30
+ p3 = element.call( nil, nil, :x )
31
+ assert_not_same p1, p3
32
+ end
33
+
34
+ def test_reset!
35
+ element = Needle::Lifecycle::Multiton.new( nil )
36
+ element.succ = Proc.new { |c,p,x| "value: #{x}" }
37
+ p1 = element.call( nil, nil, :v )
38
+ element.reset!
39
+ p2 = element.call( nil, nil, :v )
40
+ assert_not_same p1, p2
41
+ end
42
+
43
+ end
@@ -24,9 +24,25 @@ class TC_Lifecycle_Singleton < Test::Unit::TestCase
24
24
  def test_multiplicity
25
25
  element = Needle::Lifecycle::Singleton.new( nil )
26
26
  element.succ = Proc.new { Hash.new }
27
- p1 = element.call
28
- p2 = element.call
27
+ p1 = element.call( nil, nil )
28
+ p2 = element.call( nil, nil )
29
29
  assert_same p1, p2
30
30
  end
31
31
 
32
+ def test_extra_args_disallowed
33
+ element = Needle::Lifecycle::Singleton.new( nil )
34
+ assert_raise( ArgumentError ) do
35
+ element.call( 1, 2, 3 )
36
+ end
37
+ end
38
+
39
+ def test_reset!
40
+ element = Needle::Lifecycle::Singleton.new( nil )
41
+ element.succ = Proc.new { Hash.new }
42
+ p1 = element.call( nil, nil )
43
+ element.reset!
44
+ p2 = element.call( nil, nil )
45
+ assert_not_same p1, p2
46
+ end
47
+
32
48
  end
@@ -29,19 +29,25 @@ class TC_Lifecycle_Threaded < Test::Unit::TestCase
29
29
  end
30
30
 
31
31
  def test_single_thread
32
- @element.call
33
- @element.call
34
- @element.call
32
+ @element.call(1,2)
33
+ @element.call(1,2)
34
+ @element.call(1,2)
35
35
  assert_equal 1, @counter
36
36
  end
37
37
 
38
38
  def test_multi_thread
39
39
  threads = []
40
- threads << Thread.new { @element.call }
41
- threads << Thread.new { @element.call }
42
- threads << Thread.new { @element.call }
40
+ threads << Thread.new { @element.call(1,2) }
41
+ threads << Thread.new { @element.call(1,2) }
42
+ threads << Thread.new { @element.call(1,2) }
43
43
  threads.each { |t| t.join }
44
44
  assert_equal 3, @counter
45
45
  end
46
46
 
47
+ def test_extra_args_disallowed
48
+ assert_raise( ArgumentError ) do
49
+ @element.call(1,2,3)
50
+ end
51
+ end
52
+
47
53
  end
@@ -27,9 +27,12 @@ class TC_Pipeline_Collection < Test::Unit::TestCase
27
27
  attr_reader :priority
28
28
  attr_accessor :succ
29
29
 
30
+ attr_reader :events
31
+
30
32
  def initialize( point, name, priority, options )
31
33
  @service_point = point
32
34
  @name, @priority = name, priority
35
+ @events = []
33
36
  end
34
37
 
35
38
  def call( *args )
@@ -41,11 +44,16 @@ class TC_Pipeline_Collection < Test::Unit::TestCase
41
44
  def <=>( element )
42
45
  priority <=> element.priority
43
46
  end
47
+
48
+ def reset!
49
+ @events << :reset!
50
+ end
44
51
  end
45
52
 
46
53
  def setup
47
- elements = { :pipeline_elements => { :mock => MockElement } }
48
- point = Struct.new( :container ).new( elements )
54
+ elements = Struct.new( :fullname, :pipeline_elements ).new(
55
+ "test", { :mock => MockElement, :mock2 => MockElement } )
56
+ point = Struct.new( :container, :fullname ).new( elements, "test" )
49
57
  @collection = Needle::Pipeline::Collection.new( point )
50
58
  end
51
59
 
@@ -113,4 +121,20 @@ class TC_Pipeline_Collection < Test::Unit::TestCase
113
121
  assert_equal b, "|proc|Mock|tail|Done|end"
114
122
  end
115
123
 
124
+ def test_reset_one!
125
+ @collection.add :mock, 25
126
+ @collection.add :mock2, 25
127
+ @collection.reset! :mock2
128
+ assert_equal [ :reset! ], @collection.get( :mock2 ).events
129
+ assert_equal [], @collection.get( :mock ).events
130
+ end
131
+
132
+ def test_reset!
133
+ @collection.add :mock, 25
134
+ @collection.add :mock2, 25
135
+ @collection.reset!
136
+ assert_equal [ :reset! ], @collection.get( :mock2 ).events
137
+ assert_equal [ :reset! ], @collection.get( :mock ).events
138
+ end
139
+
116
140
  end
data/test/services.rb CHANGED
@@ -16,6 +16,14 @@ module A
16
16
  end
17
17
  module_function :register_other_services
18
18
 
19
+ def register_parameterized_services( container )
20
+ container.define do |b|
21
+ b.baz1 { |c,p,*args| args.join(":") }
22
+ b.baz2( :model=>:prototype ) { |c,p,*a| a.join(":") }
23
+ end
24
+ end
25
+ module_function :register_parameterized_services
26
+
19
27
  end
20
28
  end
21
29
  end
data/test/tc_container.rb CHANGED
@@ -370,4 +370,86 @@ class TC_Container < Test::Unit::TestCase
370
370
  assert_equal CustomInterceptor, subspace.intercept( :logs ).class
371
371
  end
372
372
 
373
+ def test_descended_from?
374
+ outer = new_container
375
+ middle = new_container( outer )
376
+ inner = new_container( middle )
377
+ assert inner.descended_from?( outer )
378
+ assert inner.descended_from?( middle )
379
+ assert inner.descended_from?( inner )
380
+ assert !outer.descended_from?( inner )
381
+ end
382
+
383
+ def test_defaults_inherited
384
+ root = new_container
385
+ root.defaults[:hello] = :world
386
+ c1 = new_container( root )
387
+ assert_equal :world, c1.defaults[:hello]
388
+ root.defaults[:hello] = :jisang
389
+ assert_equal :world, c1.defaults[:hello]
390
+ end
391
+
392
+ def test_defaults_used
393
+ root = new_container
394
+ root.define.foo { Object.new }
395
+ root.defaults[:model] = :prototype
396
+ root.define.bar { Object.new }
397
+ f1 = root.foo
398
+ f2 = root.foo
399
+ assert_same f1, f2
400
+ b1 = root.bar
401
+ b2 = root.bar
402
+ assert_not_same b1, b2
403
+ end
404
+
405
+ def test_use_bang_without_block
406
+ root = new_container
407
+ original = root.use! :model => :prototype
408
+ assert original.empty?
409
+ o2 = root.use! :pipeline => [ :threaded ]
410
+ assert_nil root.defaults[:model]
411
+ assert_equal [ :threaded ], root.defaults[:pipeline]
412
+ root.use! o2
413
+ assert_nil root.defaults[:pipeline]
414
+ assert_equal :prototype, root.defaults[:model]
415
+ end
416
+
417
+ def test_use_bang_with_block
418
+ root = new_container
419
+ root.use! :model => :prototype do |r|
420
+ assert_same root, r
421
+ assert_equal :prototype, root.defaults[:model]
422
+ root.use! :pipeline => [ :threaded ] do
423
+ assert_nil root.defaults[:model]
424
+ assert_equal [ :threaded ], root.defaults[:pipeline]
425
+ end
426
+ assert_nil root.defaults[:pipeline]
427
+ assert_equal :prototype, root.defaults[:model]
428
+ end
429
+ assert_nil root.defaults[:model]
430
+ end
431
+
432
+ def test_use_without_block
433
+ root = new_container
434
+ o1 = root.use :model => :prototype
435
+ o2 = root.use :pipeline => [ :threaded ]
436
+ assert_equal :prototype, root.defaults[:model]
437
+ assert_equal [ :threaded ], root.defaults[:pipeline]
438
+ end
439
+
440
+ def test_use_with_block
441
+ root = new_container
442
+ root.use :model => :prototype do |r|
443
+ assert_same root, r
444
+ assert_equal :prototype, root.defaults[:model]
445
+ root.use :pipeline => [ :threaded ] do
446
+ assert_equal :prototype, root.defaults[:model]
447
+ assert_equal [ :threaded ], root.defaults[:pipeline]
448
+ end
449
+ assert_nil root.defaults[:pipeline]
450
+ assert_equal :prototype, root.defaults[:model]
451
+ end
452
+ assert_nil root.defaults[:model]
453
+ end
454
+
373
455
  end