wirer 0.4.7
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rb +398 -0
- data/lib/wirer/container.rb +283 -0
- data/lib/wirer/dependency.rb +234 -0
- data/lib/wirer/errors.rb +30 -0
- data/lib/wirer/factory/class_mixins.rb +117 -0
- data/lib/wirer/factory/curried_dependencies.rb +33 -0
- data/lib/wirer/factory/from_args.rb +100 -0
- data/lib/wirer/factory/from_instance.rb +24 -0
- data/lib/wirer/factory/interface.rb +114 -0
- data/lib/wirer/factory/wrapped.rb +94 -0
- data/lib/wirer/service.rb +51 -0
- data/lib/wirer/version.rb +3 -0
- data/lib/wirer.rb +20 -0
- metadata +139 -0
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
|