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
@@ -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