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.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use --create ruby-1.9.3-p194@untangle
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in untangle.gemspec
4
+ gemspec
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.
@@ -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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -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,3 @@
1
+ module Untangle
2
+ VERSION = "0.0.1"
3
+ 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,2 @@
1
+ require 'rspec'
2
+ require 'untangle'
@@ -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'
@@ -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