Class: Wirer::Container

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

Constructor Details

#initialize {|_self| ... } ⇒ Container

Returns a new instance of Container.

Yields:

  • (_self)

Yield Parameters:



15
16
17
18
19
20
21
# File 'lib/wirer/container.rb', line 15

def initialize
  @singleton_factories_instances = {}
  @factories = []
  @factories_by_method_name = {}
  @construction_mutex = Mutex.new
  yield self if block_given?
end

Instance Attribute Details

#factoriesObject (readonly)

Returns the value of attribute factories.



13
14
15
# File 'lib/wirer/container.rb', line 13

def factories
  @factories
end

Instance Method Details

#[](*dep_args) ⇒ Object



142
143
144
145
146
# File 'lib/wirer/container.rb', line 142

def [](*dep_args)
  construction_session do
    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)



41
42
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
# File 'lib/wirer/container.rb', line 41

def add(*add_args, &add_block_arg)
  add_options = if add_args.last.is_a?(Hash) then add_args.pop else {} end

  (add_options[:features] ||= []) << :default if add_options.delete(:default)

  unless add_options.empty? || ADD_OPTION_NAMES.any? {|o| add_options.include?(o)}
    add_options = {:dependencies => add_options}
  end

  if add_args.first.is_a?(Symbol)
    extra_named_feature = add_args.shift
    add_options[:method_name] ||= extra_named_feature
    (add_options[:features] ||= []) << extra_named_feature
  end

  main_arg = add_args.shift
  case main_arg
  when Factory::Interface
    add_options[:args] = add_args unless add_args.empty?
    add_factory(main_arg, add_options, &add_block_arg)
  when ::Module
    add_options[:class] = main_arg
    add_options[:args] = add_args unless add_args.empty?
    add_new_factory(add_options, &add_block_arg)
  when NilClass
    add_new_factory(add_options, &add_block_arg)
  else
    add_instance(main_arg, add_options)
  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.



76
77
78
79
80
81
82
83
84
85
# File 'lib/wirer/container.rb', line 76

def add_factory(factory, options={}, &wrapped_constructor_block)
  add_options = {
    :method_name => options.delete(:method_name),
    :singleton   => options.delete(:singleton)
  }
  unless options.empty? && !wrapped_constructor_block
    factory = factory.wrapped_with(options, &wrapped_constructor_block)
  end
  add_factory_instance(factory, add_options)
end

#add_factory_instance(factory, options = {}) ⇒ Object

Adds a factory object, without any wrapping. only options are :method_name, and :singleton (default true)

Raises:

  • (TypeError)


106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/wirer/container.rb', line 106

def add_factory_instance(factory, options={})
  method_name = options[:method_name]
  singleton = (options[: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



94
95
96
97
98
99
100
101
102
# File 'lib/wirer/container.rb', line 94

def add_instance(instance, options={})
  features = options[: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, options)
end

#add_new_factory(options = {}, &constructor_block) ⇒ Object

Adds a new Factory::FromArgs constructed from the given args.



88
89
90
91
# File 'lib/wirer/container.rb', line 88

def add_new_factory(options={}, &constructor_block)
  factory = Factory::FromArgs.new(options, &constructor_block)
  add_factory_instance(factory, options)
end

#construct_factory_by_method_name(method_name, *args, &block_arg) ⇒ Object



131
132
133
134
135
136
137
138
139
140
# File 'lib/wirer/container.rb', line 131

def construct_factory_by_method_name(method_name, *args, &block_arg)
  factory = @factories_by_method_name[method_name]
  begin
    construction_session do
      construct_factory(factory, *args, &block_arg)
    end
  rescue
    raise DependencyConstructionError.new("Unable to construct factory with name #{method_name}", $!)
  end
end

#factory(name) ⇒ Object



127
128
129
# File 'lib/wirer/container.rb', line 127

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.



161
162
163
164
165
166
167
168
# File 'lib/wirer/container.rb', line 161

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