mindi 0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,54 @@
1
+ require 'mindi'
2
+
3
+ class Transformer
4
+ def transform string
5
+ string.gsub(pattern, &replacement)
6
+ end
7
+ end
8
+
9
+ class TransformerContainer
10
+ include MinDI::InjectableContainer
11
+
12
+ pattern { /foo/ }
13
+ replacement { proc {|match| match.upcase } }
14
+ transformer { Transformer.new }
15
+ transform { |str| transformer.transform(str) }
16
+ end
17
+
18
+ cont = TransformerContainer.new
19
+ s1 = cont.transform("fo foo fee")
20
+ s2 = cont.transform("fo foo fee")
21
+ p s1 # ==> "fo FOO fee"
22
+ p s1.equal?(s2) # ==> true
23
+
24
+ __END__
25
+
26
+ Here's what happens when the service is instantiated (i.e. the "{
27
+ Transformer.new }" block is called in response to the #transformer
28
+ method on the container):
29
+
30
+ The service object (the Transformer instance returned by the block) is
31
+ given an instance variable, @__injectable__object__, whose value is set
32
+ to the container.
33
+
34
+ Also, the service object is extended, but only with a fairly minimal
35
+ module called Injected:
36
+
37
+ module Injected
38
+ def method_missing(*args, &block)
39
+ @__injectable__object__ || super
40
+ @__injectable__object__.send(*args, &block)
41
+ rescue NoInjectedMethodError
42
+ super
43
+ end
44
+ end
45
+
46
+ The NoInjectedMethodError is raised by the container's own
47
+ method_missing. So the method search order for the service object is:
48
+
49
+ 1. the service object itself (the Transformer instance)
50
+
51
+ 2. other services in the container (this is how "replacement" and
52
+ "pattern" are resolved)
53
+
54
+ 3. method_missing, as defined in the ancestry of the service object
@@ -0,0 +1,14 @@
1
+
2
+ # class ContainerWithDRbObject
3
+ # extend MinDI::Container
4
+ #
5
+ # server { #"druby://localhost:9000" }
6
+ # require 'drb'
7
+ ## DRb.start_service(nil)
8
+ # DRbObject.new(nil, "druby://localhost:9000")
9
+ # }
10
+ # end
11
+ #
12
+ # cwdo = ContainerWithDRbObject.new
13
+ # p cwdo.server
14
+ # cwdo.server.foo
@@ -0,0 +1,12 @@
1
+ require 'mindi'
2
+
3
+ class C
4
+ include MinDI::InjectableContainer
5
+
6
+ # uninjected
7
+ foo { |x,y| (x + y).to_s }
8
+ end
9
+
10
+ cont = C.new
11
+
12
+ p cont.foo(1,2).foo(2,3)
@@ -0,0 +1,58 @@
1
+ # a make/rake type tool using MinDI
2
+
3
+ require 'mindi'
4
+
5
+ module Mink
6
+ include MinDI::Container
7
+
8
+ # def task(t, &bl)
9
+ # task_impl = "task_#{t}"
10
+ #
11
+ # case bl.arity
12
+ # when 0, -1
13
+ # define_method(task_impl, &bl)
14
+ # singleton(t) do
15
+ # ##ivname = Container.iv(name)
16
+ # ##if instance_variable_get(ivname)
17
+ # ## raise if value is :in_progress
18
+ # ## set value to :in_progress
19
+ # result = send(task_impl)
20
+ # result || true
21
+ # end
22
+ #
23
+ # when 1
24
+ # define_method(task_impl, &bl)
25
+ # multiton(t) do |arg|
26
+ # ##ivname = Container.iv(name)
27
+ # ##if instance_variable_get(ivname)
28
+ # ## raise if value is :in_progress
29
+ # ## set value to :in_progress
30
+ # result = send(task_impl, arg)
31
+ # result || true
32
+ # end
33
+ #
34
+ # else
35
+ # raise "arity == #{bl.arity}"
36
+ # end
37
+ end
38
+ end
39
+
40
+ class C
41
+ extend Mink
42
+
43
+ # print each string at most once
44
+ output do |s| puts s end
45
+
46
+ z do output "bar" end
47
+ a do z; output "bar" end
48
+ b do a end
49
+ c do a end
50
+ d do b; c; "d is done" end
51
+
52
+ xa { puts "xa" }
53
+ xb { xa; puts "xb" }
54
+ xc { xa; puts "xc" }
55
+ xd { xb; xc; puts "xd" }
56
+ end
57
+
58
+ p C.new.xd
data/lib/mindi.rb ADDED
@@ -0,0 +1,383 @@
1
+ module MinDI # :nodoc:
2
+ # Extend a class (or module) with Container to make the class behave as a
3
+ # class of service containers. Containers encapsulate the way in which related
4
+ # services are instantiated and connected together. Services themselves are
5
+ # insulated from this process [except when they are injected with the
6
+ # container].
7
+ #
8
+ # Use MinDI by <b><tt>include</tt></b>ing the MinDI::InjectableContainer
9
+ # module in your container class. As of version 0.2, this does two things: it
10
+ # <b><tt>extend</tt></b>s your container class with the Container module,
11
+ # which provides class methods for expressing service definitions in a
12
+ # natural, rubylike way. It also injects the container into all services which
13
+ # gives them direct access to the other services in the container. If you
14
+ # prefer to have the container funtionality without injecting services (i.e.,
15
+ # just "contructor injection") You can just include MinDI::BasicContainer.
16
+ #
17
+ # The Container module also defines shortcuts for defining some service types
18
+ # using +method_missing+. These methods and shortcuts are described below.
19
+ #
20
+ # A service definition involves a service name and a block of code. It defines
21
+ # an instance method whose name is the service name. The block is used to
22
+ # instantiate the service. When and how that service is instantiated depends
23
+ # on the type of service.
24
+ #
25
+ # <i>Note:</i> MinDI uses some instance variables and auxiliary instance
26
+ # methods in the container. The instance variables have the same name as the
27
+ # corresponding services, possibly with added suffixes, as in the case of
28
+ # #deferred. The auxiliary methods are named so as to make conflicts unlikely.
29
+ # See the implemetations of #iv and #impl_method_name for details.
30
+ #
31
+ # Note that services can be defined dynamically by reopening the class scope
32
+ # or simply by calling the service with a block. See examples/dynamic.rb.
33
+
34
+ VERSION = '0.5'
35
+
36
+ module Container
37
+
38
+ # ------------------------
39
+ # :section: Basic services
40
+ #
41
+ # Methods which define a service in terms of simple rules involving
42
+ # existence, uniqeness, and parameters.
43
+ # ------------------------
44
+
45
+ # Define a generic service, which has no built-in rules for existence or
46
+ # uniqueness. There is no shortcut for generic service definition. Calling
47
+ # the service simply calls the associated block. This is also known as a
48
+ # _prototype_ service.
49
+
50
+ def generic(name, &impl) # :yields:
51
+ define_implementation(name, impl)
52
+ end
53
+
54
+ # Define a singleton service:
55
+ #
56
+ # singleton(:service_name) { ... }
57
+ #
58
+ # The block will be called at most once to instantiate a unique value
59
+ # for the service. The shortcut for defining a singleton is:
60
+ #
61
+ # service_name { ... }
62
+ #
63
+ # The service is invoked as <tt>service_name</tt>.
64
+
65
+ def singleton(name, &impl) # :yields:
66
+ impl_name = Container.impl_method_name(name)
67
+ define_implementation(impl_name, impl)
68
+
69
+ ivname = Container.iv(name)
70
+ define_method(name) do
71
+ box = instance_variable_get(ivname)
72
+ box ||= instance_variable_set(ivname, [])
73
+ box << send(impl_name) if box.empty?
74
+ box.first
75
+ end
76
+ end
77
+
78
+ # Define a multiton service:
79
+ #
80
+ # multiton(:service_name) { |arg| ... }
81
+ #
82
+ # The block will be called once per distinct (in the sense of hash keys)
83
+ # argument to instantiate a unique value corresponding to the argument. The
84
+ # shortcut for defining a multiton is:
85
+ #
86
+ # service_name { |arg| ... }
87
+ #
88
+ # The service is invoked as <tt>service_name(arg)</tt>.
89
+
90
+ def multiton(name, &impl) # :yields: arg
91
+ impl_name = Container.impl_method_name(name)
92
+ define_implementation(impl_name, impl)
93
+
94
+ ivname = Container.iv(name)
95
+ define_method(name) do |key|
96
+ map = instance_variable_get(ivname)
97
+ map ||= instance_variable_set(ivname, {})
98
+ map.key?(key) ? map[key] : map[key] = send(impl_name, key)
99
+ end
100
+ end
101
+
102
+ # Define a multiton service with multiple keys:
103
+ #
104
+ # multiton(:service_name) { |arg0, arg1, ...| ... }
105
+ #
106
+ # The block will be called once per distinct (in the sense of hash keys)
107
+ # argument list to instantiate a unique value corresponding to the argument
108
+ # list. The shortcut for defining a multikey_multiton with multiple keys is:
109
+ #
110
+ # service_name { |arg0, arg1, ...| ... }
111
+ #
112
+ # The service is invoked as <tt>service_name(arg0, arg1, ...)</tt>.
113
+ # Variable length argument lists, using the splat notation, are permitted.
114
+
115
+ def multikey_multiton(name, &impl) # :yields: arg0, arg1, ...
116
+ impl_name = Container.impl_method_name(name)
117
+ define_implementation(impl_name, impl)
118
+
119
+ ivname = Container.iv(name)
120
+ define_method(name) do |*key|
121
+ map = instance_variable_get(ivname)
122
+ map ||= instance_variable_set(ivname, {})
123
+ map.key?(key) ? map[key] : map[key] = send(impl_name, *key)
124
+ end
125
+ end
126
+
127
+ # ---------------------------
128
+ # :section: Advanced services
129
+ #
130
+ # Methods which define a service in terms of more complex modalities, such
131
+ # as per-thread uniqueness, and deferred, on-demand existence.
132
+ # ---------------------------
133
+
134
+ # Define a service with per-thread instantiation. For each thread, the
135
+ # service appears to be a singleton service. The block will be called at
136
+ # most once per thread. There is no shortcut. The block may take a single
137
+ # argument, in which case it will be passed the current thread.
138
+ #
139
+ # threaded(:service_name) { |thr| ... }
140
+
141
+ def threaded(name, &impl) # :yields: thr
142
+ impl_name = Container.impl_method_name(name)
143
+ define_implementation(impl_name, impl)
144
+ arity = impl.arity
145
+
146
+ ivname = Container.iv(name)
147
+ define_method(name) do
148
+ key = Thread.current
149
+ map = instance_variable_get(ivname)
150
+ map ||= instance_variable_set(ivname, {})
151
+ map[key] ||= (arity == 1 ? send(impl_name, key) : send(impl_name))
152
+ end
153
+ end
154
+
155
+ PROXY_METHODS = ["__send__", "__id__", "method_missing", "call"] # :nodoc:
156
+
157
+ # Define a singleton service with deferred instantiation. Syntax and
158
+ # semantics are the same as #singleton, except that the block is not called
159
+ # when the service is requested, but only when a method is called on the
160
+ # service.
161
+
162
+ def deferred(name, &impl) # :yields:
163
+ impl_name = Container.impl_method_name(name)
164
+ define_implementation(impl_name, impl)
165
+
166
+ proxy_name = Container.impl_method_name("#{name}_proxy")
167
+
168
+ ivname = Container.iv(name)
169
+ proxy_ivname = Container.iv("#{name}_proxy")
170
+
171
+ define_method(name) do
172
+ instance_variable_get(ivname) || send(proxy_name)
173
+ end
174
+
175
+ define_method(proxy_name) do
176
+ proxy = instance_variable_get(proxy_ivname)
177
+
178
+ unless proxy
179
+ proxy = proc {instance_variable_set(ivname, send(impl_name))}
180
+ def proxy.method_missing(*args, &block)
181
+ call.__send__(*args, &block)
182
+ end
183
+ instance_variable_set(proxy_ivname, proxy)
184
+ class << proxy; self; end.class_eval do
185
+ (proxy.methods - PROXY_METHODS).each do |m|
186
+ undef_method m
187
+ end
188
+ end
189
+ end
190
+
191
+ proxy
192
+ end
193
+ end
194
+
195
+ # ---------------------------
196
+ # :section: Internal methods
197
+ # ---------------------------
198
+
199
+ # For declarative style container definitions ("shortcuts").
200
+ def method_missing(meth, *args, &bl) # :nodoc:
201
+ super unless bl
202
+
203
+ case bl.arity
204
+ when 0
205
+ singleton(meth, *args, &bl)
206
+ when 1
207
+ multiton(meth, *args, &bl)
208
+ else
209
+ # note that this includes the case of a block with _no_ args, i.e.
210
+ # { value }, which has arity -1, indistinguishabe from {|*args|}.
211
+ multikey_multiton(meth, *args, &bl)
212
+ end
213
+ end
214
+
215
+ protected
216
+ def define_implementation impl_name, impl
217
+ if @__services_are_injected__
218
+ preinject_method = impl_name + "__preinject"
219
+ define_method(preinject_method, &impl)
220
+ define_method(impl_name) do |*args|
221
+ inject_into send(preinject_method, *args)
222
+ end
223
+ else
224
+ define_method(impl_name, &impl)
225
+ end
226
+ end
227
+
228
+ # The name of an instance variable that stores the state of the service
229
+ # named _name_.
230
+ def self.iv(name) # :doc:
231
+ "@___#{name}___value"
232
+ end
233
+
234
+ # The name of a method used internally to implement the service named
235
+ # _name_.
236
+ def self.impl_method_name(name) # :doc:
237
+ "___#{name}___implementation"
238
+ end
239
+
240
+ end
241
+
242
+ # Include this in a container class to allow use of #inject_into within
243
+ # services. See examples/inject.rb. Including this module also extends the
244
+ # class with the Container module, so a simple shortcut to making a fully
245
+ # functional injectable container is to simply include Injectable.
246
+ #
247
+ # This module can be used outside of the context of MinDI::Container: almost
248
+ # any object can be injected into any other. For example:
249
+ #
250
+ # x = [1,2,3]
251
+ # y = {}
252
+ # x.extend MinDI::Injectable
253
+ # x.inject_into y
254
+ # p y.reverse => [3, 2, 1]
255
+ #
256
+ # Note that injecting an Injectable object into another object never
257
+ # interferes with methods of the latter object.
258
+ #
259
+ # Note that injected methods operate on the injecting instance, not the
260
+ # injectee. So instance variables...
261
+ #
262
+ # Note similarity to Forwardable and Delegator in the stdlib. However,
263
+ # Injectable is more passive in that (as noted above) it only affects the
264
+ # handling of methods that are _missing_ in the target object. In that
265
+ # respect, Injectable is a little like inheritance (except as noted above
266
+ # about which instance is operated on).
267
+ module Injectable
268
+ # Raised and rescued internally to pass _method_missing_ back from container
269
+ # to service object. Client code should not have to handle this exception.
270
+ class NoInjectedMethodError < StandardError; end
271
+
272
+ # Raised on attempt to add object to a second container.
273
+ class NonUniqueContainerError < StandardError; end
274
+
275
+ # Internally used to extend service objects so that they can delegate sevice
276
+ # requests to their container.
277
+ #
278
+ # This module can be included explicitly in the class of the service
279
+ # objects. For most purposes there is no reason to do so. However, if an
280
+ # object is dumped and re-loaded with YAML, it will lose track of modules
281
+ # that it has been extended with. By including the module in the class, the
282
+ # loaded object will have the right ancestors. (Marshal does not have this
283
+ # limitation.)
284
+ #
285
+ # An object can be injected with at most one Injectable object at a time.
286
+ #
287
+ # The implementation of Injected is essentially <tt>extend</tt>-ing with a
288
+ # module that has a <tt>method_missing</tt>.
289
+ module Injected
290
+ # Delegates to the Injectable object any method which it must handle. If
291
+ # the Injectable object does not handle the method, or if there is no
292
+ # Injectable object assigned to self, then self's own _method_missing_ is
293
+ # called.
294
+ def method_missing(*args, &block)
295
+ @__injectable__object__ || super
296
+ @__injectable__object__.send(*args, &block)
297
+ rescue NoInjectedMethodError
298
+ super
299
+ end
300
+ end
301
+
302
+ # Inject the container's services into _obj_. The service methods can be
303
+ # called from within the object's methods, and they will return the same
304
+ # objects as if they were called from the container.
305
+ #
306
+ # Returns _obj_, so that the method can be called within service
307
+ # definitions.
308
+ #
309
+ def inject_into obj
310
+ begin
311
+ obj.extend Injected
312
+ rescue TypeError
313
+ warn "#{caller[2]}: warning: class #{obj.class} cannot be injected into"
314
+ return obj
315
+ end
316
+
317
+ cont = obj.instance_variable_get(:@__injectable__object__)
318
+ if cont and cont != self
319
+ raise NonUniqueContainerError,
320
+ "Object #{obj.inspect} already belongs to #{cont.inspect}." +
321
+ " Was attempting to inject #{self.inspect} into it."
322
+ end
323
+
324
+ obj.instance_variable_set(:@__injectable__object__, self)
325
+ obj
326
+ end
327
+
328
+ def method_missing(m, *rest) # :nodoc:
329
+ raise NoInjectedMethodError
330
+ end
331
+ end
332
+
333
+ # For convenience, so that you can use 'include MinDI::BasicContainer' rather
334
+ # than 'extend MinDI::Container'.
335
+ module BasicContainer
336
+ def self.included mod # :nodoc:
337
+ mod.extend Container
338
+ end
339
+ end
340
+
341
+ # An InjectableContainer "injects" itself into the services, so that they
342
+ # can all refer to each other.
343
+ #
344
+ # Including this module has the combined effect of making the class act as a
345
+ # container (by *extend*-ing it with Container) and making the class
346
+ # Injectable (by *include*-ing Injectable). Additionally, including this
347
+ # module defines two class methods #injected and #uninjected, which determine
348
+ # whether subsequently defined services have the container injected into them.
349
+ # (The default is #injected.)
350
+ #
351
+ # Also, the module defines an #inspect method, so that inspecting injected
352
+ # objects doesn't dump the entire container and all of its services.
353
+ #
354
+ module InjectableContainer
355
+ include Injectable
356
+
357
+ # These methods are available in the scope of your class definition, after
358
+ # you have included MinDI::InjectableContainer.
359
+ module ClassMethods
360
+ # Set state so subsequently defined services have the container injected
361
+ # into them when they are instantiated. (This is the default state.)
362
+ def injected
363
+ @__services_are_injected__ = true
364
+ end
365
+
366
+ # Set state so subsequently defined services do not have the container
367
+ # injected into them when they are instantiated.
368
+ def uninjected
369
+ @__services_are_injected__ = false
370
+ end
371
+ end
372
+
373
+ def self.included mod # :nodoc:
374
+ mod.extend Container
375
+ mod.extend InjectableContainer::ClassMethods
376
+ mod.injected
377
+ end
378
+
379
+ def inspect # :nodoc:
380
+ "<#{self.class}:0x%0x>" % object_id
381
+ end
382
+ end
383
+ end