rails-ioc 0.1.0
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.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/README.markdown +155 -0
- data/Rakefile +6 -0
- data/lib/rails-ioc.rb +15 -0
- data/lib/rails-ioc/controller_extension.rb +13 -0
- data/lib/rails-ioc/dependencies.rb +66 -0
- data/lib/rails-ioc/dependency_constructor.rb +17 -0
- data/lib/rails-ioc/dependency_injector.rb +15 -0
- data/lib/rails-ioc/errors.rb +14 -0
- data/lib/rails-ioc/rspec_extension.rb +16 -0
- data/lib/rails-ioc/version.rb +3 -0
- data/rails-ioc.gemspec +24 -0
- data/spec/fixtures/config/dependencies/incrementing_test.rb +5 -0
- data/spec/fixtures/config/dependencies/production.rb +5 -0
- data/spec/fixtures/config/dependencies/test.rb +5 -0
- data/spec/rails-ioc/controller_extension_spec.rb +32 -0
- data/spec/rails-ioc/dependencies_spec.rb +163 -0
- data/spec/rails-ioc/dependency_constructor_spec.rb +35 -0
- data/spec/rails-ioc/dependency_injector_spec.rb +20 -0
- data/spec/rails-ioc/rspec_extension_spec.rb +25 -0
- data/spec/spec_helper.rb +62 -0
- data/spec/spec_helper_spec_spec_spec_spec.rb +46 -0
- metadata +98 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
## Quickstart
|
2
|
+
|
3
|
+
Gemfile:
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
gem 'rails-ioc'
|
7
|
+
```
|
8
|
+
|
9
|
+
config/dependencies/development.rb:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
RailsIOC::Dependencies.define do
|
13
|
+
prototype :ccv, CreditCardValidator, "Visa", "Mastercard"
|
14
|
+
prototype :payment_gateway, DummyPaymentGateway, ref(:ccv)
|
15
|
+
|
16
|
+
controller PaymentsController, {
|
17
|
+
payment_gateway: ref(:payment_gateway),
|
18
|
+
}
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
22
|
+
app/controllers/payments_controller.rb:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
class PaymentsController < ApplicationController
|
26
|
+
def accept_payment
|
27
|
+
@payment_gateway.process(params[:credit_card])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
32
|
+
## Background
|
33
|
+
|
34
|
+
Inversion of control by dependency injection is widely used in large software systems and is generally accepted as being a "good idea". If the terms are unfamiliar, do some research before going any further. These are good places to start:
|
35
|
+
|
36
|
+
- http://en.wikipedia.org/wiki/Inversion_of_control
|
37
|
+
- http://martinfowler.com/articles/injection.html
|
38
|
+
|
39
|
+
The practice as described in the articles above is not particularly common in Ruby applications. Jamis Buck, author of the erstwhile DI framework [Copland](http://copland.rubyforge.org) makes a [compelling argument](http://weblog.jamisbuck.org/2008/11/9/legos-play-doh-and-programming) against it. If you have to ask the question "will I need IOC for my next Rails app", the answer is almost certainly "No". The core components of a standard Rails app usually interact politely and can generally be tested reasonably easily in most apps.
|
40
|
+
|
41
|
+
Still, having run into several Rails projects which dealt with dependencies beyond a simple ActiveRecord database connection (payment gateways, email services, version control systems, image processing services and more) I realised that I was reminiscing fondly about Java and .NET's various containers for two reasons:
|
42
|
+
|
43
|
+
1. I really hate mocking calls to MyService#new to force a controller to use a stub implementation in a unit test.
|
44
|
+
2. There is no clean way to switch dependencies out on a per-environment basis (to use the testable version of a payment gateway in the staging environment, for example).
|
45
|
+
|
46
|
+
RailsIOC attempts to make these problems less painful for applications with complex interactions with external services by providing a very lightweight way to define dependencies and inject them into ActionController. The patterns will be familiar to anyone who has used [Spring](http://www.springsource.org/documentation) or [Unity](http://msdn.microsoft.com/en-us/library/dd203319.aspx). Where possible, RailsIOC enforces constructor injection. The exception to this rule is the creation of controllers, where it avoids interfering with Rails' own instantiation and injects dependencies as local @variables.
|
47
|
+
|
48
|
+
## Cleaner Testing
|
49
|
+
### Before
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
#Controller
|
53
|
+
@gateway = Gateway.new(ServiceA.new, ServiceB.new)
|
54
|
+
@gateway.do_something!
|
55
|
+
|
56
|
+
#RSpec
|
57
|
+
@svc_a = mock(ServiceA)
|
58
|
+
@svc_b = mock(ServiceB)
|
59
|
+
ServiceA.stub(:new).and_return(@svc_a)
|
60
|
+
ServiceB.stub(:new).and_return(@svc_b)
|
61
|
+
@gateway = mock(Gateway)
|
62
|
+
@gateway.should_receive(:do_something!).and_return(12345)
|
63
|
+
Gateway.stub(:new).with(@svc_a, @svc_b).and_return(@gateway)
|
64
|
+
```
|
65
|
+
|
66
|
+
### After
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
#Controller
|
70
|
+
@gateway.do_something!
|
71
|
+
|
72
|
+
#RSpec
|
73
|
+
@gateway = mock(Gateway)
|
74
|
+
@gateway.should_receive(:do_something!).and_return(12345)
|
75
|
+
controller_dependencies(gateway: @gateway)
|
76
|
+
```
|
77
|
+
|
78
|
+
## Customise Dependencies Per-Environment
|
79
|
+
### Before
|
80
|
+
|
81
|
+
PaymentController:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
class PaymentsController < ApplicationController
|
85
|
+
def accept_payment
|
86
|
+
if Rails.env.development? || Rails.env.test?
|
87
|
+
@credit_card_validator = BogusCardValidator.new
|
88
|
+
else
|
89
|
+
@credit_card_validator = RealCardValidator.new
|
90
|
+
end
|
91
|
+
if Rails.env.production?
|
92
|
+
@gateway = RealPaymentGateway.new
|
93
|
+
elsif Rails.env.staging?
|
94
|
+
@gateway = RealPaymentGateway.new(:use_testing_url)
|
95
|
+
else
|
96
|
+
@gateway = BogusPaymentGateway.new
|
97
|
+
end
|
98
|
+
card = @credit_card_validator.validate(params[:card])
|
99
|
+
@gateway.process(card)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
```
|
103
|
+
|
104
|
+
### After
|
105
|
+
|
106
|
+
|
107
|
+
app/controllers/payments_controller:
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
class PaymentsController < ApplicationController
|
111
|
+
def accept_payment
|
112
|
+
card = @credit_card_validator.validate(params[:card])
|
113
|
+
@gateway.process(card)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
118
|
+
config/dependencies/production.rb:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
RailsIOC::Dependencies.define do
|
122
|
+
prototype :payment_gateway, RealPaymentGateway
|
123
|
+
prototype :credit_card_validator, RealCardValidator
|
124
|
+
|
125
|
+
controller MyController, {
|
126
|
+
gateway: ref(:payment_gateway)
|
127
|
+
credit_card_validator: ref(:credit_card_validator)
|
128
|
+
}
|
129
|
+
end
|
130
|
+
```
|
131
|
+
|
132
|
+
config/dependencies/staging.rb:
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
RailsIOC::Dependencies.define do
|
136
|
+
inherit_environment(:production)
|
137
|
+
|
138
|
+
controller MyController, {
|
139
|
+
gateway: prototype(:payment_gateway, RealPaymentGateway, :use_test_url)
|
140
|
+
}
|
141
|
+
end
|
142
|
+
```
|
143
|
+
|
144
|
+
config/dependencies/development.rb:
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
RailsIOC::Dependencies.define do
|
148
|
+
inherit_environment(:production)
|
149
|
+
|
150
|
+
controller MyController, {
|
151
|
+
gateway: singleton(BogusPaymentGateway),
|
152
|
+
credit_card_validator: singleton(BogusCardValidator)
|
153
|
+
}
|
154
|
+
end
|
155
|
+
```
|
data/Rakefile
ADDED
data/lib/rails-ioc.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "rails-ioc/version"
|
2
|
+
require "rails-ioc/errors"
|
3
|
+
require "rails-ioc/dependencies"
|
4
|
+
require "rails-ioc/dependency_constructor"
|
5
|
+
require "rails-ioc/dependency_injector"
|
6
|
+
|
7
|
+
raise RailsIOC::NoRailsError.new(:Rails) unless defined?(Rails)
|
8
|
+
raise RailsIOC::NoRailsError.new(:ActionController) unless defined?(ActionController)
|
9
|
+
require "rails-ioc/controller_extension"
|
10
|
+
ActionController::Base.send(:include, RailsIOC::ControllerExtension)
|
11
|
+
|
12
|
+
if defined?(RSpec)
|
13
|
+
require "rails-ioc/rspec_extension"
|
14
|
+
Object.send(:include, RailsIOC::RSpecExtension)
|
15
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module RailsIOC
|
2
|
+
module ControllerExtension
|
3
|
+
def self.included(controller)
|
4
|
+
controller.before_filter :inject_dependencies
|
5
|
+
end
|
6
|
+
|
7
|
+
def inject_dependencies
|
8
|
+
RailsIOC::Dependencies.load!
|
9
|
+
dependencies = RailsIOC::Dependencies.controllers[self.class] || {}
|
10
|
+
RailsIOC::DependencyInjector.new(self).inject(dependencies)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module RailsIOC
|
2
|
+
class Dependencies
|
3
|
+
def self.reset!
|
4
|
+
@dependencies = {}
|
5
|
+
@controllers = {}
|
6
|
+
@loaded = false
|
7
|
+
end
|
8
|
+
self.reset!
|
9
|
+
|
10
|
+
def self.load!
|
11
|
+
self.reset! unless Rails.application.config.cache_classes
|
12
|
+
if !@loaded
|
13
|
+
inherit_environment(Rails.env)
|
14
|
+
@loaded = true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.define(&definition)
|
19
|
+
class_exec(&definition)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.inherit_environment(env)
|
23
|
+
load File.join(Rails.root, "config", "dependencies", "#{env}.rb")
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.controller(klass, dependencies)
|
27
|
+
@controllers[klass] ||= {}
|
28
|
+
@controllers[klass].merge!(dependencies)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.singleton(*args)
|
32
|
+
store_dependency(args) do |klass, constructor_args|
|
33
|
+
if klass.instance_of?(Class)
|
34
|
+
DependencyConstructor.new(klass, caller).construct(constructor_args)
|
35
|
+
else
|
36
|
+
klass
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.prototype(*args)
|
42
|
+
store_dependency(args) do |klass, constructor_args|
|
43
|
+
definition_backtrace = caller
|
44
|
+
-> { DependencyConstructor.new(klass, definition_backtrace).construct(constructor_args) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.ref(name)
|
49
|
+
@dependencies[name] || raise(MissingReferenceError.new(name))
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.controllers
|
53
|
+
@controllers
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def self.store_dependency(args)
|
59
|
+
if args.first.is_a?(Symbol)
|
60
|
+
@dependencies[args.shift] = yield(args.shift, args)
|
61
|
+
else
|
62
|
+
yield(args.shift, args)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module RailsIOC
|
2
|
+
class DependencyConstructor
|
3
|
+
def initialize(klass, definition_backtrace)
|
4
|
+
@klass = klass
|
5
|
+
@definition_backtrace = definition_backtrace
|
6
|
+
end
|
7
|
+
|
8
|
+
def construct(dependencies)
|
9
|
+
begin
|
10
|
+
@klass.new(*dependencies.map { |x| x.is_a?(Proc) ? x.call : x })
|
11
|
+
rescue ArgumentError => e
|
12
|
+
e.set_backtrace(@definition_backtrace)
|
13
|
+
raise e
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module RailsIOC
|
2
|
+
class DependencyInjector
|
3
|
+
def initialize(target)
|
4
|
+
@target = target
|
5
|
+
end
|
6
|
+
|
7
|
+
def inject(dependencies)
|
8
|
+
dependencies.each do |field, value|
|
9
|
+
value = value.call if value.is_a?(Proc)
|
10
|
+
@target.instance_variable_set("@#{field}", value)
|
11
|
+
end
|
12
|
+
@target
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module RailsIOC
|
2
|
+
class BaseError < StandardError; end
|
3
|
+
class NoRailsError < BaseError
|
4
|
+
def initialize(missing_class)
|
5
|
+
super("#{missing_class} is not defined. RailsIOC can only be used within a Rails application.")
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class MissingReferenceError < BaseError
|
10
|
+
def initialize(name)
|
11
|
+
super("No dependency defined with name :#{name}.")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module RailsIOC
|
2
|
+
module RSpecExtension
|
3
|
+
def self.included(klass)
|
4
|
+
RSpec.configure do |config|
|
5
|
+
config.before(:each) { RailsIOC::Dependencies.reset! }
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def controller_dependencies(dependencies)
|
10
|
+
klass = controller.class
|
11
|
+
Dependencies.define do
|
12
|
+
controller klass, dependencies
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/rails-ioc.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "rails-ioc/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "rails-ioc"
|
7
|
+
s.version = RailsIOC::VERSION
|
8
|
+
s.authors = ["Rick Grundy"]
|
9
|
+
s.email = ["rick@rickgrundy.com"]
|
10
|
+
s.homepage = "http://www.github.com/rickgrundy/rails-ioc"
|
11
|
+
s.summary = "Simple dependency injection for Rails."
|
12
|
+
s.description = "Simple dependency injection for Rails."
|
13
|
+
|
14
|
+
s.rubyforge_project = "rails-ioc"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_development_dependency "rspec"
|
22
|
+
# specify any dependencies here; for example:
|
23
|
+
# s.add_runtime_dependency "rest-client"
|
24
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.expand_path("../../spec_helper.rb", __FILE__)
|
2
|
+
|
3
|
+
describe RailsIOC::ControllerExtension do
|
4
|
+
class ExtendedController
|
5
|
+
def self.before_filter(method_name)
|
6
|
+
@@before_filter_method = method_name
|
7
|
+
end
|
8
|
+
|
9
|
+
def trigger_before_filter!
|
10
|
+
self.send(@@before_filter_method)
|
11
|
+
end
|
12
|
+
|
13
|
+
include RailsIOC::ControllerExtension
|
14
|
+
end
|
15
|
+
|
16
|
+
it "injects pre-defined dependencies" do
|
17
|
+
Rails.application.config.cache_classes = true
|
18
|
+
RailsIOC::Dependencies.instance_variable_set :@loaded, true
|
19
|
+
|
20
|
+
RailsIOC::Dependencies.define do
|
21
|
+
controller ExtendedController, {
|
22
|
+
a_number: 123,
|
23
|
+
a_string: "Hello"
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
controller = ExtendedController.new
|
28
|
+
controller.trigger_before_filter!
|
29
|
+
controller.instance_variable_get(:@a_number).should == 123
|
30
|
+
controller.instance_variable_get(:@a_string).should == "Hello"
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require File.expand_path("../../spec_helper.rb", __FILE__)
|
2
|
+
|
3
|
+
describe RailsIOC::Dependencies do
|
4
|
+
class TestListNode
|
5
|
+
attr_reader :next_node
|
6
|
+
def initialize(next_node=nil)
|
7
|
+
@next_node = next_node
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class TestController
|
12
|
+
attr_reader :foo, :bar
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "singletons" do
|
16
|
+
it "creates and references singletons from a class and constructor args" do
|
17
|
+
RailsIOC::Dependencies.define do
|
18
|
+
singleton :second_node, TestListNode
|
19
|
+
singleton :first_node, TestListNode, ref(:second_node)
|
20
|
+
end
|
21
|
+
|
22
|
+
first_node = RailsIOC::Dependencies.ref(:first_node)
|
23
|
+
first_node.should be_a TestListNode
|
24
|
+
first_node.should === RailsIOC::Dependencies.ref(:first_node)
|
25
|
+
first_node.next_node.should === RailsIOC::Dependencies.ref(:second_node)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "creates unreferenced singletons from a class and constructor args" do
|
29
|
+
RailsIOC::Dependencies.define do
|
30
|
+
singleton :first_node, TestListNode, singleton(TestListNode)
|
31
|
+
end
|
32
|
+
|
33
|
+
first_node = RailsIOC::Dependencies.ref(:first_node)
|
34
|
+
first_node.should be_a(TestListNode)
|
35
|
+
first_node.next_node.should be_a TestListNode
|
36
|
+
end
|
37
|
+
|
38
|
+
it "does not require a class for simple types" do
|
39
|
+
RailsIOC::Dependencies.define do
|
40
|
+
singleton :a_number, 12345
|
41
|
+
end
|
42
|
+
|
43
|
+
RailsIOC::Dependencies.ref(:a_number).should == 12345
|
44
|
+
end
|
45
|
+
|
46
|
+
it "raises an error containing information about original definition if the wrong number of constructor args are defined" do
|
47
|
+
defining_line = __LINE__ + 3
|
48
|
+
-> {
|
49
|
+
RailsIOC::Dependencies.define do
|
50
|
+
singleton :a_string, String, "Too", "many", "args"
|
51
|
+
end
|
52
|
+
}.should raise_error(ArgumentError) { |e|
|
53
|
+
e.backtrace.find { |l| l =~ /dependencies_spec.rb:#{defining_line}/ }.should_not be_nil
|
54
|
+
}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "prototypes" do
|
59
|
+
it "creates and references prototypes from a class and constructor args" do
|
60
|
+
RailsIOC::Dependencies.define do
|
61
|
+
prototype :second_node, TestListNode
|
62
|
+
prototype :first_node, TestListNode, ref(:second_node)
|
63
|
+
end
|
64
|
+
|
65
|
+
ref = RailsIOC::Dependencies.ref(:first_node)
|
66
|
+
ref.should be_a Proc
|
67
|
+
first_node = ref.call
|
68
|
+
first_node.should be_a TestListNode
|
69
|
+
first_node.should_not === ref.call
|
70
|
+
|
71
|
+
first_node.next_node.should be_a TestListNode
|
72
|
+
first_node.next_node.should_not === RailsIOC::Dependencies.ref(:second_node).call
|
73
|
+
end
|
74
|
+
|
75
|
+
it "creates unreferenced prototypes from a class and constructor args" do
|
76
|
+
RailsIOC::Dependencies.define do
|
77
|
+
prototype :first_node, TestListNode, prototype(TestListNode)
|
78
|
+
end
|
79
|
+
|
80
|
+
ref = RailsIOC::Dependencies.ref(:first_node)
|
81
|
+
ref.should be_a Proc
|
82
|
+
first_node = ref.call
|
83
|
+
first_node.should be_a(TestListNode)
|
84
|
+
first_node.next_node.should be_a TestListNode
|
85
|
+
end
|
86
|
+
|
87
|
+
it "raises an error containing information about original definition if the wrong number of constructor args are defined" do
|
88
|
+
defining_line = __LINE__ + 2
|
89
|
+
RailsIOC::Dependencies.define do
|
90
|
+
prototype :a_string, String, "Too", "many", "args"
|
91
|
+
end
|
92
|
+
|
93
|
+
-> { RailsIOC::Dependencies.ref(:a_string).call }.should raise_error(ArgumentError) { |e|
|
94
|
+
e.backtrace.find { |l| l =~ /dependencies_spec.rb:#{defining_line}/ }.should_not be_nil
|
95
|
+
}
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "controllers" do
|
100
|
+
it "stores controller dependencies for later injection by Rails' before_filter" do
|
101
|
+
RailsIOC::Dependencies.define do
|
102
|
+
prototype :second_node, TestListNode
|
103
|
+
singleton :first_node, TestListNode, ref(:second_node)
|
104
|
+
|
105
|
+
controller TestController, {
|
106
|
+
foo: ref(:first_node),
|
107
|
+
bar: singleton("The Sheep Says Bar")
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
controller = TestController.new
|
112
|
+
dependencies = RailsIOC::Dependencies.controllers[TestController]
|
113
|
+
RailsIOC::DependencyInjector.new(controller).inject(dependencies)
|
114
|
+
|
115
|
+
controller.foo.should === RailsIOC::Dependencies.ref(:first_node)
|
116
|
+
controller.bar.should == "The Sheep Says Bar"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe "loading dependencies file" do
|
121
|
+
before(:each) do
|
122
|
+
GlobalCounter.zero!
|
123
|
+
end
|
124
|
+
|
125
|
+
it "reloads the file if cache_classes is false" do
|
126
|
+
Rails.env = "incrementing_test"
|
127
|
+
Rails.application.config.cache_classes = false
|
128
|
+
|
129
|
+
RailsIOC::Dependencies.load!
|
130
|
+
RailsIOC::Dependencies.ref(:counter).should == 1
|
131
|
+
|
132
|
+
RailsIOC::Dependencies.load!
|
133
|
+
RailsIOC::Dependencies.ref(:counter).should == 2
|
134
|
+
end
|
135
|
+
|
136
|
+
it "does not reload the file if cache_classes is true" do
|
137
|
+
Rails.env = "incrementing_test"
|
138
|
+
Rails.application.config.cache_classes = true
|
139
|
+
|
140
|
+
RailsIOC::Dependencies.load!
|
141
|
+
RailsIOC::Dependencies.ref(:counter).should == 1
|
142
|
+
|
143
|
+
RailsIOC::Dependencies.load!
|
144
|
+
RailsIOC::Dependencies.ref(:counter).should == 1
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe "inheriting environments" do
|
149
|
+
it "loads the corresponding dependencies file" do
|
150
|
+
RailsIOC::Dependencies.inherit_environment("production")
|
151
|
+
RailsIOC::Dependencies.ref(:env_string).should == "Production"
|
152
|
+
RailsIOC::Dependencies.ref(:foo_string).should == "Foo"
|
153
|
+
RailsIOC::Dependencies.ref(:production_only_string).should == "This comes from production.rb"
|
154
|
+
-> { RailsIOC::Dependencies.ref(:test_only_string) }.should raise_error RailsIOC::MissingReferenceError
|
155
|
+
|
156
|
+
RailsIOC::Dependencies.inherit_environment("test")
|
157
|
+
RailsIOC::Dependencies.ref(:env_string).should == "Test"
|
158
|
+
RailsIOC::Dependencies.ref(:foo_string).should == "Foo"
|
159
|
+
RailsIOC::Dependencies.ref(:production_only_string).should == "This comes from production.rb"
|
160
|
+
RailsIOC::Dependencies.ref(:test_only_string).should == "This comes from test.rb"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require File.expand_path("../../spec_helper.rb", __FILE__)
|
2
|
+
|
3
|
+
describe RailsIOC::DependencyConstructor do
|
4
|
+
class ConstructorTestClass
|
5
|
+
attr_reader :foo, :bar
|
6
|
+
def initialize(foo, bar)
|
7
|
+
@foo, @bar = foo, bar
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
it "constructs the given class" do
|
12
|
+
instance = RailsIOC::DependencyConstructor.new(ConstructorTestClass, []).construct([123, :abc])
|
13
|
+
instance.foo.should == 123
|
14
|
+
instance.bar.should == :abc
|
15
|
+
end
|
16
|
+
|
17
|
+
it "calls any procs which are provided in constructor args" do
|
18
|
+
returns_7 = -> { 3 + 4 }
|
19
|
+
instance = RailsIOC::DependencyConstructor.new(ConstructorTestClass, []).construct([returns_7, :abc])
|
20
|
+
instance.foo.should == 7
|
21
|
+
instance.bar.should == :abc
|
22
|
+
end
|
23
|
+
|
24
|
+
it "sets the provided backtrace in the event of an ArgumentError" do
|
25
|
+
constructor = RailsIOC::DependencyConstructor.new(ConstructorTestClass, ["my_backtrace"])
|
26
|
+
|
27
|
+
-> { constructor.construct([]) }.should raise_error(ArgumentError) do |e|
|
28
|
+
e.backtrace.should == ["my_backtrace"]
|
29
|
+
end
|
30
|
+
|
31
|
+
-> { constructor.construct(nil) }.should raise_error do |e|
|
32
|
+
e.backtrace.should_not == ["my_backtrace"]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.expand_path("../../spec_helper.rb", __FILE__)
|
2
|
+
|
3
|
+
describe RailsIOC::DependencyInjector do
|
4
|
+
class InjectorTestClass
|
5
|
+
attr_reader :foo, :bar
|
6
|
+
end
|
7
|
+
|
8
|
+
it "injects named variables for the given class" do
|
9
|
+
instance = RailsIOC::DependencyInjector.new(InjectorTestClass.new).inject(foo: 123, bar: :abc)
|
10
|
+
instance.foo.should == 123
|
11
|
+
instance.bar.should == :abc
|
12
|
+
end
|
13
|
+
|
14
|
+
it "calls any procs which are provided in constructor args" do
|
15
|
+
returns_7 = -> { 3 + 4 }
|
16
|
+
instance = RailsIOC::DependencyInjector.new(InjectorTestClass.new).inject(foo: returns_7, bar: :abc)
|
17
|
+
instance.foo.should == 7
|
18
|
+
instance.bar.should == :abc
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.expand_path("../../spec_helper.rb", __FILE__)
|
2
|
+
|
3
|
+
describe RailsIOC::RSpecExtension do
|
4
|
+
class MyController; end
|
5
|
+
def controller; MyController.new; end
|
6
|
+
|
7
|
+
describe "resetting dependencies between tests" do
|
8
|
+
after(:each) do
|
9
|
+
RailsIOC::Dependencies.define { singleton :defined?, true }
|
10
|
+
end
|
11
|
+
|
12
|
+
it "resets before this test" do
|
13
|
+
-> { RailsIOC::Dependencies.ref(:defined?) }.should raise_error RailsIOC::MissingReferenceError
|
14
|
+
end
|
15
|
+
|
16
|
+
it "also resets before this test" do
|
17
|
+
-> { RailsIOC::Dependencies.ref(:defined?) }.should raise_error RailsIOC::MissingReferenceError
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it "provides a shorthand way to define controller dependencies" do
|
22
|
+
controller_dependencies(who_am_i: "The Walrus")
|
23
|
+
RailsIOC::Dependencies.controllers[MyController][:who_am_i].should == "The Walrus"
|
24
|
+
end
|
25
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
def ignoring_warnings
|
2
|
+
verbosity = $VERBOSE
|
3
|
+
$VERBOSE = nil
|
4
|
+
yield
|
5
|
+
ensure
|
6
|
+
$VERBOSE = verbosity
|
7
|
+
end
|
8
|
+
|
9
|
+
class GlobalCounter
|
10
|
+
def self.zero!
|
11
|
+
@count = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.increment!
|
15
|
+
@count += 1
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.count
|
19
|
+
@count
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class AdHocObject
|
24
|
+
def initialize
|
25
|
+
@store = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def method_missing(name, *args)
|
29
|
+
if name =~ /=$/
|
30
|
+
@store[name.to_s.sub(/=|\s/, "").to_sym] = args.first
|
31
|
+
else
|
32
|
+
@store[name]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module FakeActionController
|
38
|
+
class Base
|
39
|
+
def self.before_filter(method_name); end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def reset_fake_dependencies!
|
44
|
+
ignoring_warnings do
|
45
|
+
Object.send(:const_set, :Rails, AdHocObject.new)
|
46
|
+
Rails.application = AdHocObject.new
|
47
|
+
Rails.application.config = AdHocObject.new
|
48
|
+
Rails.root = File.expand_path("../fixtures", __FILE__)
|
49
|
+
|
50
|
+
Object.send(:const_set, :ActionController, FakeActionController)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
RSpec.configure do |config|
|
55
|
+
config.before(:each) do
|
56
|
+
GlobalCounter.zero!
|
57
|
+
reset_fake_dependencies!
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
reset_fake_dependencies!
|
62
|
+
require File.expand_path("../../lib/rails-ioc.rb", __FILE__)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require File.expand_path("../spec_helper.rb", __FILE__)
|
2
|
+
|
3
|
+
describe "Spec Helper" do
|
4
|
+
it "provides a global counter" do
|
5
|
+
3.times { GlobalCounter.increment! }
|
6
|
+
GlobalCounter.count.should == 3
|
7
|
+
GlobalCounter.zero!
|
8
|
+
GlobalCounter.count.should == 0
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "zeroing the global counter between tests" do
|
12
|
+
after(:each) { GlobalCounter.increment! }
|
13
|
+
|
14
|
+
it "zeroes before this test" do
|
15
|
+
GlobalCounter.count.should == 0
|
16
|
+
end
|
17
|
+
|
18
|
+
it "also zeroes before this test" do
|
19
|
+
GlobalCounter.count.should == 0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it "simulates Rails.application.config to allow testing outside a Rails app" do
|
24
|
+
Rails.application.config.kittens = "Small Cats"
|
25
|
+
Rails.application.config.kittens.should == "Small Cats"
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "resetting fake Rails environment between tests" do
|
29
|
+
after(:each) do
|
30
|
+
ignoring_warnings do
|
31
|
+
Rails = nil
|
32
|
+
ActionController = nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it "resets Rails before this test" do
|
37
|
+
ActionController.should_not be_nil
|
38
|
+
Rails.application.should_not be_nil
|
39
|
+
end
|
40
|
+
|
41
|
+
it "resets Rails before this test too" do
|
42
|
+
ActionController.should_not be_nil
|
43
|
+
Rails.application.should_not be_nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rails-ioc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Rick Grundy
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-08-29 00:00:00 +10:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: rspec
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "0"
|
25
|
+
type: :development
|
26
|
+
version_requirements: *id001
|
27
|
+
description: Simple dependency injection for Rails.
|
28
|
+
email:
|
29
|
+
- rick@rickgrundy.com
|
30
|
+
executables: []
|
31
|
+
|
32
|
+
extensions: []
|
33
|
+
|
34
|
+
extra_rdoc_files: []
|
35
|
+
|
36
|
+
files:
|
37
|
+
- .gitignore
|
38
|
+
- Gemfile
|
39
|
+
- README.markdown
|
40
|
+
- Rakefile
|
41
|
+
- lib/rails-ioc.rb
|
42
|
+
- lib/rails-ioc/controller_extension.rb
|
43
|
+
- lib/rails-ioc/dependencies.rb
|
44
|
+
- lib/rails-ioc/dependency_constructor.rb
|
45
|
+
- lib/rails-ioc/dependency_injector.rb
|
46
|
+
- lib/rails-ioc/errors.rb
|
47
|
+
- lib/rails-ioc/rspec_extension.rb
|
48
|
+
- lib/rails-ioc/version.rb
|
49
|
+
- rails-ioc.gemspec
|
50
|
+
- spec/fixtures/config/dependencies/incrementing_test.rb
|
51
|
+
- spec/fixtures/config/dependencies/production.rb
|
52
|
+
- spec/fixtures/config/dependencies/test.rb
|
53
|
+
- spec/rails-ioc/controller_extension_spec.rb
|
54
|
+
- spec/rails-ioc/dependencies_spec.rb
|
55
|
+
- spec/rails-ioc/dependency_constructor_spec.rb
|
56
|
+
- spec/rails-ioc/dependency_injector_spec.rb
|
57
|
+
- spec/rails-ioc/rspec_extension_spec.rb
|
58
|
+
- spec/spec_helper.rb
|
59
|
+
- spec/spec_helper_spec_spec_spec_spec.rb
|
60
|
+
has_rdoc: true
|
61
|
+
homepage: http://www.github.com/rickgrundy/rails-ioc
|
62
|
+
licenses: []
|
63
|
+
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options: []
|
66
|
+
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: "0"
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: "0"
|
81
|
+
requirements: []
|
82
|
+
|
83
|
+
rubyforge_project: rails-ioc
|
84
|
+
rubygems_version: 1.5.0
|
85
|
+
signing_key:
|
86
|
+
specification_version: 3
|
87
|
+
summary: Simple dependency injection for Rails.
|
88
|
+
test_files:
|
89
|
+
- spec/fixtures/config/dependencies/incrementing_test.rb
|
90
|
+
- spec/fixtures/config/dependencies/production.rb
|
91
|
+
- spec/fixtures/config/dependencies/test.rb
|
92
|
+
- spec/rails-ioc/controller_extension_spec.rb
|
93
|
+
- spec/rails-ioc/dependencies_spec.rb
|
94
|
+
- spec/rails-ioc/dependency_constructor_spec.rb
|
95
|
+
- spec/rails-ioc/dependency_injector_spec.rb
|
96
|
+
- spec/rails-ioc/rspec_extension_spec.rb
|
97
|
+
- spec/spec_helper.rb
|
98
|
+
- spec/spec_helper_spec_spec_spec_spec.rb
|