ruse 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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +120 -0
- data/Rakefile +10 -0
- data/lib/ruse.rb +8 -0
- data/lib/ruse/injector.rb +32 -0
- data/lib/ruse/version.rb +3 -0
- data/ruse.gemspec +23 -0
- data/specs/injector_spec.rb +44 -0
- data/specs/ruse_spec.rb +9 -0
- metadata +83 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ed5777f12733b8e7e6ed8fc9711354f77b93db58
|
4
|
+
data.tar.gz: 62796162f3a934ff9668cc39f029b7bb6b58947f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f5a5251bc60259ea099c2cf2f5402f8dd2b642c8b0ae736305ecfc71e28398ac911b6e68c3e017f9267ab7b1b3da5974f563d3f784ff55496e37fd4ab08fdbd1
|
7
|
+
data.tar.gz: 1a1a86387be403adfa675b87b9d0b91f3ea401939da9bd8937dddec6c4a34d3cca3948e86f4bc228f0d98e92ab9244869dcc04850542140b4cbb9768203ceb06
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Joshua Flanagan
|
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,120 @@
|
|
1
|
+
# Ruse
|
2
|
+
|
3
|
+
Ruse is a low friction dependency injection tool for ruby.
|
4
|
+
|
5
|
+
It was inspired by the injector in [angular.js](http://docs.angularjs.org/guide/di)
|
6
|
+
(and so, transitively, [Guice](https://code.google.com/p/google-guice/)).
|
7
|
+
|
8
|
+
## Usage
|
9
|
+
|
10
|
+
Create an injector at the top level of your application:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
injector = Ruse.create_injector
|
14
|
+
```
|
15
|
+
|
16
|
+
Retrieve instances via that injector:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
command = injector.get :create_order_command
|
20
|
+
#=> #<CreateOrderCommand:0x00000105cbea70>
|
21
|
+
```
|
22
|
+
|
23
|
+
## Example
|
24
|
+
|
25
|
+
Suppose you have a command that collaborates with a notifier service:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
class CreateOrderCommand
|
29
|
+
def execute(customer, order_details)
|
30
|
+
save_order(order_details)
|
31
|
+
notifier.notify(customer)
|
32
|
+
end
|
33
|
+
|
34
|
+
def notifier
|
35
|
+
@notifier ||= Notifier.new
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Notifier
|
40
|
+
def notify(customer)
|
41
|
+
# send a notification to customer
|
42
|
+
end
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
That `CreateOrderCommand` class is now tightly coupled to the `Notifier` class.
|
47
|
+
It has to know how to construct the Notifier. You would have to change
|
48
|
+
`CreateOrderCommand` to use a different notifier service.
|
49
|
+
|
50
|
+
You can improve the class by using dependency injection:
|
51
|
+
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
class CreateOrderCommand
|
55
|
+
def initialize(notifier)
|
56
|
+
@notifier = notifier
|
57
|
+
end
|
58
|
+
|
59
|
+
def execute(customer, order_details)
|
60
|
+
save_order(order_details)
|
61
|
+
@notifier.notify(customer)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
The `CreateOrderCommand` class is now open for extension, but closed for
|
67
|
+
modification. It can use a different notifier service, without any changes.
|
68
|
+
It also does not need to know how to construct a `Notifier` instance, which is
|
69
|
+
really important if `Notifier` has its own dependencies.
|
70
|
+
|
71
|
+
You have now passed the burden of creating and configuring the
|
72
|
+
`CreateOrderCommand`, the `Notifier`, and any of its dependencies on to the
|
73
|
+
caller. This is where an Inversion of Control/Dependency Injection tool becomes
|
74
|
+
valuable:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
command = injector.get "CreateOrderCommand"
|
78
|
+
```
|
79
|
+
|
80
|
+
The tool did the tedious work of identifying and populating the dependencies,
|
81
|
+
all the way down the object graph.
|
82
|
+
|
83
|
+
## Instance Resolution
|
84
|
+
|
85
|
+
Dependencies are determined by the identifiers used in constructure parameters.
|
86
|
+
This was lifted directly from angular.js, and I believe may be the key to
|
87
|
+
reducing the overhead in using a tool like this. Your dependency consuming
|
88
|
+
classes do not have to be annotated or registered in any way.
|
89
|
+
|
90
|
+
In this early alpha state, identifiers are resolved to types through simple
|
91
|
+
string manipulation (similiar to ActiveSupport's `classify` and `constantize`).
|
92
|
+
That means you can get an instance of `SomeService` by requesting
|
93
|
+
`"SomeService"`, `"some_service"` or `:some_service`.
|
94
|
+
|
95
|
+
In the future, I can imagine a simple configuration mechanism that lets you
|
96
|
+
resolve an identifier to some other type, so `"notifier"` resolves to
|
97
|
+
`EmailNotifier`.
|
98
|
+
|
99
|
+
## Installation
|
100
|
+
|
101
|
+
Add this line to your application's Gemfile:
|
102
|
+
|
103
|
+
gem 'ruse'
|
104
|
+
|
105
|
+
And then execute:
|
106
|
+
|
107
|
+
$ bundle
|
108
|
+
|
109
|
+
Or install it yourself as:
|
110
|
+
|
111
|
+
$ gem install ruse
|
112
|
+
|
113
|
+
|
114
|
+
## Contributing
|
115
|
+
|
116
|
+
1. [Fork it](http://github.com/joshuaflanagan/ruse/fork)
|
117
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
118
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
119
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
120
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/lib/ruse.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
module Ruse
|
2
|
+
class Injector
|
3
|
+
def get(identifier)
|
4
|
+
type = resolve_type identifier
|
5
|
+
args = resolve_dependencies type
|
6
|
+
type.new *args
|
7
|
+
end
|
8
|
+
|
9
|
+
def resolve_type(identifier)
|
10
|
+
type_name = classify(identifier)
|
11
|
+
unless Object.const_defined?(type_name)
|
12
|
+
raise UnknownServiceError.new(type_name)
|
13
|
+
end
|
14
|
+
Object.const_get type_name
|
15
|
+
end
|
16
|
+
|
17
|
+
def resolve_dependencies(type)
|
18
|
+
type.instance_method(:initialize).parameters.map{|_, identifier|
|
19
|
+
get identifier
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def classify(term)
|
24
|
+
# lifted from active_support gem: lib/active_support/inflector/methods.rb
|
25
|
+
string = term.to_s
|
26
|
+
string = string.sub(/^[a-z\d]*/) { $&.capitalize }
|
27
|
+
string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{ $2.capitalize}" }.gsub('/', '::')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class UnknownServiceError < StandardError; end
|
32
|
+
end
|
data/lib/ruse/version.rb
ADDED
data/ruse.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ruse/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ruse"
|
8
|
+
spec.version = Ruse::VERSION
|
9
|
+
spec.authors = ["Joshua Flanagan"]
|
10
|
+
spec.email = ["joshuaflanagan@gmail.com"]
|
11
|
+
spec.summary = %q{Ruse}
|
12
|
+
spec.description = %q{Ruse}
|
13
|
+
spec.homepage = "https://github.com/joshuaflanagan/ruse"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
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.6"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'minitest/spec'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'ruse/injector'
|
4
|
+
|
5
|
+
describe Ruse::Injector do
|
6
|
+
it "retrieves an instance it can infer from an identifier" do
|
7
|
+
injector = Ruse::Injector.new
|
8
|
+
injector.get("SomeService").must_be_instance_of(SomeService)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "raises UnknownServiceError for an identifier it cannot resolve" do
|
12
|
+
injector = Ruse::Injector.new
|
13
|
+
->{
|
14
|
+
injector.get("cannot_be_resolved")
|
15
|
+
}.must_raise(Ruse::UnknownServiceError)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "populates dependencies for the instance it retrieves" do
|
19
|
+
injector = Ruse::Injector.new
|
20
|
+
instance = injector.get("ServiceConsumer")
|
21
|
+
instance.must_be_instance_of(ServiceConsumer)
|
22
|
+
instance.service1.must_be_instance_of(SomeService)
|
23
|
+
instance.service2.must_be_instance_of(OtherService)
|
24
|
+
end
|
25
|
+
|
26
|
+
class SomeService; end
|
27
|
+
class OtherService; end
|
28
|
+
|
29
|
+
class ServiceConsumer
|
30
|
+
attr_reader :service1, :service2
|
31
|
+
def initialize(some_service, other_service)
|
32
|
+
@service1 = some_service
|
33
|
+
@service2 = other_service
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# classify will be moved
|
39
|
+
describe "classify" do
|
40
|
+
it "converts an underscored_term to PascalCase" do
|
41
|
+
resolver = Ruse::Injector.new
|
42
|
+
resolver.classify("camel_case").must_equal("CamelCase")
|
43
|
+
end
|
44
|
+
end
|
data/specs/ruse_spec.rb
ADDED
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruse
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joshua Flanagan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-02-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
type: :development
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ~>
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.6'
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
type: :development
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - '>='
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: Ruse
|
42
|
+
email:
|
43
|
+
- joshuaflanagan@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- .gitignore
|
49
|
+
- Gemfile
|
50
|
+
- LICENSE.txt
|
51
|
+
- README.md
|
52
|
+
- Rakefile
|
53
|
+
- lib/ruse.rb
|
54
|
+
- lib/ruse/injector.rb
|
55
|
+
- lib/ruse/version.rb
|
56
|
+
- ruse.gemspec
|
57
|
+
- specs/injector_spec.rb
|
58
|
+
- specs/ruse_spec.rb
|
59
|
+
homepage: https://github.com/joshuaflanagan/ruse
|
60
|
+
licenses:
|
61
|
+
- MIT
|
62
|
+
metadata: {}
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - '>='
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
requirements: []
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 2.1.11
|
80
|
+
signing_key:
|
81
|
+
specification_version: 4
|
82
|
+
summary: Ruse
|
83
|
+
test_files: []
|