deject 0.0.1
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.
- 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
|