9. Customizing Needle
9.1. Namespaces
By default, when you create a namespace in Needle, the namespace is registered as a service. The type of the service is determined by the :namespace_impl_factory service, which (by default) returns the Needle::Container class.
You can specify your own custom implementation for namespaces by registering your own :namespace_impl_factory service. In fact, each namespace can have its own implementation of subnamespaces—just register a :namespace_impl_factory in each one that you want to be specialized.
Here’s a contrived example. Suppose you want each namespace to keep track of the precise time that it was created.
class TimeTrackerNamespace < Needle::Container
attr_reader :birth_date
def initialize( *args )
super
@birth_date = Time.now
end
end
reg = Needle::Registry.new
reg.register( :namespace_impl_factory ) { TimeTrackerNamespace }
reg.namespace :hello
p reg.hello.birth_date
In general, you’ll be better off having your custom implementation extend Needle::Container, although the only real requirement is that your implementation publish the same interface as the default namespace implementation.
9.2. Interceptors
When you attach an interceptor to a service, that new interceptor is wrapped in a definition object that includes various metadata about the interceptor, including its implementation, its priority, its name, and so forth. The implementation of this interceptor definition is determined by the value of the :interceptor_impl_factory service, which by default returns Needle::Interceptor.
It is this wrapper object that allows interceptor definitions to be done using method chaining:
reg.intercept( :foo ).with { ... }.with_options(...)
If you wish to add custom, domain-specific functionality to the interceptor wrapper, you can register your own implementation of the :interceptor_impl_factory. Consider the following contrived example, where an “only_if” clause is given to determine when the interceptor should be invoked.
class OnlyIfInterceptor < Needle::Interceptor
def only_if( &block )
@only_if = block
self
end
def action
action_proc = super
lambda do |chain,ctx|
if @only_if.call( chain, ctx )
action_proc.call( chain, ctx )
else
chain.process_next( ctx )
end
end
end
end
reg = Needle::Registry.new
reg.register( :interceptor_impl_factory ) { OnlyIfInterceptor }
reg.register( :foo ) { Bar.new }
reg.intercept( :foo ).
with { |c| c.logging_interceptor }.
only_if { |ch,ctx| something_is_true( ch, ctx ) }.
with_options(...)
9.3. Contexts
A definition context is used when registering services using any of the #define interfaces. For example, Container#define yields an instance of a definition context to the given block, and Container#define! uses the block in an instance_eval on a definition context.
The default implementation used for definition contexts is defined by the :definition_context_factory service. By default, this service returns Needle::DefinitionContext, but you can specify your own definition context implementations by overriding this service. In fact, each namespace could have its own definition context implementation, if needed.
Consider the following contrived example, where you want to provide a convenient way to register services of type Hash.
class MyDefinitionContext < Needle::DefinitionContext
def register_hash( name, opts={} )
this_container.register( name, opts ) { Hash.new }
end
end
reg = Needle::Registry.new
reg.register( :definition_context_factory ) { MyDefinitionContext }
reg.define do |b|
b.register_hash( :test1 )
b.register_hash( :test2 )
end
reg.test1[:key] = "value"
reg.test2[:foo] = "bar"