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.
- data/benchmarks/instantiation.rb +39 -0
- data/doc/faq/faq.yml +137 -102
- data/doc/manual-html/chapter-1.html +15 -19
- data/doc/manual-html/chapter-2.html +52 -23
- data/doc/manual-html/chapter-3.html +7 -9
- data/doc/manual-html/chapter-4.html +6 -8
- data/doc/manual-html/chapter-5.html +16 -19
- data/doc/manual-html/chapter-6.html +31 -8
- data/doc/manual-html/chapter-7.html +19 -8
- data/doc/manual-html/chapter-8.html +8 -11
- data/doc/manual-html/chapter-9.html +7 -6
- data/doc/manual-html/index.html +5 -3
- data/doc/manual/manual.yml +2 -1
- data/doc/manual/parts/01_alternatives.txt +2 -1
- data/doc/manual/parts/02_services.txt +33 -0
- data/doc/manual/parts/logging_logfactory.txt +9 -0
- data/doc/manual/parts/models_models.txt +4 -0
- data/doc/manual/parts/models_pipelines.txt +1 -0
- data/lib/needle/container.rb +64 -19
- data/lib/needle/definition-context.rb +34 -6
- data/lib/needle/lifecycle/multiton.rb +64 -0
- data/lib/needle/lifecycle/singleton.rb +2 -2
- data/lib/needle/lifecycle/threaded.rb +4 -3
- data/lib/needle/pipeline/collection.rb +10 -1
- data/lib/needle/pipeline/element.rb +6 -0
- data/lib/needle/registry.rb +18 -5
- data/lib/needle/service-point.rb +21 -10
- data/lib/needle/version.rb +1 -1
- data/test/lifecycle/tc_multiton.rb +43 -0
- data/test/lifecycle/tc_singleton.rb +18 -2
- data/test/lifecycle/tc_threaded.rb +12 -6
- data/test/pipeline/tc_collection.rb +26 -2
- data/test/services.rb +8 -0
- data/test/tc_container.rb +82 -0
- data/test/tc_definition_context.rb +63 -7
- data/test/tc_registry.rb +13 -1
- data/test/tc_service_point.rb +49 -0
- 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(
|
38
|
+
def call( container, point )
|
39
39
|
unless @is_cached
|
40
40
|
@mutex.synchronize do
|
41
41
|
unless @is_cached
|
42
|
-
@cached = succ.call(
|
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(
|
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(
|
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
|
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
|
|
data/lib/needle/registry.rb
CHANGED
@@ -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
|
-
|
152
|
-
|
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
|
|
data/lib/needle/service-point.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
@
|
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
|
data/lib/needle/version.rb
CHANGED
@@ -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 =
|
48
|
-
|
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
|