Class: Wirer::Container
- Inherits:
-
Object
- Object
- Wirer::Container
- Defined in:
- lib/wirer/container.rb
Overview
A container is a collection of factories, together with logic for constructing instances from these factories in a way that satisfies their dependencies.
By default, a Factory acts as a singleton in the context of a Container which it’s added to, meaning that only one instance of that factory will be created by the Container. This instance is created lazily and cached within the container.
Alternatively if don’t want this, specify :singleton => false when adding it.
Constant Summary collapse
- ADD_OPTION_NAMES =
add Logger
add :special_logger, Logger, ‘/special_logger.txt’ add :special_logger, Logger, :args => [‘/special_logger.txt’] add(:special_logger, Logger) {|deps| Logger.new(‘/special_logger.txt’)}
add Thing, :logger => :special_logger add Thing, :logger => :special_logger
(Factory::Wrapped::OPTION_NAMES | Factory::FromArgs::OPTION_NAMES | [:method_name, :singleton]).freeze
Instance Attribute Summary collapse
-
#factories ⇒ Object
readonly
Returns the value of attribute factories.
-
#factories_by_method_name ⇒ Object
readonly
Returns the value of attribute factories_by_method_name.
-
#singleton_factories_instances ⇒ Object
readonly
Returns the value of attribute singleton_factories_instances.
Instance Method Summary collapse
- #[](*dep_args) ⇒ Object
-
#add(*add_args, &add_block_arg) ⇒ Object
Provides a bunch of different convenient argument styles for adding things to the container.
-
#add_factory(factory, options = {}, &wrapped_constructor_block) ⇒ Object
Adds an existing factory.
-
#add_factory_instance(factory, options = {}) ⇒ Object
Adds a factory object, without any wrapping.
-
#add_instance(instance, options = {}) ⇒ Object
Adds an instance wrapped via Factory::FromInstance.
-
#add_new_factory(options = {}, &constructor_block) ⇒ Object
Adds a new Factory::FromArgs constructed from the given args.
- #construct_factory_by_method_name(method_name, *args, &block_arg) ⇒ Object
- #factory(name) ⇒ Object
-
#initialize {|_self| ... } ⇒ Container
constructor
A new instance of Container.
-
#inject_methods_into(instance, *names) ⇒ Object
Injects (ie monkey-patches) constructor methods into a given object, which delegate to the corresponding constructor methods defined on the container.
Constructor Details
#initialize {|_self| ... } ⇒ Container
Returns a new instance of Container.
17 18 19 20 21 22 23 |
# File 'lib/wirer/container.rb', line 17 def initialize @singleton_factories_instances = {} @factories = [] @factories_by_method_name = {} @construction_session = ConstructionSession.new(self) yield self if block_given? end |
Instance Attribute Details
#factories ⇒ Object (readonly)
Returns the value of attribute factories.
13 14 15 |
# File 'lib/wirer/container.rb', line 13 def factories @factories end |
#factories_by_method_name ⇒ Object (readonly)
Returns the value of attribute factories_by_method_name.
14 15 16 |
# File 'lib/wirer/container.rb', line 14 def factories_by_method_name @factories_by_method_name end |
#singleton_factories_instances ⇒ Object (readonly)
Returns the value of attribute singleton_factories_instances.
15 16 17 |
# File 'lib/wirer/container.rb', line 15 def singleton_factories_instances @singleton_factories_instances end |
Instance Method Details
#[](*dep_args) ⇒ Object
145 146 147 148 149 |
# File 'lib/wirer/container.rb', line 145 def [](*dep_args) @construction_session.construction_session do @construction_session.construct_dependency(Dependency.new_from_args(*dep_args)) end end |
#add(*add_args, &add_block_arg) ⇒ Object
Provides a bunch of different convenient argument styles for adding things to the container.
add is effectively syntactic sugar around add_factory, add_instance and add_new_factory; if you prefer a more explicit approach feel free to use these directly.
(or if you like it really explcit, see add_factory_instance)
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/wirer/container.rb', line 43 def add(*add_args, &add_block_arg) = if add_args.last.is_a?(Hash) then add_args.pop else {} end ([:features] ||= []) << :default if .delete(:default) unless .empty? || ADD_OPTION_NAMES.any? {|o| .include?(o)} = {:dependencies => } end if add_args.first.is_a?(Symbol) extra_named_feature = add_args.shift [:method_name] ||= extra_named_feature ([:features] ||= []) << extra_named_feature end main_arg = add_args.shift case main_arg when Factory::Interface [:args] = add_args unless add_args.empty? add_factory(main_arg, , &add_block_arg) when ::Module [:class] = main_arg [:args] = add_args unless add_args.empty? add_new_factory(, &add_block_arg) when NilClass add_new_factory(, &add_block_arg) else add_instance(main_arg, ) end end |
#add_factory(factory, options = {}, &wrapped_constructor_block) ⇒ Object
Adds an existing factory.
If options for Factory::Wrapped are specified, will wrap the factory with these extra options / overrides prior to adding it.
78 79 80 81 82 83 84 85 86 87 |
# File 'lib/wirer/container.rb', line 78 def add_factory(factory, ={}, &wrapped_constructor_block) = { :method_name => .delete(:method_name), :singleton => .delete(:singleton) } unless .empty? && !wrapped_constructor_block factory = factory.wrapped_with(, &wrapped_constructor_block) end add_factory_instance(factory, ) end |
#add_factory_instance(factory, options = {}) ⇒ Object
Adds a factory object, without any wrapping. only options are :method_name, and :singleton (default true)
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/wirer/container.rb', line 108 def add_factory_instance(factory, ={}) method_name = [:method_name] singleton = ([:singleton] != false) raise TypeError, "expected Wirer::Factory::Interface, got #{factory.class}" unless factory.is_a?(Factory::Interface) @factories << factory @singleton_factories_instances[factory] = nil if singleton if method_name @factories_by_method_name[method_name] = factory if respond_to?(method_name, true) warn("Can't add constructor method because #{method_name.inspect} already defined on container") else instance_eval <<-EOS, __FILE__, __LINE__ def #{method_name}(*args, &block_arg) construct_factory_by_method_name(#{method_name.inspect}, *args, &block_arg) end EOS end end end |
#add_instance(instance, options = {}) ⇒ Object
Adds an instance wrapped via Factory::FromInstance
96 97 98 99 100 101 102 103 104 |
# File 'lib/wirer/container.rb', line 96 def add_instance(instance, ={}) features = [:features] factory = case features when nil then Factory::FromInstance.new(instance) when Array then Factory::FromInstance.new(instance, *features) else Factory::FromInstance.new(instance, features) end add_factory_instance(factory, ) end |
#add_new_factory(options = {}, &constructor_block) ⇒ Object
Adds a new Factory::FromArgs constructed from the given args.
90 91 92 93 |
# File 'lib/wirer/container.rb', line 90 def add_new_factory(={}, &constructor_block) factory = Factory::FromArgs.new(, &constructor_block) add_factory_instance(factory, ) end |
#construct_factory_by_method_name(method_name, *args, &block_arg) ⇒ Object
133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/wirer/container.rb', line 133 def construct_factory_by_method_name(method_name, *args, &block_arg) factory = @factories_by_method_name[method_name] begin @construction_session.construction_session do @construction_session.construct_factory(factory, *args, &block_arg) end rescue => exception exception.backtrace raise DependencyConstructionError.new("Unable to construct factory with name #{method_name}", $!) end end |
#factory(name) ⇒ Object
129 130 131 |
# File 'lib/wirer/container.rb', line 129 def factory(name) @factories_by_method_name[name] end |
#inject_methods_into(instance, *names) ⇒ Object
Injects (ie monkey-patches) constructor methods into a given object, which delegate to the corresponding constructor methods defined on the container.
This is primarily for use as a convenience by the top-level code which is driving an application, to inject application services from a container into the context of (say) an integration test or a driver script.
If you’re considering using this to supply dependencies to objects within your application: instead you would usually want to add that object to the container with dependencies specified, and let the container construct it with the right dependencies. Google for discussion about ‘service locator’ pattern vs ‘dependency injection’ / ‘IoC container’ pattern for more on this.
164 165 166 167 168 169 170 171 |
# File 'lib/wirer/container.rb', line 164 def inject_methods_into(instance, *names) _self = self names.each do |name| class << instance; self; end.send(:define_method, name) do |*args| _self.construct_factory_by_method_name(name, *args) end end end |