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.
@@ -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
@@ -0,0 +1,18 @@
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
18
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ruse.gemspec
4
+ gemspec
@@ -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.
@@ -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
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ task default: :test
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs.push "lib"
8
+ t.test_files = FileList['specs/*_spec.rb']
9
+ t.verbose = true
10
+ end
@@ -0,0 +1,8 @@
1
+ require "ruse/injector"
2
+ require "ruse/version"
3
+
4
+ module Ruse
5
+ def self.create_injector
6
+ Ruse::Injector.new
7
+ end
8
+ end
@@ -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
@@ -0,0 +1,3 @@
1
+ module Ruse
2
+ VERSION = "0.0.1"
3
+ end
@@ -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
@@ -0,0 +1,9 @@
1
+ require 'minitest/spec'
2
+ require 'minitest/autorun'
3
+ require 'ruse'
4
+
5
+ describe Ruse do
6
+ it "can create an injector" do
7
+ Ruse.create_injector.must_be_kind_of Ruse::Injector
8
+ end
9
+ end
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: []