mindi 0.5

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.
@@ -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!"