injectable 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,128 @@
1
+ Injectable [![Build Status](https://secure.travis-ci.org/durran/injectable.png?branch=master&.png)](http://travis-ci.org/durran/injectable) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/durran/injectable)
2
+ ========
3
+
4
+ Injectable is an extremely simple dependency injection framework for Ruby. It's
5
+ stupid simple and experimental so don't expect support for now - but may turn out
6
+ to be something in the future.
7
+
8
+ Usage
9
+ -----
10
+
11
+ Say we have a `UserService` that has some basic logic for performing operations
12
+ related to a `User` and a `FacebookService`. We can tell the `UserService` what
13
+ its dependencies are via `Injectable` (Objects that have no dependencies do not)
14
+ need to include the module.
15
+
16
+ ```ruby
17
+ class User; end
18
+ class FacebookService; end
19
+
20
+ class UserService
21
+ include Injectable
22
+ dependencies :user, :facebook_service
23
+ end
24
+ ```
25
+
26
+ The `UserService` would then get a constructor defined that takes a `User` and
27
+ `FacebookService` as its arguments:
28
+
29
+ ```ruby
30
+ user = User.new
31
+ facebook_service = FacebookService.new
32
+ user_service = UserService.new(user, facebook_service)
33
+ ```
34
+
35
+ Reader methods are also automatically provided for each dependency:
36
+
37
+ ```ruby
38
+ user_service.user
39
+ user_service.facebook_service
40
+ ```
41
+
42
+ Say we just want to throw a bunch of objects in a container, and ask for an
43
+ object of a specific type and let the container figure out the dependencies:
44
+
45
+ ```ruby
46
+ user = User.new
47
+ facebook_service = FacebookService.new
48
+ container = Injectable::Container.new(user, facebook_service)
49
+ user_service = container.get(UserService)
50
+ ```
51
+
52
+ Since `User` and `FacebookService` take no arguments, we don't even need to
53
+ pass them into the container - it will automatically instantiate new ones:
54
+
55
+ ```ruby
56
+ container = Injectable::Container.new
57
+ user = container.get(User)
58
+ user_service = container.get(UserService)
59
+ ```
60
+
61
+ Polymorphism is not supported since we don't have interfaces in Ruby. Setter
62
+ injection is also not supported.
63
+
64
+ How about the real world
65
+ ------------------------
66
+
67
+ Let's look at the above classes, but say we're in a Rails application:
68
+
69
+ ```ruby
70
+ class User < ActiveRecord::Base
71
+ end
72
+
73
+ class FacebookService
74
+ def post_to_wall(id, message)
75
+ # ...
76
+ end
77
+ end
78
+
79
+ class UserService
80
+ include Injectable
81
+ dependencies :user, :facebook_service
82
+
83
+ def post_to_wall(message)
84
+ facebook_service.post_to_wall(user.id, message)
85
+ end
86
+ end
87
+ ```
88
+
89
+ Now in our controller, we can just create a new container the user we find
90
+ and then ask the container to do all the other work. This is more closely
91
+ related to what one might do in a real application.
92
+
93
+ ```ruby
94
+ class UsersController < ApplicationController
95
+
96
+ def post_to_wall
97
+ container.get(UserService).post_to_wall(params[:message])
98
+ respond_with container.get(User)
99
+ end
100
+
101
+ private
102
+
103
+ def container
104
+ @container ||= Injectable::Container.new(User.find(params[:id]))
105
+ end
106
+ end
107
+ ```
108
+
109
+ Copyright (c) 2012 Durran Jordan
110
+
111
+ Permission is hereby granted, free of charge, to any person obtaining
112
+ a copy of this software and associated documentation files (the
113
+ "Software"), to deal in the Software without restriction, including
114
+ without limitation the rights to use, copy, modify, merge, publish,
115
+ distribute, sublicense, and/or sell copies of the Software, and to
116
+ permit persons to whom the Software is furnished to do so, subject to
117
+ the following conditions:
118
+
119
+ The above copyright notice and this permission notice shall be
120
+ included in all copies or substantial portions of the Software.
121
+
122
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
123
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
124
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
125
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
126
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
127
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
128
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ require "bundler"
2
+ Bundler.setup
3
+
4
+ require "rake"
5
+ require "rspec/core/rake_task"
6
+
7
+ $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
8
+ require "injectable/version"
9
+
10
+ task :gem => :build
11
+ task :build do
12
+ system "gem build injectable.gemspec"
13
+ end
14
+
15
+ task :install => :build do
16
+ system "sudo gem install injectable-#{Injectable::VERSION}.gem"
17
+ end
18
+
19
+ task :release => :build do
20
+ system "git tag -a v#{Injectable::VERSION} -m 'Tagging #{Injectable::VERSION}'"
21
+ system "git push --tags"
22
+ system "gem push injectable-#{Injectable::VERSION}.gem"
23
+ system "rm injectable-#{Injectable::VERSION}.gem"
24
+ end
25
+
26
+ RSpec::Core::RakeTask.new("spec") do |spec|
27
+ spec.pattern = "spec/**/*_spec.rb"
28
+ end
29
+
30
+ task :default => :spec
@@ -0,0 +1,58 @@
1
+ # encoding: utf-8
2
+ module Injectable
3
+
4
+ # A simple container that can resolve dependencies.
5
+ #
6
+ # @since 0.0.0
7
+ class Container
8
+
9
+ # Get an instance of an object from the container with the provided class.
10
+ #
11
+ # @example Get an instance of an object for class UserService.
12
+ # container.get(UserService)
13
+ #
14
+ # @param [ Class ] klass The type of the object to return.
15
+ #
16
+ # @return [ Object ] The instantiated object.
17
+ #
18
+ # @since 0.0.0
19
+ def get(klass)
20
+ if instantiated_objects.has_key?(klass)
21
+ instantiated_objects[klass]
22
+ else
23
+ instantiated_objects[klass] = instantiate(klass)
24
+ end
25
+ end
26
+
27
+ # Create a new container with the objects needed to resolve dependencies
28
+ # and create new objects.
29
+ #
30
+ # @example Create the new container.
31
+ # Injectable::Container.new(user, user_finder)
32
+ #
33
+ # @param [ Array<Object> ] objects The dependencies.
34
+ #
35
+ # @since 0.0.0
36
+ def initialize(*objects)
37
+ objects.each do |object|
38
+ instantiated_objects[object.class] = object
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def dependencies(klass)
45
+ (Registry.signature(klass) || []).map do |dependency|
46
+ get(dependency)
47
+ end
48
+ end
49
+
50
+ def instantiate(klass)
51
+ klass.new(*dependencies(klass))
52
+ end
53
+
54
+ def instantiated_objects
55
+ @instantiated_objects ||= {}
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+ module Injectable
3
+
4
+ # Provides class level macros for setting up dependencies.
5
+ #
6
+ # @since 0.0.0
7
+ module Macros
8
+
9
+ # Sets up the dependencies for the class.
10
+ #
11
+ # @example Define a UserService that has two dependencies.
12
+ # class User; end
13
+ # class UserFinder; end
14
+ #
15
+ # class UserService
16
+ # include Injectable
17
+ # dependencies :user, :user_finder
18
+ # end
19
+ #
20
+ # @note A constructor will get created for the object that takes the same
21
+ # number or arguments as provided to the dependencies macro. The types
22
+ # of these arguments must match the "classified" name of the provided
23
+ # symbol. For example :user would be a User class, :user_finder would be
24
+ # a UserFinder class. Order matters.
25
+ #
26
+ # @param [ Array<Symbol> ] injectables The dependency list.
27
+ #
28
+ # @since 0.0.0
29
+ def dependencies(*injectables)
30
+ define_constructor(*injectables)
31
+ define_readers(*injectables)
32
+ Registry.add(self, injectables)
33
+ end
34
+
35
+ private
36
+
37
+ def define_constructor(*injectables)
38
+ names = names(*injectables)
39
+ class_eval <<-CONST
40
+ def initialize(#{names})
41
+ #{ivars(*injectables)} = #{names}
42
+ end
43
+ CONST
44
+ end
45
+
46
+ def define_readers(*injectables)
47
+ injectables.each do |dependency|
48
+ attr_reader(dependency)
49
+ end
50
+ end
51
+
52
+ def ivars(*injectables)
53
+ injectables.map { |name| "@#{name}" }.join(",")
54
+ end
55
+
56
+ def names(*injectables)
57
+ injectables.join(",")
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,45 @@
1
+ # encoding: utf-8
2
+ module Injectable
3
+
4
+ # The registry keeps track of all objects and their dependencies that need
5
+ # to be injected at construction.
6
+ #
7
+ # @since 0.0.0
8
+ module Registry
9
+ extend self
10
+
11
+ # Add a constructor method signature to the registry.
12
+ #
13
+ # @example Add a signature.
14
+ # Injectable::Registry.add(UserService, [ :user, :user_finder ])
15
+ #
16
+ # @param [ Class ] klass The class to set the constructor signature for.
17
+ # @param [ Array<Symbol> ] dependencies The dependencies of the
18
+ # constructor.
19
+ #
20
+ # @since 0.0.0
21
+ def add(klass, dependencies)
22
+ signatures[klass] = dependencies.map { |name| name.to_s.classify.constantize }
23
+ end
24
+
25
+ # Get the constructor method signature for the provided class.
26
+ #
27
+ # @example Get the constructor signature.
28
+ # Injectable::Registry.signature(UserService)
29
+ #
30
+ # @param [ Class ] klass The class to get the signature for.
31
+ #
32
+ # @return [ Array<Class> ] The constructor signature.
33
+ #
34
+ # @since 0.0.0
35
+ def signature(klass)
36
+ signatures[klass]
37
+ end
38
+
39
+ private
40
+
41
+ def signatures
42
+ @signatures ||= {}
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,4 @@
1
+ # encoding: utf-8
2
+ module Injectable
3
+ VERSION = "0.0.1"
4
+ end
data/lib/injectable.rb ADDED
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+ require "active_support/inflector"
3
+ require "injectable/container"
4
+ require "injectable/macros"
5
+ require "injectable/registry"
6
+
7
+ # Objects that include Injectable can have their dependencies satisfied by the
8
+ # container, and removes some basic boilerplate code of creating basic
9
+ # constructors that set instance variables on the class. Constructor injection
10
+ # is the only available option here.
11
+ #
12
+ # @since 0.0.0
13
+ module Injectable
14
+
15
+ class << self
16
+
17
+ # Including the Injectable module will extend the class with the necessary
18
+ # macros.
19
+ #
20
+ # @example Include the module.
21
+ # class UserService
22
+ # include Injectable
23
+ # end
24
+ #
25
+ # @param [ Class ] klass The including class.
26
+ #
27
+ # @since 0.0.0
28
+ def included(klass)
29
+ klass.extend(Macros)
30
+ end
31
+ end
32
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: injectable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Durran Jordan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3'
30
+ description: Dead simple Ruby dependency injection
31
+ email:
32
+ - durran@gmail.com
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - lib/injectable/container.rb
38
+ - lib/injectable/macros.rb
39
+ - lib/injectable/registry.rb
40
+ - lib/injectable/version.rb
41
+ - lib/injectable.rb
42
+ - README.md
43
+ - Rakefile
44
+ homepage:
45
+ licenses: []
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ segments:
57
+ - 0
58
+ hash: 3211559438392956621
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ! '>='
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ segments:
66
+ - 0
67
+ hash: 3211559438392956621
68
+ requirements: []
69
+ rubyforge_project:
70
+ rubygems_version: 1.8.24
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: Dead simple Ruby dependency injection
74
+ test_files: []