deject 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/Readme.md +212 -0
- data/deject.gemspec +25 -0
- data/lib/deject.rb +8 -0
- data/lib/deject/functional.rb +48 -0
- data/lib/deject/object_oriented.rb +82 -0
- data/lib/deject/version.rb +3 -0
- data/spec/acceptance_spec.rb +78 -0
- data/spec/class_methods_spec.rb +41 -0
- data/spec/deject_function_spec.rb +15 -0
- data/spec/instance_methods_spec.rb +46 -0
- metadata +84 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/Readme.md
ADDED
@@ -0,0 +1,212 @@
|
|
1
|
+
About
|
2
|
+
=====
|
3
|
+
|
4
|
+
Simple dependency injection
|
5
|
+
|
6
|
+
Install
|
7
|
+
=======
|
8
|
+
|
9
|
+
On most systems:
|
10
|
+
|
11
|
+
$ gem install deject # coming soon
|
12
|
+
|
13
|
+
On some systems:
|
14
|
+
|
15
|
+
$ sudo gem install deject
|
16
|
+
|
17
|
+
If you have to use sudo and you don't know why, it's because you need to set your GEM_HOME environment variable.
|
18
|
+
|
19
|
+
Example
|
20
|
+
=======
|
21
|
+
|
22
|
+
require 'deject'
|
23
|
+
|
24
|
+
# Represents some client like https://github.com/voloko/twitter-stream
|
25
|
+
# Some client that will be used by our service
|
26
|
+
class Client
|
27
|
+
def initialize(credentials)
|
28
|
+
@credentials = credentials
|
29
|
+
end
|
30
|
+
|
31
|
+
def login(name)
|
32
|
+
@login = name
|
33
|
+
end
|
34
|
+
|
35
|
+
def has_logged_in?(name) # !> `&' interpreted as argument prefix
|
36
|
+
@login == name
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialized_with?(credentials)
|
40
|
+
@credentials == credentials
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
class Service
|
46
|
+
Deject self # <-- we'll talk more about this later
|
47
|
+
|
48
|
+
# you can basically think of the block as a factory that
|
49
|
+
# returns a client. It is evaluated in the context of the instance
|
50
|
+
# ...though I'm not sure that's a good strategy to employ
|
51
|
+
# (I suspect it would be better that these return constants as much as possible)
|
52
|
+
dependency(:client) { Client.new credentials }
|
53
|
+
|
54
|
+
attr_accessor :name
|
55
|
+
|
56
|
+
def initialize(name)
|
57
|
+
self.name = name
|
58
|
+
end
|
59
|
+
|
60
|
+
def login
|
61
|
+
client.login name
|
62
|
+
end
|
63
|
+
|
64
|
+
def credentials
|
65
|
+
# a login key or something, would probably be dejected as well
|
66
|
+
# to retrieve the result from some config file or service
|
67
|
+
'skj123@#KLFNV9ajv'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# using the default
|
72
|
+
service = Service.new('josh')
|
73
|
+
service.login
|
74
|
+
service.client # => #<Client:0x007ff97a92d9b8 @credentials="skj123@#KLFNV9ajv", @login="josh">
|
75
|
+
service.client.has_logged_in? 'josh' # => true
|
76
|
+
service.client.initialized_with? service.credentials # => true
|
77
|
+
|
78
|
+
# overriding the default at instance level
|
79
|
+
client_mock = Struct.new :recordings do
|
80
|
+
def method_missing(*args)
|
81
|
+
self.recordings ||= []
|
82
|
+
recordings << args
|
83
|
+
end
|
84
|
+
end
|
85
|
+
client = client_mock.new
|
86
|
+
sally = Service.new('sally').with_client client # <-- you can also override with a block
|
87
|
+
|
88
|
+
sally.login
|
89
|
+
client.recordings # => [[:login, "sally"]]
|
90
|
+
|
91
|
+
sally.login
|
92
|
+
client.recordings # => [[:login, "sally"], [:login, "sally"]]
|
93
|
+
|
94
|
+
Reasons
|
95
|
+
=======
|
96
|
+
|
97
|
+
Why write this?
|
98
|
+
---------------
|
99
|
+
|
100
|
+
Hard dependencies kick ass. They make your code clear and easy to understand.
|
101
|
+
But, of course, they're hard, you can't change them (or can't reasonably change them).
|
102
|
+
So when you go to test, it sucks. When you want to reuse, it sucks. How to get around this?
|
103
|
+
Inject your dependencies.
|
104
|
+
|
105
|
+
And while it's not the worst thing in the world to do custom dependency injection in Ruby,
|
106
|
+
it can still get obnoxious. What do you do? There's basically two options:
|
107
|
+
|
108
|
+
1. Add it as an argument when initializing (or _possibly_ when invoking your method). This works
|
109
|
+
fine if you aren't already doing anything complicated with your arguments. If you can just throw
|
110
|
+
an optional arg in there for the dependency, giving it a default of the hard dependency, then
|
111
|
+
it's not too big of a deal. But what if you have two dependencies? Then you can't use optional
|
112
|
+
arguments, because how will you know which is which? What if you're already taking optional args?
|
113
|
+
Then again, you can't pass this in optionally. So you have to set it to an ordinal argument, which
|
114
|
+
means that everywhere you use the thing, you have to deal with the dependency. It's cumbersome and ugly.
|
115
|
+
Or you can pass it in with an options hash, but what if you're already taking a hash (as I was when
|
116
|
+
I decided I wanted this) and it's not for this object? Then you have to namespace the common options
|
117
|
+
such that you can tell them apart, it's gross (e.g. passing html options to a form in Rails), and you
|
118
|
+
only need to do it for something that users shouldn't need to care about unless they really want to.
|
119
|
+
|
120
|
+
2. Defining methods to return the dependency that can be overridden by setting the value. This is a heavier
|
121
|
+
choice than the above, but it can become necessary. Define an `attr_writer :whatever` and a getter
|
122
|
+
whose body looks like `@whatever ||= HardDependency.new`. Not the worst thing in the world, but it takes
|
123
|
+
about four lines and clutters things up. What's more, it must be set with a setter, and setters always
|
124
|
+
return the RHS of the assignment. So to override it, you have to have three lines where you probably only want one.
|
125
|
+
And of course, having a real method in there is a statement. It says "this is the implementation", people
|
126
|
+
don't override methods all willy nilly, I'd give dirty looks to colleagues if they overrode it as was convenient.
|
127
|
+
For instance, say you _always_ want to override the default (e.g. a FakeUser in the test environment and User in
|
128
|
+
development/production environments). Then you have to open the class and redefine it in an initialization file.
|
129
|
+
Not cool.
|
130
|
+
|
131
|
+
Deject handles these shortcomings with the default ways to inject dependencies. Declaring something a dependency
|
132
|
+
inherently identifies it as overridable. Overriding it by environment is not shocking or unexpected, and only requires one line,
|
133
|
+
and has the advantage of closures during overriding -- as opposed to having to metaprogramming to set that default.
|
134
|
+
|
135
|
+
It makes it very easy to declare and to override dependencies, by adding an inline call to the override.
|
136
|
+
You don't have to deal with arguments, you don't have to define methods, it defines the methods for you
|
137
|
+
and gives you an easy way to inject a new value. In the end, it's simpler and easier to understand.
|
138
|
+
|
139
|
+
|
140
|
+
Statements I am trying to make by writing this
|
141
|
+
----------------------------------------------
|
142
|
+
|
143
|
+
Dependencies should be soft by default, dependency injection can have a place in Ruby
|
144
|
+
(even though I'll probably get made fun of for it). I acknowledge that I really enjoyed
|
145
|
+
the post [Why I love everything you hate about Java](http://magicscalingsprinkles.wordpress.com/2010/02/08/why-i-love-everything-you-hate-about-java/).
|
146
|
+
Though I agreed with a lot of the rebuttals in the comments as well.
|
147
|
+
|
148
|
+
I intentionally didn't do this with module inclusion. Module inclusion has become a cancer
|
149
|
+
(I'll probably write a blog about that later). _Especially_ the way people abuse the `self.included` hook.
|
150
|
+
I wanted to show people that you don't _HAVE_ to do that. There's no reason your module can't have
|
151
|
+
a method that is truthful about its purpose, something like `MyModule.apply_to MyClass`, it can include and extend
|
152
|
+
in there all it wants. That's fine, that's obvious, it isn't lying. But when people `include MyModule`
|
153
|
+
just so they can get into the included hook (where they almost never need to) and then **EXTEND** the class... grrrrr.
|
154
|
+
|
155
|
+
And of course, after I decided I wasn't going to directly include / extend the module, I began
|
156
|
+
thinking about how to get Deject onto the class. `Deject.dejectify SomeClass`? Couldn't think of
|
157
|
+
a good verb. But wait, do I _really_ need a verb? I went and read
|
158
|
+
[Execution in the Kingdom of Nouns](http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html)
|
159
|
+
and decided I was okay with having a method that applies it, hence `Deject SomeClass`. Not a usual practice
|
160
|
+
but not everything needs to be OO. Which led to the next realization that I didn't need a module at all.
|
161
|
+
|
162
|
+
So, there's two implementations. You can set which one you want to use with an environment variable (not that you care).
|
163
|
+
The first is "functional" which is to say that I was trying to channel functional ideas when writing it. It's really just one
|
164
|
+
big function that defines and redefines methods. Initially I hated this, I found it very difficult to read (might have been
|
165
|
+
better if Ruby had macros), I had to add comments in to keep track of what was happening.
|
166
|
+
But then I wrote the object oriented implementation, and it was pretty opaque as well.
|
167
|
+
Plus there were a lot of things I wanted to do that were very difficult to accomplish, and it was much longer.
|
168
|
+
|
169
|
+
So in the end, I'm hoping someone takes the time to look at both implementations and gives me feedback on their thoughts
|
170
|
+
Is one better? Are they each better in certain ways? Can this code be made simpler? Any feedback is welcome.
|
171
|
+
|
172
|
+
Oh, I also intentionally used closures over local variables rather than instance variables, because I
|
173
|
+
wanted to make people realize it's better to use setters and getters than to directly access instance variables
|
174
|
+
(to be fair, there are some big names that [disagree with](http://www.ruby-forum.com/topic/211544#919648) me on this).
|
175
|
+
I think most people directly access ivars because they haven't found themselves in a situation where it mattered.
|
176
|
+
But what if `attr_accessor` wound up changing implementations such that it didn't use ivars? "Ridiculous" I can hear
|
177
|
+
people saying, but it's not so ridiculous when you realize that you can remove 4 redundant lines by inheriting from
|
178
|
+
a Struct. If you use indirect access, everything still works just fine. And structs aren't the only place this occurs,
|
179
|
+
think about ActiveRecord::Base, it doesn't use ivars, so if you use attr_accessor in your model somewhere, you need to
|
180
|
+
know how a given attribute was defined so that you can know if you should use ivars or not... terrible. Deject's functional
|
181
|
+
implementation does not use ivars, you **must** use the getter and the overrider (there isn't currently a setter).
|
182
|
+
That is intentional (though I used ivars in the OO implementation).
|
183
|
+
|
184
|
+
|
185
|
+
Todo
|
186
|
+
====
|
187
|
+
|
188
|
+
Maybe raise an error if you call `with_whatever` and pass no args.
|
189
|
+
Maybe add a setter rather than only provide the overrider.
|
190
|
+
|
191
|
+
License
|
192
|
+
=======
|
193
|
+
|
194
|
+
Copyright (c) 2012 Joshua Cheek
|
195
|
+
|
196
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
197
|
+
of this software and associated documentation files (the "Software"), to deal
|
198
|
+
in the Software without restriction, including without limitation the rights
|
199
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
200
|
+
copies of the Software, and to permit persons to whom the Software is
|
201
|
+
furnished to do so, subject to the following conditions:
|
202
|
+
|
203
|
+
The above copyright notice and this permission notice shall be included in
|
204
|
+
all copies or substantial portions of the Software.
|
205
|
+
|
206
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
207
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
208
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
209
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
210
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
211
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
212
|
+
THE SOFTWARE.
|
data/deject.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "deject/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "deject"
|
7
|
+
s.version = Deject::VERSION
|
8
|
+
s.authors = ["Josh Cheek"]
|
9
|
+
s.email = ["josh.cheek@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Simple dependency injection}
|
12
|
+
s.description = %q{Provides a super simple API for dependency injection}
|
13
|
+
|
14
|
+
s.rubyforge_project = "deject"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_development_dependency "rspec"
|
23
|
+
s.add_development_dependency "pry"
|
24
|
+
# s.add_runtime_dependency "rest-client"
|
25
|
+
end
|
data/lib/deject.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Deject
|
2
|
+
UninitializedDependency = Class.new StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
def Deject(klass)
|
6
|
+
uninitialized_error = lambda do |meth|
|
7
|
+
raise Deject::UninitializedDependency, "#{meth} invoked before being defined"
|
8
|
+
end
|
9
|
+
|
10
|
+
# define klass.dependency
|
11
|
+
klass.define_singleton_method :dependency do |meth, &default_block|
|
12
|
+
|
13
|
+
# define the getter
|
14
|
+
define_method meth do
|
15
|
+
uninitialized_error[meth] unless default_block
|
16
|
+
value = instance_eval &default_block
|
17
|
+
define_singleton_method(meth) { value }
|
18
|
+
send meth
|
19
|
+
end
|
20
|
+
|
21
|
+
# define the override
|
22
|
+
define_method :"with_#{meth}" do |value=nil, &block|
|
23
|
+
|
24
|
+
# redefine getter if given a block
|
25
|
+
if block
|
26
|
+
define_singleton_method meth do
|
27
|
+
value = instance_eval &block
|
28
|
+
define_singleton_method(meth) { value }
|
29
|
+
send meth
|
30
|
+
end
|
31
|
+
|
32
|
+
# always return value if given a value
|
33
|
+
else
|
34
|
+
define_singleton_method(meth) { value }
|
35
|
+
end
|
36
|
+
|
37
|
+
self
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# override multiple dependencies
|
42
|
+
klass.send :define_method, :with_dependencies do |overrides|
|
43
|
+
overrides.each { |meth, value| send "with_#{meth}", value }
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
klass
|
48
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
def Deject(klass)
|
2
|
+
klass.extend Deject
|
3
|
+
end
|
4
|
+
|
5
|
+
module Deject
|
6
|
+
UninitializedDependency = Class.new StandardError
|
7
|
+
|
8
|
+
def dependency(meth, &block)
|
9
|
+
InstanceMethods.for self, meth, block
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
class InstanceMethods
|
14
|
+
attr_accessor :klass, :meth, :initializer, :ivar
|
15
|
+
|
16
|
+
def self.for(klass, meth, initializer)
|
17
|
+
instance = new klass, meth, initializer
|
18
|
+
instance.define_getter
|
19
|
+
instance.define_override
|
20
|
+
instance.define_multi_override
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(klass, meth, initializer)
|
24
|
+
self.klass, self.meth, self.initializer, self.ivar = klass, meth, initializer, "@#{meth}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def define_getter
|
28
|
+
ivar, meth, initializer = self.ivar, self.meth, self.initializer
|
29
|
+
klass.send :define_method, meth do
|
30
|
+
unless instance_variable_defined? ivar
|
31
|
+
instance_variable_set ivar, Deject::Dependency.new(self, meth, initializer)
|
32
|
+
end
|
33
|
+
instance_variable_get(ivar).invoke
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def define_override
|
38
|
+
ivar, meth = self.ivar, self.meth
|
39
|
+
klass.send :define_method, "with_#{meth}" do |value=nil, &initializer|
|
40
|
+
initializer ||= Proc.new { value }
|
41
|
+
instance_variable_set ivar, Deject::Dependency.new(self, meth, initializer)
|
42
|
+
self
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def define_multi_override
|
47
|
+
klass.send :define_method, :with_dependencies do |overrides|
|
48
|
+
overrides.each { |meth, value| send "with_#{meth}", value }
|
49
|
+
self
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
class Dependency < Struct.new(:instance, :dependency, :initializer)
|
56
|
+
attr_accessor :result
|
57
|
+
|
58
|
+
def invoke
|
59
|
+
validate_initializer!
|
60
|
+
memoized_result
|
61
|
+
end
|
62
|
+
|
63
|
+
def validate_initializer!
|
64
|
+
return if initializer
|
65
|
+
raise UninitializedDependency, "#{dependency} invoked before being defined"
|
66
|
+
end
|
67
|
+
|
68
|
+
def memoized_result
|
69
|
+
memoize! unless memoized?
|
70
|
+
result
|
71
|
+
end
|
72
|
+
|
73
|
+
def memoized?
|
74
|
+
@memoized
|
75
|
+
end
|
76
|
+
|
77
|
+
def memoize!
|
78
|
+
@memoized = true
|
79
|
+
self.result = instance.instance_eval &initializer
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'deject'
|
2
|
+
|
3
|
+
describe Deject, 'acceptance tests' do
|
4
|
+
example 'setting and overriding' do
|
5
|
+
class Client
|
6
|
+
def initialize(credentials)
|
7
|
+
@credentials = credentials
|
8
|
+
end
|
9
|
+
|
10
|
+
def login(name)
|
11
|
+
@login = name
|
12
|
+
end
|
13
|
+
|
14
|
+
def has_logged_in?(name)
|
15
|
+
@login == name
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialized_with?(credentials)
|
19
|
+
@credentials == credentials
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Service
|
24
|
+
Deject self
|
25
|
+
dependency(:client) { Client.new credentials }
|
26
|
+
|
27
|
+
attr_accessor :name
|
28
|
+
|
29
|
+
def initialize(name)
|
30
|
+
self.name = name
|
31
|
+
end
|
32
|
+
|
33
|
+
def login
|
34
|
+
client.login name
|
35
|
+
end
|
36
|
+
|
37
|
+
def credentials
|
38
|
+
'skj123@#KLFNV9ajv' # a login key or something, would probably be dejected as well
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# using the default
|
43
|
+
service = Service.new('josh')
|
44
|
+
service.login
|
45
|
+
service.client.should have_logged_in 'josh'
|
46
|
+
service.client.should be_initialized_with service.credentials
|
47
|
+
|
48
|
+
# overriding the default at instance level
|
49
|
+
client = double('Mock Client 1')
|
50
|
+
client.should_receive(:login).with('sally')
|
51
|
+
Service.new('sally').with_client(client).login
|
52
|
+
|
53
|
+
client_class, client = double, double
|
54
|
+
george = Service.new('george').with_client { client_class.new credentials }
|
55
|
+
client_class.should_receive(:new).with(george.credentials).and_return(client)
|
56
|
+
client.should_receive(:login).with('george')
|
57
|
+
george.login
|
58
|
+
|
59
|
+
# class default remains the same
|
60
|
+
Service.new('josh').client.should be_a_kind_of Client
|
61
|
+
|
62
|
+
# overriding the default at class level
|
63
|
+
client = double('Mock Client 2')
|
64
|
+
client.should_receive(:login).with('mei')
|
65
|
+
Service.dependency(:client) { client }
|
66
|
+
Service.new('mei').login
|
67
|
+
end
|
68
|
+
|
69
|
+
example 'avoid all dependencies by omitting the default' do
|
70
|
+
klass = Class.new do
|
71
|
+
Deject self
|
72
|
+
dependency :client
|
73
|
+
end
|
74
|
+
expect { klass.new.client }.to raise_error Deject::UninitializedDependency
|
75
|
+
client = double
|
76
|
+
klass.new.with_client(client).client.should be client
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'deject'
|
2
|
+
|
3
|
+
|
4
|
+
describe 'Klass.dependency' do
|
5
|
+
let(:klass) { Deject Class.new }
|
6
|
+
|
7
|
+
before { klass.new.should_not respond_to :dependency }
|
8
|
+
|
9
|
+
context 'with a block' do
|
10
|
+
meth = 'meth'.freeze
|
11
|
+
result = 100
|
12
|
+
before { klass.dependency(meth) { result } }
|
13
|
+
|
14
|
+
it 'adds the instance method' do
|
15
|
+
klass.new.should respond_to meth
|
16
|
+
end
|
17
|
+
|
18
|
+
specify 'the instance method defaults to the result of the init block' do
|
19
|
+
klass.new.send(meth).should == result
|
20
|
+
end
|
21
|
+
|
22
|
+
specify 'the instance method is evaluated within the context of the instance' do
|
23
|
+
klass.dependency(:a) { b }
|
24
|
+
instance = klass.new
|
25
|
+
def instance.b() 10 end
|
26
|
+
instance.a.should == instance.b
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'without a block' do
|
31
|
+
it 'adds the instance method' do
|
32
|
+
klass.dependency :abc
|
33
|
+
klass.new.should respond_to :abc
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'raises an error if called before setting it' do
|
37
|
+
klass.dependency :jjjjj
|
38
|
+
expect { klass.new.jjjjj }.to raise_error(Deject::UninitializedDependency, /jjjjj/)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'deject'
|
2
|
+
|
3
|
+
describe 'Deject()' do
|
4
|
+
let(:klass) { Class.new }
|
5
|
+
|
6
|
+
it 'adds the dependency method to the class' do
|
7
|
+
klass.should_not respond_to :dependency
|
8
|
+
Deject klass
|
9
|
+
klass.should respond_to :dependency
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'returns the class' do
|
13
|
+
Deject(klass).should be klass
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'deject'
|
2
|
+
|
3
|
+
describe 'after initializing a dependency' do
|
4
|
+
let(:klass) { Deject Class.new }
|
5
|
+
|
6
|
+
specify '#<dependency> returns the result, initialized for each instance' do
|
7
|
+
i = 0
|
8
|
+
klass.dependency(:number) { i += 1 }
|
9
|
+
klass.new.number.should == 1
|
10
|
+
klass.new.number.should == 2
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'memoizes the result' do
|
14
|
+
i = 0
|
15
|
+
klass.dependency(:number) { i += 1 }
|
16
|
+
instance = klass.new
|
17
|
+
instance.number.should == 1
|
18
|
+
instance.number.should == 1
|
19
|
+
end
|
20
|
+
|
21
|
+
specify '#with_<dependency> overrides the result' do
|
22
|
+
klass.dependency(:number) { 5 }
|
23
|
+
klass.new.with_number(6).number.should == 6
|
24
|
+
end
|
25
|
+
|
26
|
+
specify '#with_<dependency> can take a value or an init block' do
|
27
|
+
klass.dependency(:number1) { 1 }
|
28
|
+
klass.dependency(:number2) { 2 }
|
29
|
+
i = 0
|
30
|
+
instance = klass.new.with_number2 { i += number1 }
|
31
|
+
instance.number2.should == instance.number1
|
32
|
+
instance.number2.should == instance.number1
|
33
|
+
i.should == instance.number1
|
34
|
+
end
|
35
|
+
|
36
|
+
example 'you can override multiple defaults from the instance level by using with_dependencies' do
|
37
|
+
klass = Class.new do
|
38
|
+
Deject self
|
39
|
+
dependency(:a) { 1 }
|
40
|
+
dependency(:b) { 2 }
|
41
|
+
end
|
42
|
+
instance = klass.new.with_dependencies(a: 10, b: 20)
|
43
|
+
instance.a.should == 10
|
44
|
+
instance.b.should == 20
|
45
|
+
end
|
46
|
+
end
|
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: deject
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Josh Cheek
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-05 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70320438529780 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70320438529780
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: pry
|
27
|
+
requirement: &70320438528180 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70320438528180
|
36
|
+
description: Provides a super simple API for dependency injection
|
37
|
+
email:
|
38
|
+
- josh.cheek@gmail.com
|
39
|
+
executables: []
|
40
|
+
extensions: []
|
41
|
+
extra_rdoc_files: []
|
42
|
+
files:
|
43
|
+
- .gitignore
|
44
|
+
- Gemfile
|
45
|
+
- Rakefile
|
46
|
+
- Readme.md
|
47
|
+
- deject.gemspec
|
48
|
+
- lib/deject.rb
|
49
|
+
- lib/deject/functional.rb
|
50
|
+
- lib/deject/object_oriented.rb
|
51
|
+
- lib/deject/version.rb
|
52
|
+
- spec/acceptance_spec.rb
|
53
|
+
- spec/class_methods_spec.rb
|
54
|
+
- spec/deject_function_spec.rb
|
55
|
+
- spec/instance_methods_spec.rb
|
56
|
+
homepage: ''
|
57
|
+
licenses: []
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project: deject
|
76
|
+
rubygems_version: 1.8.10
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: Simple dependency injection
|
80
|
+
test_files:
|
81
|
+
- spec/acceptance_spec.rb
|
82
|
+
- spec/class_methods_spec.rb
|
83
|
+
- spec/deject_function_spec.rb
|
84
|
+
- spec/instance_methods_spec.rb
|