untangle 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 +17 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +90 -0
- data/Rakefile +2 -0
- data/lib/untangle.rb +34 -0
- data/lib/untangle/injector.rb +50 -0
- data/lib/untangle/version.rb +3 -0
- data/spec/integration/dependency_definitions_spec.rb +90 -0
- data/spec/integration/injection_spec.rb +24 -0
- data/spec/integration/spec_helper.rb +2 -0
- data/spec/unit/injector_spec.rb +75 -0
- data/spec/unit/spec_helper.rb +1 -0
- data/untangle.gemspec +20 -0
- metadata +87 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use --create ruby-1.9.3-p194@untangle
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Yves Senn
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# Untangle
|
2
|
+
|
3
|
+
This gem provides a very lightweight API to specify dependencies
|
4
|
+
between objects. The key concepts behind it are:
|
5
|
+
|
6
|
+
* Make dependencies of your objects visible by declaring them
|
7
|
+
* Depend on roles, not implementation
|
8
|
+
* Simplify testing by substituting your dependencies with mocks
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem install untangle
|
14
|
+
```
|
15
|
+
|
16
|
+
## Define Dependencies
|
17
|
+
|
18
|
+
**explicit**
|
19
|
+
|
20
|
+
Explicit definitions contain a direct reference to the actual
|
21
|
+
dependency. The reference can either be a String:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
class Person
|
25
|
+
extend Untangle
|
26
|
+
|
27
|
+
dependency :translator, 'I18n'
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
or a block:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
class Person
|
35
|
+
extend Untangle
|
36
|
+
|
37
|
+
dependency(:translator) { I18n }
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
**implicit**
|
42
|
+
|
43
|
+
Implicit definitions have no reference to the actual dependency. The
|
44
|
+
implementation will be inferred from the name. In the following
|
45
|
+
example, the dependency would resolve to `Translator`.
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
class Person
|
49
|
+
extend Untangle
|
50
|
+
|
51
|
+
dependency :translator
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
## Global Dependencies
|
56
|
+
|
57
|
+
If you have global dependencies, which are used throughout your app,
|
58
|
+
you can register them globally. This way you can use `dependency`
|
59
|
+
implicit definitions without a name that corresponds to the implementation.
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
Untangle.register :translator, 'I18n'
|
63
|
+
|
64
|
+
class Blog
|
65
|
+
extend Untangle
|
66
|
+
|
67
|
+
dependency :translator
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
You can also use these registered dependencies to inject them into a
|
72
|
+
constructor. This technique does not require that the object under
|
73
|
+
construction knows about `explicit_dependencies`
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
Untangle.register :translator, 'I18n'
|
77
|
+
ExplicitDependencies.register :people_repository, 'PeopleRepository'
|
78
|
+
|
79
|
+
class MyPrcoess
|
80
|
+
|
81
|
+
# The argument names must match the registered dependencies
|
82
|
+
def initialize(translator, people_repository)
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
Untangle.inject(MyProcess, :new)
|
88
|
+
```
|
89
|
+
|
90
|
+
## Testing
|
data/Rakefile
ADDED
data/lib/untangle.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require "untangle/version"
|
2
|
+
require "untangle/injector"
|
3
|
+
|
4
|
+
module Untangle
|
5
|
+
def self.injector
|
6
|
+
@injector ||= Injector.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.register(name, dependency)
|
10
|
+
injector.register(name, dependency)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.lookup(name)
|
14
|
+
injector.lookup(name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.inject(method)
|
18
|
+
injector.inject(method)
|
19
|
+
end
|
20
|
+
|
21
|
+
def injector
|
22
|
+
@injector ||= Injector.new(Untangle.injector)
|
23
|
+
end
|
24
|
+
|
25
|
+
def dependency(name, *args, &block)
|
26
|
+
custom_injector = injector
|
27
|
+
custom_injector.register name, *args, &block
|
28
|
+
|
29
|
+
define_method name do
|
30
|
+
custom_injector.lookup(name)
|
31
|
+
end
|
32
|
+
private name
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
|
3
|
+
module Untangle
|
4
|
+
class Injector
|
5
|
+
|
6
|
+
def initialize(parent_injector = nil)
|
7
|
+
@parent_injector = parent_injector
|
8
|
+
@subjects = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def register(name, subject = nil)
|
12
|
+
subject = yield if block_given?
|
13
|
+
@subjects[name] = subject
|
14
|
+
end
|
15
|
+
|
16
|
+
def lookup(name)
|
17
|
+
@subjects[name] || handle_missing_subject(name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def inject(method)
|
21
|
+
arguments = parameters(injection_method(method)).map { |type, name|
|
22
|
+
lookup(name)
|
23
|
+
}
|
24
|
+
method.call(*arguments)
|
25
|
+
end
|
26
|
+
|
27
|
+
def handle_missing_subject(name)
|
28
|
+
if @parent_injector
|
29
|
+
@parent_injector.lookup(name)
|
30
|
+
else
|
31
|
+
name.to_s.classify.constantize
|
32
|
+
end
|
33
|
+
end
|
34
|
+
private :handle_missing_subject
|
35
|
+
|
36
|
+
def parameters(method)
|
37
|
+
method.parameters
|
38
|
+
end
|
39
|
+
private :parameters
|
40
|
+
|
41
|
+
def injection_method(method)
|
42
|
+
if method.name == :new
|
43
|
+
method.receiver.instance_method(:initialize)
|
44
|
+
else
|
45
|
+
method
|
46
|
+
end
|
47
|
+
end
|
48
|
+
private :injection_method
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'integration/spec_helper'
|
2
|
+
|
3
|
+
module TaskRepository
|
4
|
+
end
|
5
|
+
|
6
|
+
class Person
|
7
|
+
end
|
8
|
+
|
9
|
+
class ExampleClass
|
10
|
+
extend Untangle
|
11
|
+
|
12
|
+
dependency :class_dependency, Person
|
13
|
+
dependency :module_dependency, TaskRepository
|
14
|
+
dependency(:block_dependency) { Person }
|
15
|
+
dependency :task_repository
|
16
|
+
dependency :person
|
17
|
+
dependency :repository
|
18
|
+
end
|
19
|
+
|
20
|
+
module ExampleModule
|
21
|
+
extend self
|
22
|
+
extend Untangle
|
23
|
+
|
24
|
+
dependency :class_dependency, Person
|
25
|
+
dependency :module_dependency, TaskRepository
|
26
|
+
dependency(:block_dependency) { Person }
|
27
|
+
dependency :task_repository
|
28
|
+
dependency :person
|
29
|
+
dependency :repository
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'Dependency definitions' do
|
33
|
+
shared_examples_for 'definition with explicit dependencies' do
|
34
|
+
|
35
|
+
describe 'Dependency Definitions' do
|
36
|
+
it 'supports explicit class dependencies' do
|
37
|
+
subject.send(:class_dependency).should == Person
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'supports explicit module dependencies' do
|
41
|
+
subject.send(:module_dependency).should == TaskRepository
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'supports explicit block dependencies' do
|
45
|
+
subject.send(:block_dependency).should == Person
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'supports implicit module dependencies ' do
|
49
|
+
subject.send(:task_repository).should == TaskRepository
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'supports implicit class dependencies ' do
|
53
|
+
subject.send(:person).should == Person
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'defined accessors are private' do
|
57
|
+
lambda do
|
58
|
+
subject.module_dependency
|
59
|
+
end.should raise_error(NoMethodError)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'implicit dependencies can be defined globaly' do
|
63
|
+
Untangle.register :repository, TaskRepository
|
64
|
+
subject.send(:repository).should == TaskRepository
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "Classes" do
|
70
|
+
subject { ExampleClass.new }
|
71
|
+
|
72
|
+
it_behaves_like 'definition with explicit dependencies'
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "Modules" do
|
76
|
+
subject {
|
77
|
+
Class.new do
|
78
|
+
include ExampleModule
|
79
|
+
end.new
|
80
|
+
}
|
81
|
+
|
82
|
+
it_behaves_like 'definition with explicit dependencies'
|
83
|
+
end
|
84
|
+
|
85
|
+
describe 'self extended Module' do
|
86
|
+
subject { ExampleModule }
|
87
|
+
|
88
|
+
it_behaves_like 'definition with explicit dependencies'
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'integration/spec_helper'
|
2
|
+
|
3
|
+
class Blog; end
|
4
|
+
module PostRepository; end
|
5
|
+
|
6
|
+
class MyProcess
|
7
|
+
attr_reader :blog, :posts
|
8
|
+
def initialize(blog, posts)
|
9
|
+
@blog = blog
|
10
|
+
@posts = posts
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
describe 'Injection' do
|
16
|
+
it 'can inject the dependencies into arbitrary methods' do
|
17
|
+
Untangle.register :blog, Blog
|
18
|
+
Untangle.register :posts, PostRepository
|
19
|
+
|
20
|
+
process = Untangle.inject(MyProcess.method(:new))
|
21
|
+
process.blog.should == Blog
|
22
|
+
process.posts.should == PostRepository
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'unit/spec_helper'
|
3
|
+
require 'untangle/injector'
|
4
|
+
|
5
|
+
describe Untangle::Injector do
|
6
|
+
|
7
|
+
describe '#register ' do
|
8
|
+
it 'adds a dependency for later injection' do
|
9
|
+
subject.register :message, 'my name is Jane'
|
10
|
+
subject.lookup(:message).should == 'my name is Jane'
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'works with a block' do
|
14
|
+
subject.register(:greet) {'welcome'}
|
15
|
+
subject.lookup(:greet).should == 'welcome'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#lookup ' do
|
20
|
+
it 'returns registered subjects' do
|
21
|
+
subject.register :buffer_factory, String
|
22
|
+
subject.lookup(:buffer_factory).should == String
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'without registered subject' do
|
26
|
+
it 'turns the name into a constant' do
|
27
|
+
subject.lookup(:hash).should == Hash
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#inject ' do
|
33
|
+
it 'passes matching arguments to a method' do
|
34
|
+
greeter_class = Class.new do
|
35
|
+
def greet(name)
|
36
|
+
"Hello #{name}!"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
greeter = greeter_class.new
|
41
|
+
subject.register :name, 'Peter'
|
42
|
+
subject.inject(greeter.method(:greet)).should == 'Hello Peter!'
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'can be used to create instances' do
|
46
|
+
greeter = Class.new do
|
47
|
+
attr_reader :name
|
48
|
+
def initialize(name)
|
49
|
+
@name = name
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
subject.register :name, 'Max'
|
54
|
+
subject.inject(greeter.method(:new)).name.should == 'Max'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'with parent injector' do
|
59
|
+
let(:parent_injector) { described_class.new }
|
60
|
+
subject { described_class.new(parent_injector) }
|
61
|
+
|
62
|
+
it 'returns dependencies from the parent injector' do
|
63
|
+
parent_injector.register :eleven, 11
|
64
|
+
|
65
|
+
subject.lookup(:eleven).should == 11
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'overwrites parent dependencies with the same name' do
|
69
|
+
parent_injector.register :name, 'Sophie'
|
70
|
+
subject.register :name, 'Sandy'
|
71
|
+
|
72
|
+
subject.lookup(:name).should == 'Sandy'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'rspec'
|
data/untangle.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/untangle/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Yves Senn"]
|
6
|
+
gem.email = ["yves.senn@gmail.com"]
|
7
|
+
gem.description = 'Leightweight dependency API for your POROs'
|
8
|
+
gem.summary = 'ungangle your dependencies mess and make them visible.'
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "untangle"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Untangle::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency 'activesupport', '>= 3.0'
|
19
|
+
gem.add_development_dependency 'rspec'
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: untangle
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Yves Senn
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-23 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activesupport
|
16
|
+
requirement: &70221386294880 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '3.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70221386294880
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &70221386294440 !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: *70221386294440
|
36
|
+
description: Leightweight dependency API for your POROs
|
37
|
+
email:
|
38
|
+
- yves.senn@gmail.com
|
39
|
+
executables: []
|
40
|
+
extensions: []
|
41
|
+
extra_rdoc_files: []
|
42
|
+
files:
|
43
|
+
- .gitignore
|
44
|
+
- .rvmrc
|
45
|
+
- Gemfile
|
46
|
+
- LICENSE
|
47
|
+
- README.md
|
48
|
+
- Rakefile
|
49
|
+
- lib/untangle.rb
|
50
|
+
- lib/untangle/injector.rb
|
51
|
+
- lib/untangle/version.rb
|
52
|
+
- spec/integration/dependency_definitions_spec.rb
|
53
|
+
- spec/integration/injection_spec.rb
|
54
|
+
- spec/integration/spec_helper.rb
|
55
|
+
- spec/unit/injector_spec.rb
|
56
|
+
- spec/unit/spec_helper.rb
|
57
|
+
- untangle.gemspec
|
58
|
+
homepage: ''
|
59
|
+
licenses: []
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
requirements: []
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 1.8.10
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: ungangle your dependencies mess and make them visible.
|
82
|
+
test_files:
|
83
|
+
- spec/integration/dependency_definitions_spec.rb
|
84
|
+
- spec/integration/injection_spec.rb
|
85
|
+
- spec/integration/spec_helper.rb
|
86
|
+
- spec/unit/injector_spec.rb
|
87
|
+
- spec/unit/spec_helper.rb
|