mindi 0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/COPYING +22 -0
- data/README.md +174 -0
- data/RELEASE-NOTES +26 -0
- data/Rakefile +64 -0
- data/examples/JW-app-cont-injected.rb +36 -0
- data/examples/JW-app-cont.rb +37 -0
- data/examples/coffee.rb +77 -0
- data/examples/color-namespace.rb +36 -0
- data/examples/duck.rb +66 -0
- data/examples/dynamic.rb +24 -0
- data/examples/game.rb +127 -0
- data/examples/inject.rb +141 -0
- data/examples/rake-alike.rb +13 -0
- data/examples/simple.rb +15 -0
- data/examples/test.rb +117 -0
- data/examples/transform.rb +54 -0
- data/examples/wip/drb-example.rb +14 -0
- data/examples/wip/inj-param.rb +12 -0
- data/examples/wip/mink.rb +58 -0
- data/lib/mindi.rb +383 -0
- data/test/test-injectable-container.rb +5 -0
- data/test/test-injectable.rb +48 -0
- metadata +78 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
# Namespace example
|
2
|
+
|
3
|
+
require 'mindi'
|
4
|
+
|
5
|
+
class PictureApp
|
6
|
+
include MinDI::InjectableContainer
|
7
|
+
|
8
|
+
class Picture
|
9
|
+
attr_reader :opts
|
10
|
+
def initialize(opts)
|
11
|
+
@opts = opts
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Color < Struct.new(:r, :g, :b)
|
16
|
+
def +(color)
|
17
|
+
Color.new(r + color.r, g + color.g, b + color.b)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class ColorNamespace
|
22
|
+
include MinDI::InjectableContainer
|
23
|
+
|
24
|
+
red { Color.new(1,0,0) }
|
25
|
+
green { Color.new(0,1,0) }
|
26
|
+
yellow { red + green }
|
27
|
+
end
|
28
|
+
|
29
|
+
colors { ColorNamespace.new }
|
30
|
+
picture { Picture.new(:background => colors.yellow) }
|
31
|
+
end
|
32
|
+
|
33
|
+
pic_app = PictureApp.new
|
34
|
+
pic = pic_app.picture
|
35
|
+
raise unless pic.opts[:background] == pic_app.colors.yellow
|
36
|
+
|
data/examples/duck.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# This is the basic idea of Injectable, without MinDI around it.
|
2
|
+
|
3
|
+
module Injectable
|
4
|
+
module Injected
|
5
|
+
def method_missing(*args, &block)
|
6
|
+
@__injectable__container__ || super
|
7
|
+
@__injectable__container__.send(*args, &block)
|
8
|
+
rescue NoInjectedMethodError
|
9
|
+
super
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def inject_into obj
|
14
|
+
obj.extend Injected
|
15
|
+
obj.instance_variable_set(:@__injectable__container__, self)
|
16
|
+
obj
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(m, *rest)
|
20
|
+
raise NoInjectedMethodError
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Duck
|
25
|
+
include Injectable
|
26
|
+
|
27
|
+
def quack; @quack_behavior.quack; end
|
28
|
+
def waddle; @waddle_behavior.waddle; end
|
29
|
+
|
30
|
+
def initialize(h)
|
31
|
+
@quack_behavior = h[:quack_behavior]
|
32
|
+
@waddle_behavior = h[:waddle_behavior]
|
33
|
+
|
34
|
+
inject_into @quack_behavior
|
35
|
+
inject_into @waddle_behavior
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class StandardQuacker
|
40
|
+
def quack
|
41
|
+
puts "QUACK!"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class NoisyWaddler
|
46
|
+
def waddle
|
47
|
+
quack # note that this propagates to Duck then to quacker
|
48
|
+
puts "<waddle>"
|
49
|
+
quack
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
duck = Duck.new(
|
54
|
+
:quack_behavior => StandardQuacker.new,
|
55
|
+
:waddle_behavior => NoisyWaddler.new
|
56
|
+
)
|
57
|
+
|
58
|
+
duck.waddle
|
59
|
+
|
60
|
+
__END__
|
61
|
+
|
62
|
+
Output:
|
63
|
+
|
64
|
+
QUACK!
|
65
|
+
<waddle>
|
66
|
+
QUACK!
|
data/examples/dynamic.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'mindi'
|
2
|
+
|
3
|
+
# Shows how to register services dynamically; this is usually done
|
4
|
+
# with a "register" method in other DI frameworks, but we use method
|
5
|
+
# definition, because that can be done dynamically in ruby.
|
6
|
+
|
7
|
+
class DynamicContainer
|
8
|
+
include MinDI::InjectableContainer
|
9
|
+
|
10
|
+
stuff { [foo, bar] } # foo and bar not defined yet
|
11
|
+
end
|
12
|
+
|
13
|
+
cont = DynamicContainer.new
|
14
|
+
|
15
|
+
# Add a service to DynamicContainer at service point foo.
|
16
|
+
DynamicContainer.foo {"FOO"}
|
17
|
+
p cont.foo # ==> "FOO"
|
18
|
+
|
19
|
+
# Add a service just to the instance cont at service point bar.
|
20
|
+
class << cont; bar { "BAR" }; end
|
21
|
+
p cont.bar # ==> "BAR"
|
22
|
+
|
23
|
+
# Now #stuff will work,
|
24
|
+
p cont.stuff # ==> ["FOO", "BAR"]
|
data/examples/game.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'mindi'
|
2
|
+
|
3
|
+
Parser = Struct.new :game # add parser state vars here
|
4
|
+
Player = Struct.new :game, :location, :contents
|
5
|
+
World = Struct.new :game # add state vars (:time, maybe)
|
6
|
+
Map = Struct.new :game
|
7
|
+
# The :game attr is useful if you want to write methods of
|
8
|
+
# these classes (Player for example) that refer to the other
|
9
|
+
# game objects. But it's not strictly necessary.
|
10
|
+
|
11
|
+
Room = Struct.new :name, :contents
|
12
|
+
|
13
|
+
# Sometimes, a struct is not enough...
|
14
|
+
class Thing
|
15
|
+
attr_accessor :name
|
16
|
+
attr_reader :location
|
17
|
+
|
18
|
+
def initialize name, location = nil
|
19
|
+
@name = name
|
20
|
+
@location = location
|
21
|
+
end
|
22
|
+
|
23
|
+
# Moving a Thing updates the Room it's in or Player who has it
|
24
|
+
def location=(loc)
|
25
|
+
@location.contents.delete self if @location
|
26
|
+
@location = loc
|
27
|
+
@location.contents << self if @location
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class GameContainer
|
32
|
+
include MinDI::InjectableContainer
|
33
|
+
|
34
|
+
# These are some singletons--one instance each.
|
35
|
+
parser { Parser.new self }
|
36
|
+
player { Player.new self, start_room, [] }
|
37
|
+
world { World.new self }
|
38
|
+
map { Map.new self }
|
39
|
+
|
40
|
+
# A multiton.
|
41
|
+
# The |name| means there can be many things--one for each name.
|
42
|
+
# internally, there is a hash stored in @thing that maps
|
43
|
+
# each name strign to a Thing.
|
44
|
+
thing { |name| Thing.new name }
|
45
|
+
|
46
|
+
# The shovel (unique with that name).
|
47
|
+
shovel { thing "Shovel" }
|
48
|
+
|
49
|
+
room { |name| Room.new name, [] }
|
50
|
+
|
51
|
+
start_room { room "garden" }
|
52
|
+
|
53
|
+
# Set up some initial conditions
|
54
|
+
def initialize
|
55
|
+
# create and locate the shovel
|
56
|
+
shovel.location = start_room
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
game = GameContainer.new
|
61
|
+
|
62
|
+
# The shovel is already defined:
|
63
|
+
p game.shovel
|
64
|
+
puts
|
65
|
+
|
66
|
+
# Create a new thing:
|
67
|
+
ball = game.thing("ball")
|
68
|
+
ball.location = game.room "basement"
|
69
|
+
p ball
|
70
|
+
puts
|
71
|
+
|
72
|
+
player = game.player
|
73
|
+
|
74
|
+
# pick up the shovel and anything else in the start room
|
75
|
+
# This could be made into a #pick_up method of Game or of Player
|
76
|
+
player.location.contents.each { |thing| thing.location = player }
|
77
|
+
p player.contents.map {|thing| thing.name}
|
78
|
+
puts
|
79
|
+
|
80
|
+
# move the player
|
81
|
+
p player.location.name
|
82
|
+
player.location = game.room "basement"
|
83
|
+
p player.location.name
|
84
|
+
puts
|
85
|
+
|
86
|
+
# get the ball
|
87
|
+
player.location.contents.each { |thing| thing.location = player }
|
88
|
+
p player.contents.map {|thing| thing.name}
|
89
|
+
puts
|
90
|
+
|
91
|
+
# You can define new methods on the GameContainer that access the internal
|
92
|
+
# storage used by the "service" methods.
|
93
|
+
class GameContainer
|
94
|
+
def rooms
|
95
|
+
instance_variable_get(MinDI::Container.iv("room")).values
|
96
|
+
# because internally the "room" service uses a hash stored in
|
97
|
+
# an instance var with a munged name
|
98
|
+
end
|
99
|
+
|
100
|
+
def things
|
101
|
+
instance_variable_get(MinDI::Container.iv("thing")).values
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Show all the rooms
|
106
|
+
p game.rooms
|
107
|
+
|
108
|
+
# Show all the things
|
109
|
+
p game.things.map {|thing| thing.name}
|
110
|
+
|
111
|
+
__END__
|
112
|
+
|
113
|
+
Output:
|
114
|
+
|
115
|
+
#<Thing:0xb7d862fc @name="Shovel", @location=#<struct Room name="garden", contents=[#<Thing:0xb7d862fc ...>]>>
|
116
|
+
|
117
|
+
#<Thing:0xb7d85078 @name="ball", @location=#<struct Room name="basement", contents=[#<Thing:0xb7d85078 ...>]>>
|
118
|
+
|
119
|
+
["Shovel"]
|
120
|
+
|
121
|
+
"garden"
|
122
|
+
"basement"
|
123
|
+
|
124
|
+
["Shovel", "ball"]
|
125
|
+
|
126
|
+
[#<struct Room name="garden", contents=[]>, #<struct Room name="basement", contents=[]>]
|
127
|
+
["ball", "Shovel"]
|
data/examples/inject.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'mindi'
|
2
|
+
|
3
|
+
#---------------
|
4
|
+
# Example showing how to use #inject_into to give a service object direct access
|
5
|
+
# to other services in the container, without having to explicitly reference
|
6
|
+
# the container object. See the GenericCopier#copy method and the #copier
|
7
|
+
# service. This further reduces the dependence of service classes on the
|
8
|
+
# structure of the container, since the initialize method of GenericCopier
|
9
|
+
# doesn't need to know about reader and writer (in fact you don't even need to
|
10
|
+
# write an initialize method in this case). The GenericCopier class can be
|
11
|
+
# tested with a mock reader and a mock writer by adding reader and writer
|
12
|
+
# methods.
|
13
|
+
|
14
|
+
module Acme
|
15
|
+
class Scanner1200PDQ
|
16
|
+
def read
|
17
|
+
"this is some dummy text"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module FooCorp
|
23
|
+
class LaserPrinter5
|
24
|
+
def write(str)
|
25
|
+
puts str
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class GenericCopier
|
31
|
+
def copy
|
32
|
+
data = reader.read
|
33
|
+
writer.write data
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class SimpleOffice
|
38
|
+
include MinDI::InjectableContainer
|
39
|
+
|
40
|
+
copier { GenericCopier.new }
|
41
|
+
reader { Acme::Scanner1200PDQ.new }
|
42
|
+
writer { FooCorp::LaserPrinter5.new }
|
43
|
+
end
|
44
|
+
|
45
|
+
office = SimpleOffice.new
|
46
|
+
|
47
|
+
office.copier.copy
|
48
|
+
|
49
|
+
|
50
|
+
#---------------
|
51
|
+
# This example shows that inject_into can be used to define mutually depedent
|
52
|
+
# services more conveniently. First, a mutual dependence example without using
|
53
|
+
# inject_into:
|
54
|
+
|
55
|
+
class A
|
56
|
+
attr_accessor :b
|
57
|
+
def initialize b
|
58
|
+
@b = b
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class B
|
63
|
+
attr_accessor :a
|
64
|
+
def initialize a
|
65
|
+
@a = a
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class MutualContainer
|
70
|
+
include MinDI::BasicContainer
|
71
|
+
|
72
|
+
a { temp_a = A.new(b); b.a = temp_a; temp_a }
|
73
|
+
# awkward!
|
74
|
+
|
75
|
+
b { B.new(nil) }
|
76
|
+
# This still doesn't work properly! There's no way to refer to 'a' yet.
|
77
|
+
end
|
78
|
+
|
79
|
+
cont = MutualContainer.new
|
80
|
+
p cont.b.a # returns nil until after service 'a' is invoked
|
81
|
+
p cont.a.b.a.b
|
82
|
+
p cont.b.a # returns a
|
83
|
+
|
84
|
+
#---------------
|
85
|
+
# Next, a mutual dependence example using inject_into. Note how much
|
86
|
+
# simpler the service implementations are!
|
87
|
+
|
88
|
+
class A2
|
89
|
+
end
|
90
|
+
|
91
|
+
class B2
|
92
|
+
end
|
93
|
+
|
94
|
+
class MutualContainerUsingInjectable
|
95
|
+
include MinDI::InjectableContainer
|
96
|
+
|
97
|
+
a { A2.new }
|
98
|
+
b { B2.new }
|
99
|
+
end
|
100
|
+
|
101
|
+
cont = MutualContainerUsingInjectable.new
|
102
|
+
p cont.b.a.b.a # everything is set up correctly
|
103
|
+
p cont.a.b.a.b
|
104
|
+
|
105
|
+
|
106
|
+
#---------------
|
107
|
+
# It is even possible to inject one _container_ into another:
|
108
|
+
|
109
|
+
class OuterContainer
|
110
|
+
include MinDI::BasicContainer
|
111
|
+
|
112
|
+
something { [:foo, "bar", something_else] }
|
113
|
+
# note the reference to a service provided by the InnerContainer
|
114
|
+
end
|
115
|
+
|
116
|
+
class InnerContainer
|
117
|
+
include MinDI::InjectableContainer
|
118
|
+
|
119
|
+
injected # this is the default anyway
|
120
|
+
outer { OuterContainer.new }
|
121
|
+
|
122
|
+
uninjected
|
123
|
+
# No need to mess up this little hash by injecting InnerContainer into it
|
124
|
+
something_else { { :baz => :zap } }
|
125
|
+
end
|
126
|
+
|
127
|
+
ic = InnerContainer.new
|
128
|
+
|
129
|
+
p ic.outer.something
|
130
|
+
|
131
|
+
|
132
|
+
#---------------
|
133
|
+
# Using Injectable without MinDI containers.
|
134
|
+
|
135
|
+
x = [1,2,3]
|
136
|
+
y = {}
|
137
|
+
x.extend MinDI::Injectable
|
138
|
+
x.inject_into y
|
139
|
+
p y.reverse
|
140
|
+
|
141
|
+
|
data/examples/simple.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'mindi'
|
2
|
+
|
3
|
+
class SimpleContainer
|
4
|
+
include MinDI::BasicContainer
|
5
|
+
|
6
|
+
greeting { "Hello, world\n" }
|
7
|
+
|
8
|
+
point_at { |x,y| [x,y] }
|
9
|
+
|
10
|
+
stuff { [greeting, point_at(100,200)] }
|
11
|
+
end
|
12
|
+
|
13
|
+
cont = SimpleContainer.new
|
14
|
+
|
15
|
+
p cont.stuff # ==> ["Hello, world\n", [100, 200]]
|
data/examples/test.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
# Examples and unit tests (sort of) for MinDI.
|
2
|
+
|
3
|
+
require 'mindi'
|
4
|
+
|
5
|
+
class Foo; end
|
6
|
+
class Bar < Struct.new(:key); end
|
7
|
+
class Point < Struct.new(:x, :y); end
|
8
|
+
|
9
|
+
class MyContainer
|
10
|
+
include MinDI::BasicContainer
|
11
|
+
|
12
|
+
singleton :foo do
|
13
|
+
Foo.new
|
14
|
+
end
|
15
|
+
|
16
|
+
# shortcut for singleton
|
17
|
+
other_foo { Foo.new }
|
18
|
+
|
19
|
+
multiton :bar_for do |key|
|
20
|
+
Bar.new(key)
|
21
|
+
# The Bar instance doesn't have to receive the key, but it can.
|
22
|
+
end
|
23
|
+
|
24
|
+
# shortcut for multiton
|
25
|
+
other_bar_for { |key| Bar.new(key) }
|
26
|
+
|
27
|
+
# shortcut for multiton with multiple keys
|
28
|
+
point_for_xy { |x,y| Point.new(x,y) }
|
29
|
+
|
30
|
+
# Reference other services.
|
31
|
+
foos { [foo, other_foo] }
|
32
|
+
|
33
|
+
# We can manage services manually.
|
34
|
+
generic :gene do
|
35
|
+
@gene ||= Foo.new
|
36
|
+
end
|
37
|
+
|
38
|
+
# service implementations are closures
|
39
|
+
shared_foo = nil
|
40
|
+
generic :shared do
|
41
|
+
shared_foo ||= Foo.new
|
42
|
+
# shared among all MyContainer instances
|
43
|
+
end
|
44
|
+
|
45
|
+
threaded :per_thread do # |thread| # optional
|
46
|
+
Array.new
|
47
|
+
end
|
48
|
+
|
49
|
+
deferred :lazy do
|
50
|
+
$deferred_service_done = true # for testing
|
51
|
+
"Deferred service result"
|
52
|
+
end
|
53
|
+
|
54
|
+
begin
|
55
|
+
not_a_service
|
56
|
+
rescue NameError
|
57
|
+
# Raises an error because no block is given, so it can't possibly be
|
58
|
+
# a service definition. We just let the superclass's method_missing
|
59
|
+
# handle it.
|
60
|
+
else raise
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
MyContainer.service_declared_outside do
|
65
|
+
Bar.new(other_foo) # note scope is still the container instance
|
66
|
+
end
|
67
|
+
|
68
|
+
cont = MyContainer.new
|
69
|
+
|
70
|
+
raise unless cont.foo.equal?(cont.foo)
|
71
|
+
raise unless cont.other_foo.equal?(cont.other_foo)
|
72
|
+
|
73
|
+
raise unless cont.bar_for(3).equal?(cont.bar_for(3))
|
74
|
+
raise unless cont.other_bar_for(3).equal?(cont.other_bar_for(3))
|
75
|
+
raise unless cont.point_for_xy(3, 33).equal?(cont.point_for_xy(3, 33))
|
76
|
+
|
77
|
+
raise if cont.bar_for(3).equal?(cont.bar_for(7))
|
78
|
+
raise if cont.other_bar_for(3).equal?(cont.other_bar_for(7))
|
79
|
+
raise if cont.point_for_xy(3, 33).equal?(cont.point_for_xy(7, 77))
|
80
|
+
|
81
|
+
raise unless cont.foos.uniq.size == 2
|
82
|
+
|
83
|
+
raise unless cont.gene.is_a?(Foo)
|
84
|
+
raise unless cont.gene.equal?(cont.gene)
|
85
|
+
|
86
|
+
cont2 = MyContainer.new
|
87
|
+
raise unless cont2.shared.equal?(cont.shared)
|
88
|
+
|
89
|
+
raise unless cont.service_declared_outside.is_a?(Bar)
|
90
|
+
|
91
|
+
t1 = Thread.new { cont.per_thread }
|
92
|
+
t2 = Thread.new { cont.per_thread }
|
93
|
+
|
94
|
+
raise if t1.value.equal?(t2.value)
|
95
|
+
|
96
|
+
lazy = cont.lazy
|
97
|
+
raise if $deferred_service_done
|
98
|
+
|
99
|
+
lazy.grep /foo/ # Call a method, which will instantiate the object.
|
100
|
+
raise unless $deferred_service_done
|
101
|
+
|
102
|
+
# The container class may accept perameters for instantiating containers.
|
103
|
+
class ParametricContainer
|
104
|
+
include MinDI::InjectableContainer
|
105
|
+
def initialize(name, opts = {})
|
106
|
+
@name = name
|
107
|
+
@opts = opts
|
108
|
+
end
|
109
|
+
|
110
|
+
thing { @opts[:thing_class].new("thing of #{@name}") }
|
111
|
+
end
|
112
|
+
|
113
|
+
tc = Struct.new(:name)
|
114
|
+
pc = ParametricContainer.new("fred", {:thing_class => tc})
|
115
|
+
raise unless pc.thing.name == "thing of fred"
|
116
|
+
|
117
|
+
puts "all tests passed!"
|