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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: aca8e938147f48488f4e0f0c39a0cf2b2dc73c6d
|
4
|
+
data.tar.gz: 45deba41f62f63149767a778bdb634b54c0d2a4c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 90b98a6bface5c02dc56a7c12642f612a6e3ce2ba8ed3210c62e9909012277b0f7cfb05c5469a2900c0c6f6ad886d2d3c915b9285ef695142ee586cedecf605c
|
7
|
+
data.tar.gz: b0500a9adb11e764ea026658222e2b9d502e8947601a6567a69bb1973a85269b42357112f1faf44efe513b65c516be8688e14500d56a69f24d89df5f6bf0ff19
|
data/COPYING
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2004-2014, Joel VanderWerf, vjoel@users.sourceforge.net
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
8
|
+
list of conditions and the following disclaimer.
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
11
|
+
and/or other materials provided with the distribution.
|
12
|
+
|
13
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
14
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
15
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
16
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
17
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
18
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
19
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
20
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
21
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
22
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
MinDI
|
2
|
+
=====
|
3
|
+
|
4
|
+
MinDI is Minimalist Dependency Injection for Ruby. It is inspired by Jamis Buck's Needle (http://needle.rubyforge.org) and Jim Weirich's article on DI in Ruby (http://onestepback.org/index.cgi/Tech/Ruby/DependencyInjectionInRuby.rdoc).
|
5
|
+
|
6
|
+
MinDI is minimalist in that it attempts to map concepts of DI into basic ruby
|
7
|
+
constructs, rather than into a layer of specialized constructs. In particular, classes and modules function as containers and registries, and methods and method definitions function as service points and services. There are some inherent advantages and disadvantages to this approach, discussed below.
|
8
|
+
|
9
|
+
MinDI builds on this minimal DI container by adding the InjectableContainer concept, which is a kind of DI available only in dynamic languages: through the magic of <tt>method_missing</tt>, a service may invoke other services without having explicit setter or constructor references to those services.
|
10
|
+
|
11
|
+
Synopsis
|
12
|
+
--------
|
13
|
+
|
14
|
+
Using the BasicContainer module for constructor injection:
|
15
|
+
|
16
|
+
require 'mindi'
|
17
|
+
|
18
|
+
class SimpleContainer
|
19
|
+
include MinDI::BasicContainer
|
20
|
+
|
21
|
+
greeting { "Hello, world\n" }
|
22
|
+
|
23
|
+
point_at { |x,y| [x,y] }
|
24
|
+
|
25
|
+
stuff { [greeting, point_at(100,200)] }
|
26
|
+
end
|
27
|
+
|
28
|
+
cont = SimpleContainer.new
|
29
|
+
|
30
|
+
p cont.stuff # ==> ["Hello, world\n", [100, 200]]
|
31
|
+
|
32
|
+
Using the InjectableContainer module for "dynamic" or "fallback" injection, using `method_missing`:
|
33
|
+
|
34
|
+
require 'mindi'
|
35
|
+
|
36
|
+
class Transformer
|
37
|
+
def transform string
|
38
|
+
string.gsub(pattern, &replacement)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class TransformerContainer
|
43
|
+
include MinDI::InjectableContainer
|
44
|
+
|
45
|
+
pattern { /foo/ }
|
46
|
+
replacement { proc {|match| match.upcase } }
|
47
|
+
transformer { Transformer.new }
|
48
|
+
transform { |str| transformer.transform(str) }
|
49
|
+
end
|
50
|
+
|
51
|
+
cont = TransformerContainer.new
|
52
|
+
s1 = cont.transform("fo foo fee")
|
53
|
+
s2 = cont.transform("fo foo fee")
|
54
|
+
p s1 # ==> "fo FOO fee"
|
55
|
+
p s1.equal?(s2) # ==> true
|
56
|
+
|
57
|
+
|
58
|
+
Note that the Transformer class is written without explicitly linking up
|
59
|
+
to services (either in initialize or in setters). It just assumes that
|
60
|
+
the services will be defined in the container.
|
61
|
+
|
62
|
+
Note also that the #transform service is a multiton service, and (like
|
63
|
+
singleton services) it caches its value for each argument.
|
64
|
+
|
65
|
+
|
66
|
+
Advantages
|
67
|
+
----------
|
68
|
+
|
69
|
+
- Compact implementation (the essentials are about 100 lines).
|
70
|
+
|
71
|
+
- Compact syntax.
|
72
|
+
|
73
|
+
- Familiar constructs and idioms, like subclassing, module inclusion, nested
|
74
|
+
classes, protected and private, all apply.
|
75
|
+
|
76
|
+
- Use of classes and methods as containers and services means you can apply a
|
77
|
+
standard AOP or debugging lib.
|
78
|
+
|
79
|
+
- Services can take arguments, and this permits multiton services. Like singleton services, multiton services cache their results.
|
80
|
+
|
81
|
+
- Dynamic service registration is easy, since ruby's class system is itself
|
82
|
+
so dynamic. See examples/dynamic.rb.
|
83
|
+
|
84
|
+
Disadvantages
|
85
|
+
-------------
|
86
|
+
|
87
|
+
- A container's services live in the same namespace as the methods inherited
|
88
|
+
from Kernel and Object, so a service called "dup", for example, will
|
89
|
+
prevent calling Object#dup on the container (except in the implementation
|
90
|
+
of the dup service, which can use super to invoke Object#dup). The MinDI
|
91
|
+
framework itself adds a few methods that could conflict with services
|
92
|
+
(#singleton, #generic, etc.). Also, the "shortcut" service definition
|
93
|
+
(using method_missing) will not let you define services like "dup"--you
|
94
|
+
would have to use an explicit definer, like #singleton.
|
95
|
+
|
96
|
+
- No built-in AOP, logging, debugging, or reflection interface, as in Needle.
|
97
|
+
|
98
|
+
Notes
|
99
|
+
-----
|
100
|
+
|
101
|
+
- Supports threaded, deferred, singleton, and multiton service models (though
|
102
|
+
these are not yet independent choices). Additional service models can be
|
103
|
+
easily added in modules which include Container. The "generic" model can
|
104
|
+
be used like "prototype" in Needle, or for manual service management.
|
105
|
+
|
106
|
+
- Use mixins to build apps out of groups of services that need to coexist in
|
107
|
+
one name space.
|
108
|
+
|
109
|
+
- Use a nested class for a group of services when you want them to live in
|
110
|
+
their own namespace. (See the ColorNamespace example.)
|
111
|
+
|
112
|
+
- The Injectable module can be used without MinDI containers as a kind of
|
113
|
+
delegation:
|
114
|
+
|
115
|
+
require 'mindi'
|
116
|
+
x = [1,2,3]
|
117
|
+
y = {}
|
118
|
+
x.extend MinDI::Injectable
|
119
|
+
x.inject_into y
|
120
|
+
p y.reverse # ==> [3, 2, 1]
|
121
|
+
|
122
|
+
- MinDI can be used as a Rake-like task scheduler:
|
123
|
+
|
124
|
+
require 'mindi'
|
125
|
+
|
126
|
+
class Tasks
|
127
|
+
include MinDI::BasicContainer
|
128
|
+
|
129
|
+
a { print "a" }
|
130
|
+
b { a; print "b" }
|
131
|
+
c { a; print "c" }
|
132
|
+
d { b; c; print "d" }
|
133
|
+
end
|
134
|
+
|
135
|
+
Tasks.new.d # ==> abcd
|
136
|
+
|
137
|
+
Bugs
|
138
|
+
----
|
139
|
+
|
140
|
+
- Private and protected services must be declared explicitly:
|
141
|
+
|
142
|
+
private :some_service
|
143
|
+
|
144
|
+
rather than by putting them in the private section of the class def.
|
145
|
+
|
146
|
+
- Because of how ruby defines Proc#arity, a service defined like
|
147
|
+
|
148
|
+
sname { do_something }
|
149
|
+
|
150
|
+
with no argument list will be treated as a multikey_multiton rather than
|
151
|
+
as a singleton. The behavior will be the same, though.
|
152
|
+
|
153
|
+
- Running ruby with the -w option will warn about 'instance variable @foo not
|
154
|
+
initialized'.
|
155
|
+
|
156
|
+
Todo
|
157
|
+
----
|
158
|
+
|
159
|
+
- MinDI had some introspection methods ("services_by_model(m)"), but I took
|
160
|
+
them out to keep the lib minimal. Maybe they will be in a mixin later.
|
161
|
+
|
162
|
+
- Use args passed to service point declarations to specify aspects of the
|
163
|
+
service model (e.g., threaded and deferred could be specified this way).
|
164
|
+
|
165
|
+
- DRb services and distributed containers. Use Rinda for service discovery.
|
166
|
+
|
167
|
+
- Thread safety issues.
|
168
|
+
|
169
|
+
Legal and Contact Information
|
170
|
+
-----------------------------
|
171
|
+
|
172
|
+
Copyright (C) 2004-2014 Joel VanderWerf, mailto:vjoel@users.sourceforge.net.
|
173
|
+
|
174
|
+
License is BSD. See [COPYING](COPYING).
|
data/RELEASE-NOTES
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
mindi 0.5
|
2
|
+
|
3
|
+
- Repackaged and released as gem.
|
4
|
+
|
5
|
+
mindi 0.4
|
6
|
+
|
7
|
+
- The internal names for instance variables used to store values associated
|
8
|
+
with services are now more obscure. Thanks to Greg Fodor for the suggestion.
|
9
|
+
|
10
|
+
mindi 0.3
|
11
|
+
|
12
|
+
- Allow +nil+ and +false+ as service values. This allows limited rake-like functionality--see examples/rake-alike.rb.
|
13
|
+
|
14
|
+
- Can now include MinDI::BasicContainer instead of extend MinDI::Container, for convenience.
|
15
|
+
|
16
|
+
- Lots of examples.
|
17
|
+
|
18
|
+
mindi 0.2
|
19
|
+
|
20
|
+
- Injectable containers. See examples/inject.rb. Note that including the MinDI::Injectable module not only provides this new functionality but also extends with MinDI::Container
|
21
|
+
|
22
|
+
- Because of the above change, including MinDI::Injectable is now the recommended way of using MinDI.
|
23
|
+
|
24
|
+
mindi 0.1
|
25
|
+
|
26
|
+
- first release
|
data/Rakefile
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
PRJ = "mindi"
|
5
|
+
|
6
|
+
def version
|
7
|
+
@version ||= begin
|
8
|
+
require 'mindi'
|
9
|
+
warn "MinDI::VERSION not a string" unless MinDI::VERSION.kind_of? String
|
10
|
+
MinDI::VERSION
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def tag
|
15
|
+
@tag ||= "#{PRJ}-#{version}"
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Run tests"
|
19
|
+
Rake::TestTask.new :test do |t|
|
20
|
+
t.libs << "lib"
|
21
|
+
t.libs << "ext"
|
22
|
+
t.test_files = FileList["test/**/*.rb"]
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "Commit, tag, and push repo; build and push gem"
|
26
|
+
task :release => "release:is_new_version" do
|
27
|
+
require 'tempfile'
|
28
|
+
|
29
|
+
sh "gem build #{PRJ}.gemspec"
|
30
|
+
|
31
|
+
file = Tempfile.new "template"
|
32
|
+
begin
|
33
|
+
file.puts "release #{version}"
|
34
|
+
file.close
|
35
|
+
sh "git commit --allow-empty -a -v -t #{file.path}"
|
36
|
+
ensure
|
37
|
+
file.close unless file.closed?
|
38
|
+
file.unlink
|
39
|
+
end
|
40
|
+
|
41
|
+
sh "git tag #{tag}"
|
42
|
+
sh "git push"
|
43
|
+
sh "git push --tags"
|
44
|
+
|
45
|
+
sh "gem push #{tag}.gem"
|
46
|
+
end
|
47
|
+
|
48
|
+
namespace :release do
|
49
|
+
desc "Diff to latest release"
|
50
|
+
task :diff do
|
51
|
+
latest = `git describe --abbrev=0 --tags --match '#{PRJ}-*'`.chomp
|
52
|
+
sh "git diff #{latest}"
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "Log to latest release"
|
56
|
+
task :log do
|
57
|
+
latest = `git describe --abbrev=0 --tags --match '#{PRJ}-*'`.chomp
|
58
|
+
sh "git log #{latest}.."
|
59
|
+
end
|
60
|
+
|
61
|
+
task :is_new_version do
|
62
|
+
abort "#{tag} exists; update version!" unless `git tag -l #{tag}`.empty?
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'mindi'
|
2
|
+
|
3
|
+
# Jim Weirich's example, in MinDI. From Jim Weirich's article at
|
4
|
+
# http://onestepback.org/index.cgi/Tech/Ruby/DependencyInjectionInRuby.rdoc).
|
5
|
+
|
6
|
+
# Note that this code does not run because it depends on undefined classes.
|
7
|
+
|
8
|
+
# For the "injected" version, we assume that classes like WebApp, StockQuotes,
|
9
|
+
# and so on are written to refer directly to "error_handler", "logger", etc.
|
10
|
+
# The effect of injection is that these references will resolve to the
|
11
|
+
# error_handler, logger, etc. that belong to the same container.
|
12
|
+
#
|
13
|
+
# In the case of DBI and Logger, which are pre-existing classes, we use the
|
14
|
+
# more traditional approach of using argument lists to pass in references to
|
15
|
+
# the services that they need from the container.
|
16
|
+
|
17
|
+
class JWApplicationContainer
|
18
|
+
include MinDI::InjectableContainer
|
19
|
+
|
20
|
+
logfilename { "logfile.log" }
|
21
|
+
db_user { "jim" }
|
22
|
+
db_password { "secret" }
|
23
|
+
dbi_string { "DBI:Pg:example_data" }
|
24
|
+
|
25
|
+
app { WebApp.new }
|
26
|
+
quotes { StockQuotes.new }
|
27
|
+
authenticator { Authenticator.new }
|
28
|
+
database { DBI.connect(dbi_string, db_user, db_password) }
|
29
|
+
|
30
|
+
logger { Logger.new }
|
31
|
+
error_handler { ErrorHandler.new }
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_application
|
35
|
+
JWApplicationContainer.new.app
|
36
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'mindi'
|
2
|
+
|
3
|
+
# Jim Weirich's example, in MinDI. From Jim Weirich's article at
|
4
|
+
# http://onestepback.org/index.cgi/Tech/Ruby/DependencyInjectionInRuby.rdoc).
|
5
|
+
|
6
|
+
# Note that this code does not run because it depends on undefined classes.
|
7
|
+
|
8
|
+
class JWApplicationContainer
|
9
|
+
include MinDI::BasicContainer
|
10
|
+
|
11
|
+
logfilename { "logfile.log" }
|
12
|
+
db_user { "jim" }
|
13
|
+
db_password { "secret" }
|
14
|
+
dbi_string { "DBI:Pg:example_data" }
|
15
|
+
|
16
|
+
app {
|
17
|
+
app = WebApp.new(quotes, authenticator, database)
|
18
|
+
app.logger = logger
|
19
|
+
app.set_error_handler error_handler
|
20
|
+
app
|
21
|
+
}
|
22
|
+
|
23
|
+
quotes { StockQuotes.new(error_handler, logger) }
|
24
|
+
authenticator { Authenticator.new(database, logger, error_handler) }
|
25
|
+
database { DBI.connect(dbi_string, db_user, db_password) }
|
26
|
+
|
27
|
+
logger { Logger.new(logfilename) }
|
28
|
+
error_handler {
|
29
|
+
errh = ErrorHandler.new
|
30
|
+
errh.logger = logger
|
31
|
+
errh
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_application
|
36
|
+
JWApplicationContainer.new.app
|
37
|
+
end
|
data/examples/coffee.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# Coffee machine example taken from Jim Weirich's OSCON 2005 slides
|
2
|
+
# and rewritten in MinDI (following Christian Neukirchen).
|
3
|
+
|
4
|
+
require 'mindi'
|
5
|
+
|
6
|
+
class PotSensor
|
7
|
+
def initialize(port)
|
8
|
+
@port = port
|
9
|
+
end
|
10
|
+
|
11
|
+
def coffee_present?
|
12
|
+
#...
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class MockSensor < PotSensor; end
|
17
|
+
|
18
|
+
class Heater
|
19
|
+
def initialize(port)
|
20
|
+
@port = port
|
21
|
+
end
|
22
|
+
|
23
|
+
def on
|
24
|
+
#...
|
25
|
+
end
|
26
|
+
def off
|
27
|
+
#...
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class MockHeater < Heater; end
|
32
|
+
|
33
|
+
class Warmer
|
34
|
+
# Use attr_reader instead of "inject"--advantages: Warmer is
|
35
|
+
# not dependent on DI framework, and the configuration of
|
36
|
+
# the warmer is explicit in the container definition, below,
|
37
|
+
# rather than using implicit, based on method names.
|
38
|
+
attr_reader :pot_sensor, :heater
|
39
|
+
|
40
|
+
def initialize(h)
|
41
|
+
@pot_sensor = h[:pot_sensor]
|
42
|
+
@heater = h[:heater]
|
43
|
+
end
|
44
|
+
|
45
|
+
def trigger
|
46
|
+
if pot_sensor.coffee_present?
|
47
|
+
heater.on
|
48
|
+
else
|
49
|
+
heater.off
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
class MarkIVConfiguration
|
56
|
+
include MinDI::InjectableContainer
|
57
|
+
|
58
|
+
uninjected # avoid warnings that "class Fixnum cannot be injected into"
|
59
|
+
pot_sensor_io_port {0x08F0}
|
60
|
+
heater_io_port {0x08F1}
|
61
|
+
injected
|
62
|
+
|
63
|
+
pot_sensor {PotSensor.new pot_sensor_io_port}
|
64
|
+
heater {Heater.new heater_io_port}
|
65
|
+
warmer {Warmer.new :heater => heater, :pot_sensor => pot_sensor}
|
66
|
+
# IMO, it's better to keep this information in the container def.,
|
67
|
+
# rather than "hide" it in the inject declarations in Warmer. Isn't
|
68
|
+
# that more the spirit of DI?
|
69
|
+
end
|
70
|
+
|
71
|
+
class MarkIVTestConfig < MarkIVConfiguration
|
72
|
+
heater {MockHeater.new}
|
73
|
+
pot_sensor {MockSensor.new}
|
74
|
+
end
|
75
|
+
|
76
|
+
mkiv = MarkIVConfiguration.new
|
77
|
+
p mkiv.warmer
|