wirer 0.4.7

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/README.rb ADDED
@@ -0,0 +1,398 @@
1
+ # Wirer
2
+ #
3
+ # A lightweight dependency injection framework to help wire up objects in Ruby.
4
+ #
5
+ # Some usage examples for now:
6
+
7
+ container = Wirer do |c|
8
+
9
+ # SHOWING THE CONTAINER HOW TO CONSTRUCT AN EXISTING CLASS:
10
+
11
+ # This is registered as providing class Logger.
12
+ # It will be constructed via Logger.new('/option_for_logger.txt')
13
+ c.add Logger, '/option_for_logger.txt'
14
+
15
+ # This is registered as providing class Logger, and also providing feature :special_logger
16
+ # which can then be used to request it in particular situations
17
+ c.add :special_logger, Logger, '/special_log.txt'
18
+
19
+ # You can supply a custom block for constructing the dependency if you want;
20
+ # specifying the class upfront means it still knows what class is provided by the block
21
+ c.add(:other_special_logger, Logger) do
22
+ Logger.new(foo, bar, baz)
23
+ end
24
+
25
+ # You don't actually have to specify the class that's provided; it will just
26
+ # provide_class Object by default. In this case you really need to specify
27
+ # a feature name for it, or else you'll have no way to refer to it:
28
+ c.add(:mystery_meat) do
29
+ rand(2) == 0 ? Mystery.new : Meat.new
30
+ end
31
+
32
+ # add_new_factory is the more explicit but verbose way to do this.
33
+ # note in this case you need to specify a :method_name separately if you want a method defined on
34
+ # the container for it.
35
+ c.add_new_factory(:class => Foo, :features => [:foo, :bar], :method_name => :foo) {Foo.new(...)}
36
+ c.add_new_factory(:class => Logger, :features => [:logger], :method_name => :logger, :args => ['/arg_for_logger.txt'])
37
+
38
+
39
+
40
+
41
+ # SPECIFYING DEPENDENCIES (which will then automatically get constructed and passed into your constructor)
42
+
43
+
44
+ # This will be constructed via LogSpewer.new(:logger => logger)
45
+ c.add LogSpewer, :logger => Logger
46
+
47
+ # however since two Loggers are available, we might want to specify
48
+ # a particular one, by making it depend on the feature :special_logger
49
+ # provided by only one of them.
50
+ c.add :special_log_spewer, LogSpewer, :logger => :special_logger
51
+
52
+ # You can specify a combination of class/module and feature name requirements for a
53
+ # dependency:
54
+ c.add :fussy_log_spewer, LogSpewer, :logger => [SpecialLogger, :providing, :these, :features]
55
+
56
+
57
+
58
+ # USING DEFAULTS and PREFERRED FEATURES TO CHOOSE FROM MANY AVAILABLE DEPENDENCIES
59
+
60
+ # If there are many Loggers available, and you have a dependency on a Logger, how does it
61
+ # decide which one to give you?
62
+ #
63
+ # Answer: It will never make an arbitrary choice for you. If there are multiple matching
64
+ # factories and it has no way to differentiate between them, it will raise an error complaining
65
+ # about it and let you fix the issue.
66
+ #
67
+ # You can either refine the dependency to a *particular* logger, as in the example above
68
+ # where we asked for a :special_logger.
69
+ #
70
+ # But it would also be nice if you could nominate one logger as the default to use in the
71
+ # case of multiple loggers, without having to specifically request it in each case:
72
+
73
+ c.add Logger, :default => true
74
+
75
+ # which is shorthand for:
76
+ c.add Logger, :features => [:default]
77
+
78
+
79
+ # this will then be chosen over any other options when resolving a dependency.
80
+ # (if more than one 'default' is available, it will still complain).
81
+
82
+ # Defaults are actually implemented under the hood using 'preferred features'.
83
+ # These are extra feature names in addition to the required features for a dependency,
84
+ # which you'd kinda like if possible but if not then no worries.
85
+ # eg:
86
+
87
+ c.add ColourfulLogSpewer, :logger => {:class => Logger, :prefer => :colour_capable_logger}
88
+
89
+ # If there are multiple matches for the logger dependency here, it will prefer one which
90
+ # provides the :colour_capable_logger feature.
91
+ # (if there are multiple :colour_capable_loggers, it will still complain).
92
+
93
+ # By default, dependencies come with a preferred feature of :default, as though they
94
+ # were constructed via:
95
+ c.add LogSpewer, :logger => {:class => Logger, :prefer => :default}
96
+
97
+ # You can even have multiple preferred_features, in which case it'll try to pick the
98
+ # dependency providing the greatest number of them. However if you need more advanced
99
+ # logic to choose the particular dependency you want
100
+
101
+
102
+
103
+
104
+ # MULTIPLE AND OPTIONAL DEPENDENCIES
105
+
106
+ # intended to be useful for extension points in plugin systems - you can have for example a
107
+ # multiple dependency on 'anything interested in listening to me' or 'anything interested
108
+ # in plugging in to this extension point'.
109
+
110
+ # You can specify cardinality options on dependencies via a longer argument form:
111
+ # by default one and only one dependency is required, but you can make it
112
+ # :multiple to get an array of all matching dependencies.
113
+ # This will be constructed as NoisyLogSpewer.new(:loggers => [logger1, logger2, ...])
114
+ c.add :i_spew_to_all_logs, NoisyLogSpewer, :loggers => {:class => Logger, :multiple => true}
115
+
116
+ # if you don't mind getting a nil if there dependency isn't available, you can make it :optional
117
+ c.add :i_spew_to_a_log_if_present, HesitantLogSpewer, :logger => {:class => Logger, :optional => true}
118
+
119
+ # or maybe you want as many are as available but don't mind if that number is zero:
120
+ # if you don't mind getting a nil if there dependency isn't available, you can make it :optional
121
+ c.add :i_spew_to_what_i_can_get, HesitantLogSpewer, :loggers => {:class => Logger, :multiple => true, :optional => true}
122
+
123
+
124
+
125
+ # a particularly complicated dependency example:
126
+ c.add :complicated, LogSpewer, :loggers => {:class => Logger, :features => [:foo, :bar], :multiple => true, :optional => true}
127
+
128
+
129
+ # CUSTOM ARGS OR CONSTRUCTOR BLOCK
130
+
131
+ # By default, dependencies are passed to the class's new method as a hash argument.
132
+ # you can customize this with a block though:
133
+ c.add(:foo, Foo, :logger => Logger) do |dependencies|
134
+ Foo.new(dependencies[:logger])
135
+ end
136
+
137
+ # And you can specify initial :args which will be passed before the dependencies hash.
138
+ # in this case it'll be constructed as
139
+ # Foo.new('initial arg', :logger => logger)
140
+ c.add(:foo, Foo, 'initial arg', :logger => Logger)
141
+
142
+
143
+
144
+ # If you need to specify any other keyword arguments for the factory, :dependencies need to be supplied separately, eg:
145
+ c.add(:foo, Foo, :dependencies => {:logger => Logger}, :args => ['initial arg'], :features => [:extra, :feature, :names])
146
+
147
+
148
+
149
+
150
+ # SETTER DEPENDENCIES AND TWO-PHASE INITIALIZATION
151
+
152
+ # Sometimes you need depdendencies to be supplied after the object has been constructed.
153
+ # Eg if you need to break a cyclic dependency.
154
+ # These kinds of dependencies can be specified as :setter_dependencies.
155
+ # An example:
156
+
157
+ c.add(Foo, :setter_dependencies => {:bar => Bar})
158
+ c.add(Bar, :setter_dependencies => {:bar => Foo})
159
+
160
+ # this situation will be wired up like so:
161
+ #
162
+ # foo = Foo.new
163
+ # bar = Bar.new
164
+ # foo.send(:bar=, bar)
165
+ # bar.send(:foo=, foo)
166
+ # foo.send(:post_initialize) if foo.respond_to?(:post_initialize)
167
+ # bar.send(:post_initialize) if bar.respond_to?(:post_initialize)
168
+ #
169
+ # Note you can get a post_initialize callback once your entire dependency graph
170
+ # is wired up and ready for action.
171
+ #
172
+ # Note that the setters and post_initialize hook used for this purpose
173
+ # can be private, if you want to limit them only to use by the container
174
+ # during two-phase initialization.
175
+
176
+
177
+ # If you need precise control over two-phase initialization, you can add your own
178
+ # Factory provided it implements Wirer::Factory::Interface.
179
+ #
180
+ # The factory implementation can, if it wants, override the default mechanism for
181
+ # injecting dependencies into instances created from it, and the default mechanism
182
+ # for post_initializing them.
183
+ #
184
+ # It can also make the setter_dependencies requested conditional on the particular
185
+ # instance constructed, which may be useful if they vary depending on arguments to
186
+ # the constructor.
187
+ add_factory_instance(my_custom_factory, :method_name => :my_custom_factory)
188
+
189
+
190
+
191
+ # ADDING AN EXISTING GLOBAL OBJECT
192
+
193
+ # Useful if you're using some (doubtless third-party ;-) library which has
194
+ # hardcoded global state or singletons in a global scope, but you want to add them
195
+ # to your container anyway so they at least appear as modular components for use by
196
+ # other stuff.
197
+
198
+ # this will work provided the global thing is not itself a class or module:
199
+ c.add :naughty_global_state, SomeLibraryWithA::GLOBAL_THINGUMY
200
+
201
+ # or this is more explicit:
202
+ c.add_instance SomeLibraryWithA::GLOBAL_THINGUMY, :method_name => :naughty_global_state
203
+
204
+ # the object will be added as providing the class of which it is an instance,
205
+ # together with any extra feature name or names that you specify.
206
+ # here multiple feature names are specified
207
+ c.add :instance => SomeLibraryWithA::GLOBAL_THINGUMY, :features => [:foo, :bar]
208
+
209
+
210
+
211
+
212
+ # NON-SINGLETON FACTORIES
213
+
214
+ # So far every factory we added to our container has been a singleton in the scope of the container.
215
+ # This is the default and means that the container will only ever construct one instance of it, and
216
+ # will cache that instance.
217
+ #
218
+ # You can turn this off it you want though, via eg:
219
+ c.add :foo, Foo, :singleton => false
220
+
221
+ # The container will then construct a new instance whenever a Foo is required.
222
+ #
223
+ # Factories which are added as singletons can also support arguments, eg:
224
+ # container.foo(args, for, factory)
225
+ #
226
+ # These will then be passed on as additional arguments to the constructor
227
+ # block where you supply one, eg:
228
+ c.add(:foo, Foo, :singleton => false, :dependencies => {:logger => Logger}) do |dependencies, *other_args|
229
+ Foo.new(other_args, dependencies[:logger])
230
+ end
231
+
232
+ # Where you only supply a class, by default they'll be passed as additional
233
+ # arguments to the new method before the dependencies hash.
234
+ # If the last argument is a hash, dependencies will be merged into it. eg:
235
+ #
236
+ c.add(:foo, Foo, :singleton => false, :dependencies => {:logger => Logger})
237
+ # here,
238
+ # c.foo(:other => arg)
239
+ # will lead to
240
+ # Foo.new(:other => arg, :logger => logger)
241
+ # and
242
+ # c.foo(arg1, arg2, :arg3 => 'foo')
243
+ # to
244
+ # Foo.new(arg1, arg2, :arg3 => 'foo', :logger => logger)
245
+ #
246
+ # If you don't like this, just make sure to supply a constructor block.
247
+
248
+
249
+
250
+ # Note that the singleton-ness or otherwise, is not a property of the factory itself, rather
251
+ # it's specific to the context of that factory within a particular container.
252
+
253
+ # Note that when using non-singleton factories, all bets are off when it comes to wiring up
254
+ # object graphs which have cycles in them - since it can't keep constructing new instances
255
+ # all the way down.
256
+ #
257
+ # Similarly, if the same dependency occurs twice in your dependency graph,
258
+ # where a non-singleton factory is used for it, you'll obviously get multiple distinct instances
259
+ # rather than references to one shared instance.
260
+ #
261
+ # I considered providing more fine-grained control over this (eg making things a singleton in the
262
+ # scope of one particular 'construction session', but able to construct new instance for each
263
+ # such construction session) but this is out of scope for now.
264
+ end
265
+
266
+ # GETTING STUFF OUT OF A CONTAINER
267
+
268
+ # Pretty crucial eh!
269
+
270
+ # Things added via 'add' with a symbol method name as the first argument, are made available via
271
+ # corresponding methods on the container:
272
+ container.special_logger
273
+ container.mystery_meat
274
+ # You can also specify this via an explicit :method_name parameter (and in fact you need to
275
+ # specify it this if you use the slightly-lower-level add_factory / add_new_factory / add_instance
276
+ # calls)
277
+
278
+ # You can also ask the container to find any kind of dependency, via
279
+ # passing dependency specification arguments to []:
280
+ container[Logger]
281
+ container[:special_logger]
282
+ container[Logger, :multiple => true]
283
+ container[SomeClass, :and, :some, :features]
284
+ container[SomeModule]
285
+ container[SomeModule, :optional => true]
286
+ # unless you specify :optional => true, it'll whinge if the dependency can't be fulfilled.
287
+
288
+
289
+
290
+ # DSL FOR EXPRESSING DEPENDENCIES FOR A PARTICULAR CLASS
291
+ #
292
+ # This is really handy if you're writing classes which are designed to be
293
+ # components that are ready to be wired up by a Wirer::Container.
294
+ #
295
+ # Using the DSL makes your class instance itself expose Wirer::Factory::Interface,
296
+ # meaning it can be added to a container without having to manually state
297
+ # its dependencies or provided features. The container will 'just know'.
298
+ #
299
+ # (although as we shall see, you can refine the dependencies when adding it to a
300
+ # container, to override the defaults within that particular context if you need to;
301
+ # you can also specify extra provided features within the container context)
302
+
303
+ class Foo
304
+ wireable # extends with the DSL methods (Wirer::Factory::ClassDSL)
305
+
306
+ # Declare some dependencies.
307
+ dependency :logger, Logger
308
+ dependency :arg_name, DesiredClass, :other, :desired, :feature, :names, :optional => true
309
+ dependency :arg_name, :class => DesiredClass, :features => [:desired, :feature, :names], :optional => true
310
+
311
+ # to avoid cyclic load-order dependencies between classes using this DSL, you can specify a class or module
312
+ # name as a string to be resolved later. In this case you need to use the explicit :class => foo args style.
313
+ dependency :something, :class => "Some::Thing"
314
+
315
+ # you can declare extra features which the class factory provides:
316
+ provides_feature :foo, :bar, :baz
317
+
318
+ # and setter dependencies
319
+ setter_dependency :foo, Foo
320
+ # (by default this will also define a private attr_writer :foo for you, which is
321
+ # what it will use by default to inject the dependency)
322
+
323
+ # you can also override the factory methods which the class has been extended with.
324
+ # the most common case would be where you want to customize how instances are
325
+ # constructed from the named dependencies (and any other args), via eg:
326
+ def self.new_from_dependencies(dependencies, *other_args)
327
+ new(dependencies[:foo], dependencies[:bar], *other_args)
328
+ end
329
+
330
+ # you could also add extra instance-specific setter dependencies, eg via:
331
+ def self.setter_dependencies(instance)
332
+ result = super
333
+ result[:extra] = Wirer::Dependency.new(...) if instance.something?
334
+ result
335
+ end
336
+
337
+ # Or to customize the way that setter dependencies are injected:
338
+ def self.inject_dependency(instance, arg_name, dependency)
339
+ instance.instance_variable_set(:"@#{arg_name}", dependency)
340
+ end
341
+ end
342
+
343
+ class Bar < Foo
344
+ # when using Wirer::Factory::ClassDSL, subclasses inherit their superclass's dependencies
345
+ # and features, but you can add new ones:
346
+ dependency :another_thing, Wotsit
347
+ provides_feature :extra
348
+
349
+ # or override existing dependencies
350
+ dependency :logger, :special_logger
351
+
352
+ # or if you don't want this inheritance between the class factory instances of subclasses,
353
+ # you can just extend with Wirer::Factory::ClassMixin instead of using the DSL, or you
354
+ # can override constructor_dependencies / setter_dependencies / provides_features class
355
+ # methods, or both.
356
+ end
357
+
358
+ # Adding these classes into a container is then quite simple:
359
+ Wirer do |c|
360
+
361
+ # It will see that Foo.is_a?(Wirer::Factory::Interface) and add it directly as a factory
362
+ # taking into account its dependencies etc
363
+
364
+ c.add Foo
365
+
366
+ # You can *refine* the dependencies of an existing factory when adding it, eg:
367
+
368
+ c.add Foo, :logger => :special_logger
369
+
370
+ # its original dependency was just on a Logger, but now it's on a Logger which also
371
+ # provides_feature :special_logger.
372
+ #
373
+ # This allows you to customize which particular instance of a given dependency this
374
+ # class gets constructed with. It will be added using a Wirer::Factory::Wrapped around
375
+ # the original factory.
376
+
377
+ # You can also specify extra features when adding a factory, which then give you a handle
378
+ # by which to refer to it when you want it passed to some other thing. Eg to provide the
379
+ # special logger above:
380
+ c.add :special_logger, Logger
381
+
382
+ # or both at once: adding an existing factory with some extra features and some refined
383
+ # dependencies within this container's context.
384
+ c.add Foo, :features => [:special_foo], :dependencies => {:logger => :special_logger}
385
+
386
+ # (if you want to specify other arguments for Factory::Wrapped, the dependency refining
387
+ # arguments need to go in their own :dependencies arg)
388
+
389
+ # then could then eg
390
+ c.add Bar, :foo => :special_foo
391
+
392
+ # If you have an existing factory which takes arguments, you can wrap it with specific
393
+ # (initial) arguments, allowing it to be added as a singleton, eg:
394
+ c.add Foo, 'args', 'for', 'foo', :logger => Logger
395
+
396
+ # or to be more explicit:
397
+ c.add Foo, :args => ['args', 'for', 'foo'], :dependencies => {:logger => Logger}
398
+ end
@@ -0,0 +1,283 @@
1
+ require 'thread'
2
+
3
+ module Wirer
4
+ # A container is a collection of factories, together with logic for constructing
5
+ # instances from these factories in a way that satisfies their dependencies.
6
+ #
7
+ # By default, a Factory acts as a singleton in the context of a Container which
8
+ # it's added to, meaning that only one instance of that factory will be created by
9
+ # the Container. This instance is created lazily and cached within the container.
10
+ #
11
+ # Alternatively if don't want this, specify :singleton => false when adding it.
12
+ class Container
13
+ attr_reader :factories
14
+
15
+ def initialize
16
+ @singleton_factories_instances = {}
17
+ @factories = []
18
+ @factories_by_method_name = {}
19
+ @construction_mutex = Mutex.new
20
+ yield self if block_given?
21
+ end
22
+
23
+ # add Logger
24
+ #
25
+ # add :special_logger, Logger, '/special_logger.txt'
26
+ # add :special_logger, Logger, :args => ['/special_logger.txt']
27
+ # add(:special_logger, Logger) {|deps| Logger.new('/special_logger.txt')}
28
+ #
29
+ # add Thing, :logger => :special_logger
30
+ # add Thing, :logger => :special_logger
31
+
32
+ ADD_OPTION_NAMES = (Factory::Wrapped::OPTION_NAMES | Factory::FromArgs::OPTION_NAMES | [:method_name, :singleton]).freeze
33
+
34
+ # Provides a bunch of different convenient argument styles for adding things
35
+ # to the container.
36
+ #
37
+ # add is effectively syntactic sugar around add_factory, add_instance and add_new_factory;
38
+ # if you prefer a more explicit approach feel free to use these directly.
39
+ #
40
+ # (or if you like it *really* explcit, see add_factory_instance)
41
+ def add(*add_args, &add_block_arg)
42
+ add_options = if add_args.last.is_a?(Hash) then add_args.pop else {} end
43
+
44
+ (add_options[:features] ||= []) << :default if add_options.delete(:default)
45
+
46
+ unless add_options.empty? || ADD_OPTION_NAMES.any? {|o| add_options.include?(o)}
47
+ add_options = {:dependencies => add_options}
48
+ end
49
+
50
+ if add_args.first.is_a?(Symbol)
51
+ extra_named_feature = add_args.shift
52
+ add_options[:method_name] ||= extra_named_feature
53
+ (add_options[:features] ||= []) << extra_named_feature
54
+ end
55
+
56
+ main_arg = add_args.shift
57
+ case main_arg
58
+ when Factory::Interface
59
+ add_options[:args] = add_args unless add_args.empty?
60
+ add_factory(main_arg, add_options, &add_block_arg)
61
+ when ::Module
62
+ add_options[:class] = main_arg
63
+ add_options[:args] = add_args unless add_args.empty?
64
+ add_new_factory(add_options, &add_block_arg)
65
+ when NilClass
66
+ add_new_factory(add_options, &add_block_arg)
67
+ else
68
+ add_instance(main_arg, add_options)
69
+ end
70
+ end
71
+
72
+ # Adds an existing factory.
73
+ #
74
+ # If options for Factory::Wrapped are specified, will wrap the factory with these
75
+ # extra options / overrides prior to adding it.
76
+ def add_factory(factory, options={}, &wrapped_constructor_block)
77
+ add_options = {
78
+ :method_name => options.delete(:method_name),
79
+ :singleton => options.delete(:singleton)
80
+ }
81
+ unless options.empty? && !wrapped_constructor_block
82
+ factory = factory.wrapped_with(options, &wrapped_constructor_block)
83
+ end
84
+ add_factory_instance(factory, add_options)
85
+ end
86
+
87
+ # Adds a new Factory::FromArgs constructed from the given args.
88
+ def add_new_factory(options={}, &constructor_block)
89
+ factory = Factory::FromArgs.new(options, &constructor_block)
90
+ add_factory_instance(factory, options)
91
+ end
92
+
93
+ # Adds an instance wrapped via Factory::FromInstance
94
+ def add_instance(instance, options={})
95
+ features = options[:features]
96
+ factory = case features
97
+ when nil then Factory::FromInstance.new(instance)
98
+ when Array then Factory::FromInstance.new(instance, *features)
99
+ else Factory::FromInstance.new(instance, features)
100
+ end
101
+ add_factory_instance(factory, options)
102
+ end
103
+
104
+ # Adds a factory object, without any wrapping.
105
+ # only options are :method_name, and :singleton (default true)
106
+ def add_factory_instance(factory, options={})
107
+ method_name = options[:method_name]
108
+ singleton = (options[:singleton] != false)
109
+
110
+ raise TypeError, "expected Wirer::Factory::Interface, got #{factory.class}" unless factory.is_a?(Factory::Interface)
111
+ @factories << factory
112
+ @singleton_factories_instances[factory] = nil if singleton
113
+ if method_name
114
+ @factories_by_method_name[method_name] = factory
115
+ if respond_to?(method_name, true)
116
+ warn("Can't add constructor method because #{method_name.inspect} already defined on container")
117
+ else
118
+ instance_eval <<-EOS, __FILE__, __LINE__
119
+ def #{method_name}(*args, &block_arg)
120
+ construct_factory_by_method_name(#{method_name.inspect}, *args, &block_arg)
121
+ end
122
+ EOS
123
+ end
124
+ end
125
+ end
126
+
127
+ def factory(name)
128
+ @factories_by_method_name[name]
129
+ end
130
+
131
+ def construct_factory_by_method_name(method_name, *args, &block_arg)
132
+ factory = @factories_by_method_name[method_name]
133
+ begin
134
+ construction_session do
135
+ construct_factory(factory, *args, &block_arg)
136
+ end
137
+ rescue
138
+ raise DependencyConstructionError.new("Unable to construct factory with name #{method_name}", $!)
139
+ end
140
+ end
141
+
142
+ def [](*dep_args)
143
+ construction_session do
144
+ construct_dependency(Dependency.new_from_args(*dep_args))
145
+ end
146
+ end
147
+
148
+ # Injects (ie monkey-patches) constructor methods into a given object,
149
+ # which delegate to the corresponding constructor methods defined on the
150
+ # container.
151
+ #
152
+ # This is primarily for use as a convenience by the top-level code which is
153
+ # driving an application, to inject application services from a container
154
+ # into the context of (say) an integration test or a driver script.
155
+ #
156
+ # If you're considering using this to supply dependencies to objects
157
+ # *within* your application: instead you would usually want to add that object to
158
+ # the container with dependencies specified, and let the container construct
159
+ # it with the right dependencies. Google for discussion about 'service locator'
160
+ # pattern vs 'dependency injection' / 'IoC container' pattern for more on this.
161
+ def inject_methods_into(instance, *names)
162
+ _self = self
163
+ names.each do |name|
164
+ class << instance; self; end.send(:define_method, name) do |*args|
165
+ _self.construct_factory_by_method_name(name, *args)
166
+ end
167
+ end
168
+ end
169
+
170
+ private
171
+
172
+ # N.B. all calls to the private construct_ methods below must be wrapped with
173
+ # this at the top-level entry point.
174
+ # It wraps with a mutex, to avoid race conditions with concurrent
175
+ # attempts to construct a singleton factory, and also ensures that everything
176
+ # constructed in this session gets post_initialized at the end.
177
+ def construction_session
178
+ @construction_mutex.synchronize do
179
+ begin
180
+ @phase_1_in_progress = []
181
+ @queued_for_phase_2 = []
182
+ @queued_for_post_initialize = []
183
+
184
+ result = yield
185
+
186
+ until @queued_for_phase_2.empty?
187
+ factory_instance = @queued_for_phase_2.pop
188
+ construct_and_inject_setter_dependencies(*factory_instance)
189
+ @queued_for_post_initialize.push(factory_instance)
190
+ end
191
+
192
+ result
193
+ ensure
194
+ post_initialize(@queued_for_post_initialize)
195
+ remove_instance_variable(:@phase_1_in_progress)
196
+ remove_instance_variable(:@queued_for_post_initialize)
197
+ end
198
+ end
199
+ end
200
+
201
+ def construct_dependencies(dependencies)
202
+ result = {}
203
+ dependencies.each do |arg_name, dependency|
204
+ result[arg_name] = construct_dependency(dependency)
205
+ end
206
+ result
207
+ end
208
+
209
+ def construct_dependency(dependency)
210
+ dependency.match_factories(@factories) do |factory|
211
+ if dependency.factory?
212
+ if @singleton_factories_instances.has_key?(factory)
213
+ raise Error, "Problem with :factory => true dependency: #{factory} was added to the container as a singleton, so only a singleton instance can be supplied, not a wrapped factory"
214
+ end
215
+ curry_factory_with_constructed_dependencies(factory)
216
+ else
217
+ construct_factory_without_args(factory)
218
+ end
219
+ end
220
+ end
221
+
222
+ def construct_factory(factory, *args, &block_arg)
223
+ if args.empty? && !block_arg
224
+ construct_factory_without_args(factory)
225
+ else
226
+ construct_factory_with_args(factory, *args, &block_arg)
227
+ end
228
+ end
229
+
230
+ def construct_factory_without_args(factory)
231
+ instance = @singleton_factories_instances[factory] and return instance
232
+
233
+ if @phase_1_in_progress.include?(factory)
234
+ cycle = @phase_1_in_progress[@phase_1_in_progress.index(factory)..-1] + [factory]
235
+ raise CyclicDependencyError, "Cyclic constructor dependencies. Break the cycle by changing some into setter dependencies:\n#{cycle.map(&:inspect).join("\n")}"
236
+ end
237
+ @phase_1_in_progress.push(factory)
238
+ result = construct_with_constructor_dependencies(factory)
239
+ @phase_1_in_progress.pop
240
+
241
+ if @singleton_factories_instances.has_key?(factory)
242
+ @singleton_factories_instances[factory] = result
243
+ end
244
+
245
+ @queued_for_phase_2.push([factory, result])
246
+ result
247
+ end
248
+
249
+ def construct_factory_with_args(factory, *args, &block_arg)
250
+ result = construct_with_constructor_dependencies(factory, *args, &block_arg)
251
+ @queued_for_phase_2.push([factory, result])
252
+ result
253
+ end
254
+
255
+ def construct_with_constructor_dependencies(factory, *args, &block_arg)
256
+ deps = construct_dependencies(factory.constructor_dependencies)
257
+ begin
258
+ factory.new_from_dependencies(deps, *args, &block_arg)
259
+ rescue Wirer::Error
260
+ raise
261
+ rescue => e
262
+ wrapped = DependencyConstructionError.new("Unable to construct factory: #{factory.inspect}", e)
263
+ raise wrapped
264
+ end
265
+ end
266
+
267
+ def curry_factory_with_constructed_dependencies(factory)
268
+ deps = construct_dependencies(factory.constructor_dependencies)
269
+ factory.curry_with_dependencies(deps)
270
+ end
271
+
272
+ def construct_and_inject_setter_dependencies(factory, instance)
273
+ setter_deps = construct_dependencies(factory.setter_dependencies(instance))
274
+ setter_deps.each do |dep_name, dep|
275
+ factory.inject_dependency(instance, dep_name, dep)
276
+ end
277
+ end
278
+
279
+ def post_initialize(factories_instances)
280
+ factories_instances.each {|factory, instance| factory.post_initialize(instance)}
281
+ end
282
+ end
283
+ end