quill 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +17 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +124 -0
- data/features/declaring_dependencies.feature +38 -0
- data/features/step_definitions/declaring_dependencies_steps.rb +82 -0
- data/features/support/env.rb +1 -0
- data/lib/quill.rb +5 -0
- data/lib/quill/container.rb +36 -0
- data/lib/quill/dsl.rb +33 -0
- data/lib/quill/factory.rb +37 -0
- data/lib/quill/no_factory_error.rb +14 -0
- data/lib/quill/unsatisfied_dependency_error.rb +14 -0
- data/lib/quill/version.rb +3 -0
- data/quill.gemspec +24 -0
- data/spec/quill/container_spec.rb +50 -0
- data/spec/quill/dsl_spec.rb +73 -0
- data/spec/quill/factory_spec.rb +104 -0
- data/spec/spec_helper.rb +11 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NWRmOGQ2ZDY1ZjU1ODFjY2UzMjU0MmZhZGMwZTFmOGFkZGFlODhjOQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZDgzM2YyZmViNjI5NzQ5NTQ5MWY0YmRlMWM4M2JiNTZlNjA4YjQ3ZA==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
ZWY1MWRkMmJkMTc4YzM0OGQ1M2M3ZTcwNjlkMjExZmI5ZGVlYmY5MzhlZGJh
|
10
|
+
ZDUxNTAyMDE4Njg3ODNmMGRiOTkxZjNkMzM3NDRhNGMxODE0MzE3NzM1ZmFh
|
11
|
+
MjkxODViNzUxOGRhODg1YTZiMTY1ZmRjMjEyNWU5ZjZlZmE4ZWY=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ZTZlYjgxYzU0MjgxZGFjOGYyZTk1NmM5NjZlOGNhMTRiMGZkYzBkYjU2ZGMz
|
14
|
+
OGRkNjQ5MWY0OWI1MDIxODdjYmRjY2Y2ODc3NjA4MDE1NTNkOTNlMWY0MDdh
|
15
|
+
OTBhZjQyZWYxNjg0NDNhZGI4YzNmMWQzYmFlMDUzNWRlNzU5MDQ=
|
data/.gitignore
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
quill
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.9.3
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Tim Cowlishaw
|
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,124 @@
|
|
1
|
+
# Quill
|
2
|
+
|
3
|
+
Quill helps you write cleaner, clearer ruby code, by disentangling the business
|
4
|
+
of specifying a classes dependencies from the business of constructing and
|
5
|
+
supplying them.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'quill'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install quill
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Extend your classes with the Quill DSL in order to specify their dependencies.
|
24
|
+
Each class must specify the name of the feature it implements, and can
|
25
|
+
optionally specify a list of features on which it depends:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
require 'quill/dsl'
|
29
|
+
class UIController
|
30
|
+
extend Quill::DSL
|
31
|
+
|
32
|
+
#The name of the feature implemented
|
33
|
+
provides :ui_controller
|
34
|
+
|
35
|
+
#The names of the features we depend on. These will be passed in order as
|
36
|
+
arguments to the constructor.
|
37
|
+
depends [:logger, :display]
|
38
|
+
|
39
|
+
def initialize(logger, display)
|
40
|
+
@logger = logger
|
41
|
+
@display = display
|
42
|
+
end
|
43
|
+
|
44
|
+
def run!
|
45
|
+
#...do things with the logger and display
|
46
|
+
end
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
You can then construct a Quill Container, and register all your Quill classes
|
51
|
+
with them:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
require 'quill/container'
|
55
|
+
container = Quill::Container.new
|
56
|
+
container.register_class(UIController)
|
57
|
+
|
58
|
+
#register implementation for every feature, so that the container can resolve
|
59
|
+
the dependencies:
|
60
|
+
container.register_class(Logger)
|
61
|
+
|
62
|
+
#You can also register single instances as named features:
|
63
|
+
container.register_singleton(:display, STDOUT)
|
64
|
+
|
65
|
+
```
|
66
|
+
|
67
|
+
Finally, ask the container for the implementation of a feature, this will
|
68
|
+
automatically resolve all the dependencies and construct the entire object
|
69
|
+
graph!
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
controller = container[:ui_controller]
|
73
|
+
controller.run!
|
74
|
+
```
|
75
|
+
|
76
|
+
You can also lazily construct objects with a mixture of static dependencies and
|
77
|
+
other arguments, using the 'curried' option:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
require 'quill/dsl'
|
81
|
+
require 'quill/container'
|
82
|
+
|
83
|
+
class InventoryDisplay
|
84
|
+
extend Quill::DSL
|
85
|
+
|
86
|
+
provides :inventory_display
|
87
|
+
depends [:display]
|
88
|
+
curried
|
89
|
+
|
90
|
+
#the display dependency is injected, items is not.
|
91
|
+
def initialize(display, items)
|
92
|
+
@display = display
|
93
|
+
@items = items
|
94
|
+
end
|
95
|
+
|
96
|
+
container = Quill::Container.new
|
97
|
+
container.register_class(InventoryDisplay)
|
98
|
+
|
99
|
+
display_builder_proc = container[:inventory_display]
|
100
|
+
|
101
|
+
display_builder_proc.call(inventory) #=> Constructs and returns an instance
|
102
|
+
```
|
103
|
+
|
104
|
+
## Why?
|
105
|
+
[Dependency
|
106
|
+
Injection](https://speakerdeck.com/bestie/improve-your-ruby-code-with-dependency-injection) is a fundamental pattern in object oriented programming, and is invaluable for making sure your code is testable and minimally coupled. However, it's often difficult to work out where the responsiblity for satisfying dependencies should happen. Quill takes care of this for you, with a simple, minimal framework for configuring your application's dependencies.
|
107
|
+
|
108
|
+
## Compatibiliyu
|
109
|
+
Tested with Ruby 1.9.3
|
110
|
+
|
111
|
+
## Still to come
|
112
|
+
|
113
|
+
- Support for argument hashes
|
114
|
+
- Support for Ruby 2.0 named arguments
|
115
|
+
- Configurable instance memoization
|
116
|
+
|
117
|
+
|
118
|
+
## Contributing
|
119
|
+
|
120
|
+
1. Fork it
|
121
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
122
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
123
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
124
|
+
5. Create new Pull Request
|
@@ -0,0 +1,38 @@
|
|
1
|
+
Feature: Declaring and Satisfying Dependencies
|
2
|
+
|
3
|
+
Scenario: Satisfying a dependency with a singleton
|
4
|
+
Given I have a factory with a named dependency
|
5
|
+
When I satisfy the named dependency with a singleton
|
6
|
+
And I call the factory
|
7
|
+
Then the factory should receive the singleton instance of its dependency
|
8
|
+
|
9
|
+
Scenario: Satisfying a dependency with a factory
|
10
|
+
Given I have a factory with a named dependency
|
11
|
+
When I satisfy the named dependency with another factory that has no dependencies
|
12
|
+
And I call the factory
|
13
|
+
Then the factory should receive an instance created by the dependency factory
|
14
|
+
|
15
|
+
Scenario: Transitively satisfying dependencies
|
16
|
+
Given I have a factory with a named dependency
|
17
|
+
When I satisfy the named dependency with another factory that has a dependency
|
18
|
+
And I satisfy that dependency with a singleton
|
19
|
+
And I call the factory
|
20
|
+
Then the dependency factory should receive the singleton instance of its dependency
|
21
|
+
And the factory should receive an instance created by the dependency factory
|
22
|
+
|
23
|
+
Scenario: Raising an error when dependencies are not satisfied
|
24
|
+
Given I have a factory with a named dependency
|
25
|
+
When I satisfy the named dependency with another factory that has a dependency
|
26
|
+
And I call the factory
|
27
|
+
Then an unmet dependency error should be raised for the transitive depenency
|
28
|
+
|
29
|
+
Scenario: Satisfying dependencies for curried factories
|
30
|
+
Given I have a curried factory with a named dependency
|
31
|
+
When I satisfy the named dependency with a singleton
|
32
|
+
And I call the factory
|
33
|
+
And I call the returned proc with additional arguments
|
34
|
+
Then the factory should receive the singleton dependency and the additional arguments
|
35
|
+
|
36
|
+
Scenario: Satisfying dependencies for factories that take an arguments hash
|
37
|
+
|
38
|
+
Scenario: Satisfying dependencies for curried factories that take an arguments hash
|
@@ -0,0 +1,82 @@
|
|
1
|
+
Given(/^I have a factory with a named dependency$/) do
|
2
|
+
@container = Quill::Container.new
|
3
|
+
@feature_class = Struct.new(:dependency) do
|
4
|
+
extend Quill::DSL
|
5
|
+
provides :feature
|
6
|
+
depends :dependency
|
7
|
+
end
|
8
|
+
@container.register_class(@feature_class)
|
9
|
+
end
|
10
|
+
|
11
|
+
Given(/^I have a curried factory with a named dependency$/) do
|
12
|
+
@container = Quill::Container.new
|
13
|
+
@feature_class = Struct.new(:dependency, :argument) do
|
14
|
+
extend Quill::DSL
|
15
|
+
provides :feature
|
16
|
+
depends :dependency
|
17
|
+
curried
|
18
|
+
end
|
19
|
+
@container.register_class(@feature_class)
|
20
|
+
end
|
21
|
+
|
22
|
+
When(/^I satisfy the named dependency with a singleton$/) do
|
23
|
+
@dependency = Object.new
|
24
|
+
@container.register_singleton(:dependency, @dependency)
|
25
|
+
end
|
26
|
+
|
27
|
+
When(/^I call the factory$/) do
|
28
|
+
begin
|
29
|
+
@feature = @container[:feature]
|
30
|
+
rescue Exception => e
|
31
|
+
@error = e
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
When(/^I satisfy the named dependency with another factory that has no dependencies$/) do
|
36
|
+
@dependency_class = Class.new do
|
37
|
+
extend Quill::DSL
|
38
|
+
provides :dependency
|
39
|
+
end
|
40
|
+
@container.register_class(@dependency_class)
|
41
|
+
end
|
42
|
+
|
43
|
+
When(/^I satisfy the named dependency with another factory that has a dependency$/) do
|
44
|
+
@dependency_class = Struct.new(:transitive_dependency) do
|
45
|
+
extend Quill::DSL
|
46
|
+
provides :dependency
|
47
|
+
depends :transitive_dependency
|
48
|
+
end
|
49
|
+
@container.register_class(@dependency_class)
|
50
|
+
end
|
51
|
+
|
52
|
+
When(/^I satisfy that dependency with a singleton$/) do
|
53
|
+
@transitive_dependency = Object.new
|
54
|
+
@container.register_singleton(:transitive_dependency, @transitive_dependency)
|
55
|
+
end
|
56
|
+
|
57
|
+
When(/^I call the returned proc with additional arguments$/) do
|
58
|
+
@argument = Object.new
|
59
|
+
@feature = @container[:feature][@argument]
|
60
|
+
end
|
61
|
+
|
62
|
+
Then(/^the factory should receive the singleton instance of its dependency$/) do
|
63
|
+
expect(@feature.dependency).to be == @dependency
|
64
|
+
end
|
65
|
+
|
66
|
+
Then(/^the factory should receive an instance created by the dependency factory$/) do
|
67
|
+
expect(@feature.dependency).to be_instance_of(@dependency_class)
|
68
|
+
end
|
69
|
+
|
70
|
+
Then(/^the dependency factory should receive the singleton instance of its dependency$/) do
|
71
|
+
expect(@feature.dependency.transitive_dependency).to be == @transitive_dependency
|
72
|
+
end
|
73
|
+
|
74
|
+
Then(/^the factory should receive the singleton dependency and the additional arguments$/) do
|
75
|
+
expect(@feature.dependency).to be == @dependency
|
76
|
+
expect(@feature.argument).to be == @argument
|
77
|
+
end
|
78
|
+
|
79
|
+
Then(/^an unmet dependency error should be raised for the transitive depenency$/) do
|
80
|
+
@error.should be_instance_of(Quill::UnsatisfiedDependencyError)
|
81
|
+
@error.dependency.should be == :transitive_dependency
|
82
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'quill'
|
data/lib/quill.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'quill/unsatisfied_dependency_error'
|
2
|
+
require 'quill/no_factory_error'
|
3
|
+
module Quill
|
4
|
+
class Container
|
5
|
+
def initialize
|
6
|
+
@singletons = {}
|
7
|
+
@factories = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def register_singleton(feature, instance)
|
11
|
+
@singletons[feature] = instance
|
12
|
+
end
|
13
|
+
|
14
|
+
def register_class(klass)
|
15
|
+
raise NoFactoryError.new(klass) unless klass.respond_to?(:factory)
|
16
|
+
factory = klass.factory
|
17
|
+
@factories[factory.feature] = factory
|
18
|
+
end
|
19
|
+
|
20
|
+
def [](name)
|
21
|
+
instance = named_singleton(name) || instance_from_named_factory(name)
|
22
|
+
instance or raise UnsatisfiedDependencyError.new(name)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def named_singleton(name)
|
28
|
+
@singletons[name]
|
29
|
+
end
|
30
|
+
|
31
|
+
def instance_from_named_factory(name)
|
32
|
+
factory = @factories[name]
|
33
|
+
factory && factory.call(self)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/quill/dsl.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'quill/factory'
|
2
|
+
module Quill
|
3
|
+
module DSL
|
4
|
+
|
5
|
+
def factory
|
6
|
+
raise "You must specify a feature provided by the class" unless provided_feature
|
7
|
+
@factory ||= Quill::Factory.new(self, {
|
8
|
+
:feature => provided_feature,
|
9
|
+
:dependencies => @dependencies || [],
|
10
|
+
:curried => curried?
|
11
|
+
})
|
12
|
+
end
|
13
|
+
|
14
|
+
def provides(feature_name)
|
15
|
+
@provided_feature = feature_name
|
16
|
+
end
|
17
|
+
|
18
|
+
def depends(*dependency_names)
|
19
|
+
@dependencies = dependency_names
|
20
|
+
end
|
21
|
+
|
22
|
+
def curried(value = true)
|
23
|
+
@curried = value
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
attr_reader :provided_feature
|
28
|
+
|
29
|
+
def curried?
|
30
|
+
!!@curried
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Quill
|
2
|
+
class Factory
|
3
|
+
attr_reader :feature
|
4
|
+
|
5
|
+
def initialize(klass, arguments={})
|
6
|
+
@feature = arguments.fetch(:feature)
|
7
|
+
@dependencies = arguments[:dependencies] || []
|
8
|
+
@curried = arguments[:curried]
|
9
|
+
@klass = klass
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(container)
|
13
|
+
curried? ? construct_curried_proc(container) : construct_instance(container)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
attr_reader :klass, :dependencies
|
18
|
+
|
19
|
+
def curried?
|
20
|
+
!!@curried
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def construct_instance(container, args=[])
|
25
|
+
arguments = fulfilled_dependencies(container) + args
|
26
|
+
@instance ||= klass.new(*arguments)
|
27
|
+
end
|
28
|
+
|
29
|
+
def construct_curried_proc(container)
|
30
|
+
->(*args) { construct_instance(container, args) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def fulfilled_dependencies(container)
|
34
|
+
dependencies.map { |dep| container[dep] }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Quill
|
2
|
+
class NoFactoryError < RuntimeError
|
3
|
+
def initialize(klass)
|
4
|
+
@klass = klass
|
5
|
+
end
|
6
|
+
|
7
|
+
def message
|
8
|
+
"The class #{klass.name} cannot be registered with the container, as it does not extend Quill::DSL."
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
attr_reader :klass
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Quill
|
2
|
+
class UnsatisfiedDependencyError < RuntimeError
|
3
|
+
def initialize(dependency)
|
4
|
+
@dependency = dependency
|
5
|
+
super
|
6
|
+
end
|
7
|
+
|
8
|
+
def message
|
9
|
+
"No implementation for the feature #{dependency} is registered with the container."
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :dependency
|
13
|
+
end
|
14
|
+
end
|
data/quill.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'quill/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "quill"
|
8
|
+
spec.version = Quill::VERSION
|
9
|
+
spec.authors = ["Tim Cowlishaw"]
|
10
|
+
spec.email = ["tim@timcowlishaw.co.uk"]
|
11
|
+
spec.description = %q{Quill helps you organize your code more easily by separating the business of specifying a class's dependencies from the business of actually providing them, using a simple, declerative syntax.}
|
12
|
+
spec.summary = %q{Quill - Simple DI for your rupy applications}
|
13
|
+
spec.homepage = "http://github.com/timcowlishaw/quill"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rspec"
|
23
|
+
spec.add_development_dependency "cucumber"
|
24
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'quill/container'
|
3
|
+
require 'quill/unsatisfied_dependency_error'
|
4
|
+
|
5
|
+
describe Quill::Container do
|
6
|
+
subject(:container) { Quill::Container.new }
|
7
|
+
let(:instance) { double(:instance) }
|
8
|
+
let(:name) { double(:factory) }
|
9
|
+
|
10
|
+
describe "requesting features which don't exist" do
|
11
|
+
it "raises an unment dependecy error for the feature" do
|
12
|
+
expect {
|
13
|
+
container[:non_existant_feature]
|
14
|
+
}.to raise_error { |error|
|
15
|
+
expect(error).to be_instance_of(Quill::UnsatisfiedDependencyError)
|
16
|
+
expect(error.dependency).to be == :non_existant_feature
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "registering and returning singletons" do
|
22
|
+
it "returns the registed instance" do
|
23
|
+
container.register_singleton(name, instance)
|
24
|
+
expect(container[name]).to be(instance)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "registering and constructing classes" do
|
29
|
+
context "registering a class with a factory" do
|
30
|
+
it "calls the factory with itself when the named feature is requested" do
|
31
|
+
klass = double(:class)
|
32
|
+
instance = double(:instance)
|
33
|
+
factory = double(:factory, :feature => :feature_name)
|
34
|
+
expect(klass).to receive(:factory).and_return(factory)
|
35
|
+
container.register_class(klass)
|
36
|
+
expect(factory).to receive(:call).with(container).and_return(instance)
|
37
|
+
expect(container[:feature_name]).to be == instance
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "registering a class without a factory" do
|
42
|
+
it "raises an error" do
|
43
|
+
klass = double(:class)
|
44
|
+
expect {
|
45
|
+
container.register_class(klass)
|
46
|
+
}.to raise_error(Quill::NoFactoryError)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'quill/dsl'
|
3
|
+
describe "Quill::DSL" do
|
4
|
+
|
5
|
+
let(:factory) { double(:factory) }
|
6
|
+
|
7
|
+
subject(:extending_class) {
|
8
|
+
Class.new do
|
9
|
+
extend Quill::DSL
|
10
|
+
end
|
11
|
+
}
|
12
|
+
|
13
|
+
before(:each) do
|
14
|
+
allow(Quill::Factory).to receive(:new).and_return(factory)
|
15
|
+
end
|
16
|
+
|
17
|
+
context "when a provided feature name is not supplied" do
|
18
|
+
it "raises an error when the factory is accessed" do
|
19
|
+
expect {
|
20
|
+
extending_class.factory
|
21
|
+
}.to raise_error
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "when a provided feature name is supplied" do
|
26
|
+
context "when no dependencies are supplied" do
|
27
|
+
it "exposes a Quill factory wrapping the extended class with the specified feature name and no dependencies" do
|
28
|
+
expect(Quill::Factory).to receive(:new).with(extending_class, {
|
29
|
+
:feature => :feature_name, :dependencies => [], :curried => false
|
30
|
+
}).and_return(factory)
|
31
|
+
|
32
|
+
extending_class.instance_eval do
|
33
|
+
extend Quill::DSL
|
34
|
+
provides :feature_name
|
35
|
+
end
|
36
|
+
|
37
|
+
expect(extending_class.factory).to be == factory
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "when dependencies are supplied" do
|
42
|
+
it "exposes a Quill factory wrapping the extended class with the specified feature name and the_specified dependencies" do
|
43
|
+
expect(Quill::Factory).to receive(:new).with(extending_class, {
|
44
|
+
:feature => :feature_name,
|
45
|
+
:dependencies => [:dependency_1, :dependency_2],
|
46
|
+
:curried => false
|
47
|
+
}).and_return(factory)
|
48
|
+
|
49
|
+
extending_class.instance_eval do
|
50
|
+
extend Quill::DSL
|
51
|
+
provides :feature_name
|
52
|
+
depends :dependency_1, :dependency_2
|
53
|
+
end
|
54
|
+
|
55
|
+
expect(extending_class.factory).to be == factory
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "when the factory is specified as curried" do
|
60
|
+
it "exposes a curried Quill factory wrapping the extended class with the specified feature name" do
|
61
|
+
expect(Quill::Factory).to receive(:new).with(extending_class, {
|
62
|
+
:feature => :feature_name, :dependencies => [], :curried => true
|
63
|
+
})
|
64
|
+
extending_class.instance_eval do
|
65
|
+
extend Quill::DSL
|
66
|
+
provides :feature_name
|
67
|
+
curried
|
68
|
+
end
|
69
|
+
expect(extending_class.factory).to be == factory
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'quill/factory'
|
3
|
+
describe Quill::Factory do
|
4
|
+
let(:feature_name) { double(:feature_name) }
|
5
|
+
let(:klass) { double(:klass) }
|
6
|
+
|
7
|
+
subject(:factory) { Quill::Factory.new(klass, :feature => feature_name )}
|
8
|
+
|
9
|
+
describe "feature" do
|
10
|
+
it "returns the feature name" do
|
11
|
+
expect(factory.feature).to be == feature_name
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "call" do
|
16
|
+
let(:container) { double(:container) }
|
17
|
+
let(:instance) { double(:instance) }
|
18
|
+
|
19
|
+
context "when the factory is not curried" do
|
20
|
+
context "when the factory has no dependencies" do
|
21
|
+
it "calls the class constructor with no arguments" do
|
22
|
+
expect(klass).to receive(:new).and_return(instance)
|
23
|
+
expect(factory.call(container)).to be == instance
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "when the factory has dependencies" do
|
28
|
+
let(:dependency_1) { double(:dependency_1) }
|
29
|
+
let(:dependency_2) { double(:dependency_2) }
|
30
|
+
|
31
|
+
subject(:factory) {
|
32
|
+
Quill::Factory.new(klass, {
|
33
|
+
:feature => feature_name,
|
34
|
+
:dependencies => [:dependency_1, :dependency_2],
|
35
|
+
})
|
36
|
+
}
|
37
|
+
|
38
|
+
it "calls the class constructor with the dependencies from the factory" do
|
39
|
+
expect(container).to receive(:[]).with(:dependency_1).and_return(dependency_1)
|
40
|
+
expect(container).to receive(:[]).with(:dependency_2).and_return(dependency_2)
|
41
|
+
expect(klass).to receive(:new).with(dependency_1, dependency_2).and_return(instance)
|
42
|
+
expect(factory.call(container)).to be == instance
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it "memoizes the constructed instance of the class" do
|
47
|
+
allow(klass).to receive(:new) { Object.new }
|
48
|
+
first = factory.call(container)
|
49
|
+
second = factory.call(container)
|
50
|
+
expect(first).to eq(second)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "when the factory is curried" do
|
55
|
+
context "when the factory has no dependencies" do
|
56
|
+
subject(:factory) {
|
57
|
+
Quill::Factory.new(klass, {
|
58
|
+
:feature => feature_name,
|
59
|
+
:curried => true,
|
60
|
+
})
|
61
|
+
}
|
62
|
+
|
63
|
+
it "returns a proc which calls the class constructor with no arguments" do
|
64
|
+
the_proc = factory.call(container)
|
65
|
+
expect(klass).to receive(:new).and_return(instance)
|
66
|
+
expect(the_proc.call).to be == instance
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "when the factory has dependencies" do
|
71
|
+
let(:dependency_1) { double(:dependency_1) }
|
72
|
+
let(:dependency_2) { double(:dependency_2) }
|
73
|
+
let(:additional_arg_1) { double(:additional_arg_1) }
|
74
|
+
let(:additional_arg_2) { double(:additional_arg_2) }
|
75
|
+
|
76
|
+
subject(:factory) {
|
77
|
+
Quill::Factory.new(klass, {
|
78
|
+
:feature => feature_name,
|
79
|
+
:dependencies => [:dependency_1, :dependency_2],
|
80
|
+
:curried => true
|
81
|
+
})
|
82
|
+
}
|
83
|
+
|
84
|
+
it "calls the class constructor with the dependencies from the factory" do
|
85
|
+
expect(container).to receive(:[]).with(:dependency_1).and_return(dependency_1)
|
86
|
+
expect(container).to receive(:[]).with(:dependency_2).and_return(dependency_2)
|
87
|
+
the_proc = factory.call(container)
|
88
|
+
expect(klass).to receive(:new).
|
89
|
+
with(dependency_1, dependency_2, additional_arg_1, additional_arg_2).
|
90
|
+
and_return(instance)
|
91
|
+
result = the_proc.call(additional_arg_1, additional_arg_2)
|
92
|
+
expect(result).to be == instance
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
it "memoizes the constructed instance of the class" do
|
97
|
+
allow(klass).to receive(:new) { Object.new }
|
98
|
+
first = factory.call(container)
|
99
|
+
second = factory.call(container)
|
100
|
+
expect(first).to eq(second)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: quill
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tim Cowlishaw
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-08-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: cucumber
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Quill helps you organize your code more easily by separating the business
|
56
|
+
of specifying a class's dependencies from the business of actually providing them,
|
57
|
+
using a simple, declerative syntax.
|
58
|
+
email:
|
59
|
+
- tim@timcowlishaw.co.uk
|
60
|
+
executables: []
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- .gitignore
|
65
|
+
- .ruby-gemset
|
66
|
+
- .ruby-version
|
67
|
+
- Gemfile
|
68
|
+
- LICENSE.txt
|
69
|
+
- README.md
|
70
|
+
- features/declaring_dependencies.feature
|
71
|
+
- features/step_definitions/declaring_dependencies_steps.rb
|
72
|
+
- features/support/env.rb
|
73
|
+
- lib/quill.rb
|
74
|
+
- lib/quill/container.rb
|
75
|
+
- lib/quill/dsl.rb
|
76
|
+
- lib/quill/factory.rb
|
77
|
+
- lib/quill/no_factory_error.rb
|
78
|
+
- lib/quill/unsatisfied_dependency_error.rb
|
79
|
+
- lib/quill/version.rb
|
80
|
+
- quill.gemspec
|
81
|
+
- spec/quill/container_spec.rb
|
82
|
+
- spec/quill/dsl_spec.rb
|
83
|
+
- spec/quill/factory_spec.rb
|
84
|
+
- spec/spec_helper.rb
|
85
|
+
homepage: http://github.com/timcowlishaw/quill
|
86
|
+
licenses:
|
87
|
+
- MIT
|
88
|
+
metadata: {}
|
89
|
+
post_install_message:
|
90
|
+
rdoc_options: []
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ! '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ! '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
requirements: []
|
104
|
+
rubyforge_project:
|
105
|
+
rubygems_version: 2.0.4
|
106
|
+
signing_key:
|
107
|
+
specification_version: 4
|
108
|
+
summary: Quill - Simple DI for your rupy applications
|
109
|
+
test_files:
|
110
|
+
- features/declaring_dependencies.feature
|
111
|
+
- features/step_definitions/declaring_dependencies_steps.rb
|
112
|
+
- features/support/env.rb
|
113
|
+
- spec/quill/container_spec.rb
|
114
|
+
- spec/quill/dsl_spec.rb
|
115
|
+
- spec/quill/factory_spec.rb
|
116
|
+
- spec/spec_helper.rb
|