rails-ioc 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -1,155 +1,143 @@
1
1
  ## Quickstart
2
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
3
  ```ruby
4
+ # Gemfile:
5
+ gem 'rails-ioc'
6
+
7
+ # config/dependencies/development.rb:
8
+ RailsIOC::Dependencies.define do
9
+ prototype :card_validator, CreditCardValidator, "Visa", "Mastercard"
10
+ prototype :payment_gateway, DummyPaymentGateway, ref(:card_validator)
11
+
12
+ controller PaymentsController, {
13
+ payment_gateway: ref(:payment_gateway),
14
+ }
15
+ end
16
+
17
+ # app/controllers/payments_controller.rb:
25
18
  class PaymentsController < ApplicationController
26
19
  def accept_payment
27
20
  @payment_gateway.process(params[:credit_card])
28
21
  end
29
22
  end
23
+
24
+ # spec/controllers/payments_controller_spec.rb
25
+ it "processes a succesful payment" do
26
+ controller_dependencies(payment_gateway: mock(PaymentGateway))
27
+ controller.payment_gateway.should_receive(:process).with("4111").and_return(true)
28
+ post :accept_payment, credit_card: "4111"
29
+ response.status.should == 200
30
+ end
30
31
  ```
31
32
 
32
33
  ## Background
33
34
 
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
+ Inversion of control via dependency injection is widely used in large software systems and is generally accepted to be a "Good Thing". If the terms are unfamiliar these are good places to start reading:
35
36
 
36
37
  - http://en.wikipedia.org/wiki/Inversion_of_control
37
38
  - http://martinfowler.com/articles/injection.html
38
39
 
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
+ 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 easily tested.
40
41
 
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
+ Still, having run into several Rails projects which dealt with dependencies beyond an 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
 
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).
44
+ 1. **I really despise mocking calls to MyService#new to force a controller to use a stub implementation in a unit test.**
45
+ 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
 
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
+ RailsIOC attempts to make these problems less painful for applications with complex interactions with external services by providing a 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). Dependencies are defined in pure Ruby using a simple internal DSL. 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
 
48
49
  ## Cleaner Testing
49
50
  ### Before
50
51
 
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
- ```
52
+ ```ruby
53
+ # Controller:
54
+ @gateway = Gateway.new(ServiceA.new, ServiceB.new)
55
+ @gateway.do_something!
56
+
57
+ # RSpec:
58
+ @svc_a = mock(ServiceA)
59
+ @svc_b = mock(ServiceB)
60
+ ServiceA.stub(:new).and_return(@svc_a)
61
+ ServiceB.stub(:new).and_return(@svc_b)
62
+ @gateway = mock(Gateway)
63
+ @gateway.should_receive(:do_something!).and_return(12345)
64
+ Gateway.stub(:new).with(@svc_a, @svc_b).and_return(@gateway)
65
+ ```
65
66
 
66
67
  ### After
67
68
 
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
- ```
69
+ ```ruby
70
+ # Controller:
71
+ @gateway.do_something!
72
+
73
+ # RSpec:
74
+ controller_dependencies(gateway: mock(Gateway))
75
+ controller.gateway.should_receive(:do_something!).and_return(12345)
76
+ ```
77
77
 
78
- ## Customise Dependencies Per-Environment
78
+ ## Customise and Override Dependencies Per-Environment
79
79
  ### Before
80
-
81
- PaymentController:
82
80
 
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)
81
+ ```ruby
82
+ # app/controllers/payments_controller.rb:
83
+ class PaymentsController < ApplicationController
84
+ def accept_payment
85
+ if Rails.env.development? || Rails.env.test?
86
+ @credit_card_validator = BogusCardValidator.new
87
+ else
88
+ @credit_card_validator = RealCardValidator.new
100
89
  end
90
+ if Rails.env.production?
91
+ @gateway = RealPaymentGateway.new
92
+ elsif Rails.env.staging?
93
+ @gateway = RealPaymentGateway.new(use_testing_url: true)
94
+ else
95
+ @gateway = BogusPaymentGateway.new
96
+ end
97
+ card = @credit_card_validator.validate(params[:card])
98
+ @gateway.process(card)
101
99
  end
102
- ```
100
+ end
101
+ ```
103
102
 
104
103
  ### After
105
104
 
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
105
+ ```ruby
106
+ # app/controllers/payments_controller:
107
+ class PaymentsController < ApplicationController
108
+ def accept_payment
109
+ card = @credit_card_validator.validate(params[:card])
110
+ @gateway.process(card)
115
111
  end
116
- ```
112
+ end
117
113
 
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
- ```
114
+ # config/dependencies/production.rb:
115
+ RailsIOC::Dependencies.define do
116
+ prototype :payment_gateway, RealPaymentGateway
117
+ prototype :credit_card_validator, RealCardValidator
143
118
 
