Extend a class (or module) with Container to make the class behave as a class of service containers. Containers encapsulate the way in which related services are instantiated and connected together. Services themselves are insulated from this process [except when they are injected with the container].
Use MinDI by includeing the MinDI::InjectableContainer module in your container class. As of version 0.2, this does two things: it extends your container class with the Container module, which provides class methods for expressing service definitions in a natural, rubylike way. It also injects the container into all services which gives them direct access to the other services in the container. If you prefer to have the container funtionality without injecting services (i.e., just "contructor injection") You can just include MinDI::BasicContainer.
The Container module also defines shortcuts for defining some service types using method_missing. These methods and shortcuts are described below.
A service definition involves a service name and a block of code. It defines an instance method whose name is the service name. The block is used to instantiate the service. When and how that service is instantiated depends on the type of service.
Note: MinDI uses some instance variables and auxiliary instance methods in the container. The instance variables have the same name as the corresponding services, possibly with added suffixes, as in the case of deferred. The auxiliary methods are named so as to make conflicts unlikely. See the implemetations of iv and impl_method_name for details.
Note that services can be defined dynamically by reopening the class scope or simply by calling the service with a block. See examples/dynamic.rb.
- deferred
- define_implementation
- generic
- impl_method_name
- iv
- multikey_multiton
- multiton
- singleton
- threaded
Methods which define a service in terms of simple rules involving existence, uniqeness, and parameters.
Define a generic service, which has no built-in rules for existence or uniqueness. There is no shortcut for generic service definition. Calling the service simply calls the associated block. This is also known as a prototype service.
[ show source ]
# File lib/mindi.rb, line 48 def generic(name, &impl) # :yields: define_implementation(name, impl) end
Define a multiton service with multiple keys:
multiton(:service_name) { |arg0, arg1, ...| ... }
The block will be called once per distinct (in the sense of hash keys) argument list to instantiate a unique value corresponding to the argument list. The shortcut for defining a multikey_multiton with multiple keys is:
service_name { |arg0, arg1, ...| ... }
The service is invoked as service_name(arg0, arg1, …). Variable length argument lists, using the splat notation, are permitted.
[ show source ]
# File lib/mindi.rb, line 113 def multikey_multiton(name, &impl) # :yields: arg0, arg1, ... impl_name = Container.impl_method_name(name) define_implementation(impl_name, impl) ivname = Container.iv(name) define_method(name) do |*key| map = instance_variable_get(ivname) map ||= instance_variable_set(ivname, {}) map.key?(key) ? map[key] : map[key] = send(impl_name, *key) end end
Define a multiton service:
multiton(:service_name) { |arg| ... }
The block will be called once per distinct (in the sense of hash keys) argument to instantiate a unique value corresponding to the argument. The shortcut for defining a multiton is:
service_name { |arg| ... }
The service is invoked as service_name(arg).
[ show source ]
# File lib/mindi.rb, line 88 def multiton(name, &impl) # :yields: arg impl_name = Container.impl_method_name(name) define_implementation(impl_name, impl) ivname = Container.iv(name) define_method(name) do |key| map = instance_variable_get(ivname) map ||= instance_variable_set(ivname, {}) map.key?(key) ? map[key] : map[key] = send(impl_name, key) end end
Define a singleton service:
singleton(:service_name) { ... }
The block will be called at most once to instantiate a unique value for the service. The shortcut for defining a singleton is:
service_name { ... }
The service is invoked as service_name.
[ show source ]
# File lib/mindi.rb, line 63 def singleton(name, &impl) # :yields: impl_name = Container.impl_method_name(name) define_implementation(impl_name, impl) ivname = Container.iv(name) define_method(name) do box = instance_variable_get(ivname) box ||= instance_variable_set(ivname, []) box << send(impl_name) if box.empty? box.first end end
Methods which define a service in terms of more complex modalities, such as per-thread uniqueness, and deferred, on-demand existence.
Define a singleton service with deferred instantiation. Syntax and semantics are the same as singleton, except that the block is not called when the service is requested, but only when a method is called on the service.
[ show source ]
# File lib/mindi.rb, line 160 def deferred(name, &impl) # :yields: impl_name = Container.impl_method_name(name) define_implementation(impl_name, impl) proxy_name = Container.impl_method_name("#{name}_proxy") ivname = Container.iv(name) proxy_ivname = Container.iv("#{name}_proxy") define_method(name) do instance_variable_get(ivname) || send(proxy_name) end define_method(proxy_name) do proxy = instance_variable_get(proxy_ivname) unless proxy proxy = proc {instance_variable_set(ivname, send(impl_name))} def proxy.method_missing(*args, &block) call.__send__(*args, &block) end instance_variable_set(proxy_ivname, proxy) class << proxy; self; end.class_eval do (proxy.methods - PROXY_METHODS).each do |m| undef_method m end end end proxy end end
Define a service with per-thread instantiation. For each thread, the service appears to be a singleton service. The block will be called at most once per thread. There is no shortcut. The block may take a single argument, in which case it will be passed the current thread.
threaded(:service_name) { |thr| ... }
[ show source ]
# File lib/mindi.rb, line 139 def threaded(name, &impl) # :yields: thr impl_name = Container.impl_method_name(name) define_implementation(impl_name, impl) arity = impl.arity ivname = Container.iv(name) define_method(name) do key = Thread.current map = instance_variable_get(ivname) map ||= instance_variable_set(ivname, {}) map[key] ||= (arity == 1 ? send(impl_name, key) : send(impl_name)) end end
The name of a method used internally to implement the service named name.
[ show source ]
# File lib/mindi.rb, line 232 def self.impl_method_name(name) # :doc: "___#{name}___implementation" end
The name of an instance variable that stores the state of the service named name.
[ show source ]
# File lib/mindi.rb, line 226 def self.iv(name) # :doc: "@#{name}" end
[ show source ]
# File lib/mindi.rb, line 212 def define_implementation impl_name, impl if @__services_are_injected__ preinject_method = impl_name + "__preinject" define_method(preinject_method, &impl) define_method(impl_name) do |*args| inject_into send(preinject_method, *args) end else define_method(impl_name, &impl) end end