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 +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
|