144
- config/dependencies/development.rb:
119
+ controller PaymentsController, {
120
+ gateway: ref(:payment_gateway)
121
+ credit_card_validator: ref(:credit_card_validator)
122
+ }
123
+ end
145
124
 
146
- ```ruby
147
- RailsIOC::Dependencies.define do
148
- inherit_environment(:production)
125
+ # config/dependencies/staging.rb:
126
+ RailsIOC::Dependencies.define do
127
+ inherit_environment(:production)
128
+
129
+ controller PaymentsController, {
130
+ gateway: prototype(RealPaymentGateway, use_testing_url: true)
131
+ }
132
+ end
149
133
 
150
- controller MyController, {
151
- gateway: singleton(BogusPaymentGateway),
152
- credit_card_validator: singleton(BogusCardValidator)
153
- }
154
- end
155
- ```
134
+ # config/dependencies/development.rb:
135
+ RailsIOC::Dependencies.define do
136
+ inherit_environment(:production)
137
+
138
+ controller PaymentsController, {
139
+ gateway: singleton(BogusPaymentGateway),
140
+ credit_card_validator: singleton(BogusCardValidator)
141
+ }
142
+ end
143
+ ```
@@ -3,7 +3,7 @@ module RailsIOC
3
3
  def self.reset!
4
4
  @dependencies = {}
5
5
  @controllers = {}
6
- @loaded = false
6
+ @loaded = false
7
7
  end
8
8
  self.reset!
9
9
 
@@ -11,6 +11,7 @@ module RailsIOC
11
11
  Dependencies.define do
12
12
  controller klass, dependencies
13
13
  end
14
+ dependencies.keys.each { |field| controller.class.send(:attr_reader, field) }
14
15
  end
15
16
  end
16
17
  end
@@ -1,3 +1,3 @@
1
1
  module RailsIOC
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -1,18 +1,6 @@
1
1
  require File.expand_path("../../spec_helper.rb", __FILE__)
2
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
-
3
+ describe RailsIOC::ControllerExtension do
16
4
  it "injects pre-defined dependencies" do
17
5
  Rails.application.config.cache_classes = true
18
6
  RailsIOC::Dependencies.instance_variable_set :@loaded, true
@@ -99,8 +99,7 @@ describe RailsIOC::Dependencies do
99
99
  describe "controllers" do
100
100
  it "stores controller dependencies for later injection by Rails' before_filter" do
101
101
  RailsIOC::Dependencies.define do
102
- prototype :second_node, TestListNode
103
- singleton :first_node, TestListNode, ref(:second_node)
102
+ singleton :first_node, TestListNode
104
103
 
105
104
  controller TestController, {
106
105
  foo: ref(:first_node),
@@ -115,13 +114,27 @@ describe RailsIOC::Dependencies do
115
114
  controller.foo.should === RailsIOC::Dependencies.ref(:first_node)
116
115
  controller.bar.should == "The Sheep Says Bar"
117
116
  end
117
+
118
+ it "merges overriden controller dependencies" do
119
+ RailsIOC::Dependencies.define do
120
+ controller TestController, {
121
+ foo: "original foo",
122
+ bar: "original bar"
123
+ }
124
+ end
125
+
126
+ RailsIOC::Dependencies.define do
127
+ controller TestController, {
128
+ bar: "override bar"
129
+ }
130
+ end
131
+
132
+ RailsIOC::Dependencies.controllers[TestController][:foo].should == "original foo"
133
+ RailsIOC::Dependencies.controllers[TestController][:bar].should == "override bar"
134
+ end
118
135
  end
119
136
 
120
- describe "loading dependencies file" do
121
- before(:each) do
122
- GlobalCounter.zero!
123
- end
124
-
137
+ describe "loading dependencies file" do
125
138
  it "reloads the file if cache_classes is false" do
126
139
  Rails.env = "incrementing_test"
127
140
  Rails.application.config.cache_classes = false
@@ -1,8 +1,9 @@
1
1
  require File.expand_path("../../spec_helper.rb", __FILE__)
2
2
 
3
3
  describe RailsIOC::RSpecExtension do
4
- class MyController; end
5
- def controller; MyController.new; end
4
+ def controller
5
+ @controller ||= ExtendedController.new
6
+ end
6
7
 
7
8
  describe "resetting dependencies between tests" do
8
9
  after(:each) do
@@ -18,8 +19,12 @@ describe RailsIOC::RSpecExtension do
18
19
  end
19
20
  end
20
21
 
21
- it "provides a shorthand way to define controller dependencies" do
22
+ it "provides a shorthand way to define and expose controller dependencies" do
23
+ Rails.application.config.cache_classes = true
24
+ RailsIOC::Dependencies.instance_variable_set :@loaded, true
25
+
22
26
  controller_dependencies(who_am_i: "The Walrus")
23
- RailsIOC::Dependencies.controllers[MyController][:who_am_i].should == "The Walrus"
27
+ controller.trigger_before_filter!
28
+ controller.who_am_i.should == "The Walrus"
24
29
  end
25
30
  end
data/spec/spec_helper.rb CHANGED
@@ -1,25 +1,17 @@
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
1
+ module ActionController
2
+ class Base
3
+ def self.before_filter(method_name)
4
+ @@before_filter_method = method_name
5
+ end
6
+
7
+ def trigger_before_filter!
8
+ self.send(@@before_filter_method)
9
+ end
20
10
  end
21
11
  end
22
12
 
13
+ class ExtendedController < ActionController::Base; end
14
+
23
15
  class AdHocObject
24
16
  def initialize
25
17
  @store = {}
@@ -34,29 +26,30 @@ class AdHocObject
34
26
  end
35
27
  end
36
28
 
37
- module FakeActionController
38
- class Base
39
- def self.before_filter(method_name); end
40
- end
41
- end
29
+ Rails = AdHocObject.new
30
+ Rails.application = AdHocObject.new
31
+ Rails.application.config = AdHocObject.new
32
+ Rails.root = File.expand_path("../fixtures", __FILE__)
42
33
 
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
34
 
50
- Object.send(:const_set, :ActionController, FakeActionController)
35
+ class GlobalCounter
36
+ def self.zero!
37
+ @count = 0
38
+ end
39
+
40
+ def self.increment!
41
+ @count += 1
42
+ end
43
+
44
+ def self.count
45
+ @count
51
46
  end
52
47
  end
53
48
 
54
49
  RSpec.configure do |config|
55
50
  config.before(:each) do
56
51
  GlobalCounter.zero!
57
- reset_fake_dependencies!
58
52
  end
59
53
  end
60
54
 
61
- reset_fake_dependencies!
62
55
  require File.expand_path("../../lib/rails-ioc.rb", __FILE__)
@@ -24,23 +24,4 @@ describe "Spec Helper" do
24
24
  Rails.application.config.kittens = "Small Cats"
25
25
  Rails.application.config.kittens.should == "Small Cats"
26
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
27
  end
metadata CHANGED
@@ -1,39 +1,34 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: rails-ioc
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
4
5
  prerelease:
5
- version: 0.1.0
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Rick Grundy
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
-
13
- date: 2011-08-29 00:00:00 +10:00
14
- default_executable:
15
- dependencies:
16
- - !ruby/object:Gem::Dependency
12
+ date: 2011-08-29 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
17
15
  name: rspec
18
- prerelease: false
19
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: &70311518245340 !ruby/object:Gem::Requirement
20
17
  none: false
21
- requirements:
22
- - - ">="
23
- - !ruby/object:Gem::Version
24
- version: "0"
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
25
22
  type: :development
26
- version_requirements: *id001
23
+ prerelease: false
24
+ version_requirements: *70311518245340
27
25
  description: Simple dependency injection for Rails.
28
- email:
26
+ email:
29
27
  - rick@rickgrundy.com
30
28
  executables: []
31
-
32
29
  extensions: []
33
-
34
30
  extra_rdoc_files: []
35
-
36
- files:
31
+ files:
37
32
  - .gitignore
38
33
  - Gemfile
39
34
  - README.markdown
@@ -57,35 +52,31 @@ files:
57
52
  - spec/rails-ioc/rspec_extension_spec.rb
58
53
  - spec/spec_helper.rb
59
54
  - spec/spec_helper_spec_spec_spec_spec.rb
60
- has_rdoc: true
61
55
  homepage: http://www.github.com/rickgrundy/rails-ioc
62
56
  licenses: []
63
-
64
57
  post_install_message:
65
58
  rdoc_options: []
66
-
67
- require_paths:
59
+ require_paths:
68
60
  - lib
69
- required_ruby_version: !ruby/object:Gem::Requirement
61
+ required_ruby_version: !ruby/object:Gem::Requirement
70
62
  none: false
71
- requirements:
72
- - - ">="
73
- - !ruby/object:Gem::Version
74
- version: "0"
75
- required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
68
  none: false
77
- requirements:
78
- - - ">="
79
- - !ruby/object:Gem::Version
80
- version: "0"
69
+ requirements:
70
+ - - ! '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
81
73
  requirements: []
82
-
83
74
  rubyforge_project: rails-ioc
84
- rubygems_version: 1.5.0
75
+ rubygems_version: 1.8.6
85
76
  signing_key:
86
77
  specification_version: 3
87
78
  summary: Simple dependency injection for Rails.
88
- test_files:
79
+ test_files:
89
80
  - spec/fixtures/config/dependencies/incrementing_test.rb
90
81
  - spec/fixtures/config/dependencies/production.rb
91
82
  - spec/fixtures/config/dependencies/test.rb