mindi 0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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!
@@ -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"]
@@ -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
+
@@ -0,0 +1,13 @@
1
+ require 'mindi'
2
+
3
+ class Tasks
4
+ include MinDI::BasicContainer
5
+
6
+ a { print "a" }
7
+ b { a; print "b" }
8
+ c { a; print "c" }
9
+ d { b; c; print "d" }
10
+ end
11
+
12
+ Tasks.new.d
13
+
@@ -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!"