injection 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.gitignore +6 -0
  2. data/Gemfile +4 -0
  3. data/README.rdoc +116 -0
  4. data/Rakefile +21 -0
  5. data/injection.gemspec +28 -0
  6. data/lib/injection.rb +45 -0
  7. data/lib/injection/class_inject.rb +38 -0
  8. data/lib/injection/observer_inject.rb +44 -0
  9. data/lib/injection/railtie.rb +41 -0
  10. data/lib/injection/version.rb +3 -0
  11. data/spec/class_inject_spec.rb +197 -0
  12. data/spec/dummy/Rakefile +7 -0
  13. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  14. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  15. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  16. data/spec/dummy/config.ru +4 -0
  17. data/spec/dummy/config/application.rb +45 -0
  18. data/spec/dummy/config/boot.rb +10 -0
  19. data/spec/dummy/config/database.yml +22 -0
  20. data/spec/dummy/config/environment.rb +5 -0
  21. data/spec/dummy/config/environments/development.rb +26 -0
  22. data/spec/dummy/config/environments/production.rb +49 -0
  23. data/spec/dummy/config/environments/test.rb +35 -0
  24. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  25. data/spec/dummy/config/initializers/inflections.rb +10 -0
  26. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  27. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  28. data/spec/dummy/config/initializers/session_store.rb +8 -0
  29. data/spec/dummy/config/locales/en.yml +5 -0
  30. data/spec/dummy/config/objects.yml +20 -0
  31. data/spec/dummy/config/routes.rb +58 -0
  32. data/spec/dummy/db/migrate/20101227205147_create_bananas.rb +13 -0
  33. data/spec/dummy/db/test.sqlite3 +0 -0
  34. data/spec/dummy/lib/class_inject_classes.rb +12 -0
  35. data/spec/dummy/lib/observer_inject_classes.rb +2 -0
  36. data/spec/dummy/public/404.html +26 -0
  37. data/spec/dummy/public/422.html +26 -0
  38. data/spec/dummy/public/500.html +26 -0
  39. data/spec/dummy/public/favicon.ico +0 -0
  40. data/spec/dummy/public/javascripts/application.js +2 -0
  41. data/spec/dummy/public/javascripts/controls.js +965 -0
  42. data/spec/dummy/public/javascripts/dragdrop.js +974 -0
  43. data/spec/dummy/public/javascripts/effects.js +1123 -0
  44. data/spec/dummy/public/javascripts/prototype.js +6001 -0
  45. data/spec/dummy/public/javascripts/rails.js +175 -0
  46. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  47. data/spec/dummy/script/rails +6 -0
  48. data/spec/observer_inject_spec.rb +228 -0
  49. data/spec/spec_helper.rb +40 -0
  50. metadata +223 -0
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ Gemfile.lock
5
+ doc
6
+ spec/dummy/log/*.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in injection.gemspec
4
+ gemspec
data/README.rdoc ADDED
@@ -0,0 +1,116 @@
1
+ == DESCRIPTION:
2
+
3
+ Injection is a simple dependency injection plugin for rails3. It allows you to inject objects into your controllers and observers which have been described in a yaml file (config/objects.yml).
4
+
5
+ * https://github.com/atomicobject/injection
6
+
7
+ (for the rails2 plugin install from the rails_2_plugin tag of this repository)
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * Declarative dependency injection that automatically instantiates ivars via the objects.yml file
12
+
13
+ == SYNOPSIS:
14
+ To specify objects to be injected from the DIY context into your controller or observer
15
+ use the inject class method:
16
+
17
+ inject :one_component, :another
18
+
19
+ === Example
20
+
21
+ This example defines a context with two objects, _foo_ and _bar_, which
22
+ are injected into every instance of the WidgetController or WidgetObserver. The objects
23
+ are available as instance variables within the controller and observer.
24
+
25
+ config/objects.yml:
26
+ ---
27
+ foo:
28
+ bar:
29
+
30
+ lib/foo.rb:
31
+ class Foo
32
+ ...
33
+ end
34
+
35
+ lib/bar.rb:
36
+ class Bar
37
+ ...
38
+ end
39
+
40
+ app/controllers/widget_controller.rb:
41
+ class WidgetController < ApplicationController
42
+ inject :foo, :bar
43
+
44
+ def index
45
+ render :text => "{@foo.inspect} {@bar.inspect}"
46
+ end
47
+ end
48
+
49
+ app/models/widget_observer.rb:
50
+ class WidgetObserver < ActiveRecord::Observer
51
+ inject :foo, :bar
52
+
53
+ before :save do |widget|
54
+ @foo.bar(widget)
55
+ @bar.foo(widget)
56
+ end
57
+ end
58
+
59
+ === Declarative Observations
60
+
61
+ Observations on an active record object can be specified in a declarative way using
62
+ the before and after class methods. The methods are invoked with a
63
+ symbol which specifies what kind of event the observer is interested in, and a block
64
+ that defines what actions it will perform.
65
+
66
+ class WidgetObserver < ActiveRecord::Observer
67
+ before :update do |record|
68
+ ...
69
+ end
70
+
71
+ after :create do |record|
72
+ ...
73
+ end
74
+
75
+ after :validation do |record|
76
+ ...
77
+ end
78
+
79
+ before :validation do |record|
80
+ ...
81
+ end
82
+ end
83
+
84
+ == REQUIREMENTS:
85
+
86
+ * constructor
87
+ * diy
88
+
89
+ == INSTALL:
90
+
91
+ * gem install injection
92
+
93
+ == LICENSE:
94
+
95
+ (The MIT License)
96
+
97
+ Copyright (c) 2007-2010 Atomic Object
98
+
99
+ Permission is hereby granted, free of charge, to any person obtaining
100
+ a copy of this software and associated documentation files (the
101
+ 'Software'), to deal in the Software without restriction, including
102
+ without limitation the rights to use, copy, modify, merge, publish,
103
+ distribute, sublicense, and/or sell copies of the Software, and to
104
+ permit persons to whom the Software is furnished to do so, subject to
105
+ the following conditions:
106
+
107
+ The above copyright notice and this permission notice shall be
108
+ included in all copies or substantial portions of the Software.
109
+
110
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
111
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
112
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
113
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
114
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
115
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
116
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ require 'bundler/setup'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake'
5
+ require 'rake/rdoctask'
6
+
7
+ desc 'Default: run unit specs'
8
+ task :default => :spec
9
+
10
+ desc 'Generate documentation for the injection plugin.'
11
+ Rake::RDocTask.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = 'doc'
13
+ rdoc.title = 'injection'
14
+ rdoc.options << '--line-numbers' << '--inline-source'
15
+ rdoc.rdoc_files.include('README')
16
+ rdoc.rdoc_files.include('lib/**/*.rb')
17
+ end
18
+
19
+ require 'rspec/core'
20
+ require 'rspec/core/rake_task'
21
+ RSpec::Core::RakeTask.new(:spec)
data/injection.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "injection/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "injection"
7
+ s.version = Injection::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Atomic Object"]
10
+ s.email = ["github@atomicobject.com"]
11
+ s.homepage = "https://github.com/atomicobject/injection"
12
+ s.summary = %q{Dependency injection for Rails controllers and observers}
13
+ s.description = %q{Injection is a simple dependency injection gem for rails3. It allows you to inject objects into your controllers and observers which have been described in a yaml file (config/objects.yml).}
14
+
15
+ s.rubyforge_project = "injection"
16
+
17
+ s.files = `git ls-files`.split("\n").reject {|f| f =~ /homepage/}
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency "rails", ["~> 3"]
23
+ s.add_dependency "diy", ["~> 1"]
24
+ s.add_dependency "constructor", ["~> 2"]
25
+
26
+ s.add_development_dependency "rspec-rails", "~> 2"
27
+ s.add_development_dependency "sqlite3-ruby"
28
+ end
data/lib/injection.rb ADDED
@@ -0,0 +1,45 @@
1
+ require 'diy'
2
+ require 'constructor'
3
+ require 'injection/railtie'
4
+ require 'injection/class_inject'
5
+ require 'injection/observer_inject'
6
+
7
+ # === Accessing Objects in the Context
8
+ #
9
+ # Provides access to the context loaded from <tt>config/objects.yml</tt> which
10
+ # can be used to look up individual components.
11
+ #
12
+ # config/objects.yml:
13
+ # ---
14
+ # foo:
15
+ # bar:
16
+ #
17
+ # lib/foo.rb:
18
+ # class Foo
19
+ # ...
20
+ # end
21
+ #
22
+ # Inject.context[:foo] #=> #<Foo:0x81eb0>
23
+ #
24
+ module Injection
25
+ @@context = {}
26
+ @@extra_inputs = {}
27
+
28
+ # Accessor for the context loaded from <tt>config/objects.yml</tt>
29
+ def self.context
30
+ @@context
31
+ end
32
+
33
+ def self.context_file=(path)
34
+ @@context_file = path
35
+ end
36
+
37
+ def self.extra_inputs=(hash)
38
+ @@extra_inputs = hash
39
+ end
40
+
41
+ def self.reset_context
42
+ @@context = DIY::Context.from_file(@@context_file, @@extra_inputs) if File.exists?(@@context_file)
43
+ end
44
+ end
45
+
@@ -0,0 +1,38 @@
1
+ module Injection
2
+ module ClassInject
3
+ extend ActiveSupport::Concern
4
+
5
+ module Initialize #:nodoc:
6
+ extend ActiveSupport::Concern
7
+
8
+ def initialize(*args)
9
+ if args.empty?
10
+ raise 'No context file loaded' if Injection.context.is_a?(Hash)
11
+ constructor_args = self.class.constructor_keys.inject({}) do |memo, key|
12
+ memo[key] = Injection.context[key]
13
+ memo
14
+ end
15
+ super(constructor_args)
16
+ else
17
+ super
18
+ end
19
+ end
20
+ end
21
+
22
+ module ClassMethods
23
+ #
24
+ # Specify which components should be injected into this observer as the
25
+ # list of <tt>keys</tt> to look them up from the DI context.
26
+ #
27
+ def inject(*keys)
28
+ constructor_args = if keys.last.is_a?(Hash)
29
+ keys[0..-2] + [{:super => []}.merge(keys.last)]
30
+ else
31
+ keys + [:super => []]
32
+ end
33
+ constructor *constructor_args
34
+ include Initialize
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,44 @@
1
+ module Injection
2
+ module ObserverExtension
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ #
7
+ # Specify what events the observer is interested in after an activerecord object has
8
+ # performed an operation by passing in a <tt>symbol</tt> and a <tt>block</tt>.
9
+ def after(observation, &block)
10
+ build_methods(:after, observation, &block)
11
+ end
12
+
13
+ #
14
+ # Specify what events the observer is interested in before an activerecord object has
15
+ # performed an operation by passing in a <tt>symbol</tt> and a <tt>block</tt>.
16
+ def before(observation, &block)
17
+ build_methods(:before, observation, &block)
18
+ end
19
+
20
+ #
21
+ # Specify what events the observer is interested in before and after an activerecord object has
22
+ # performed an operation by passing in a <tt>symbol</tt> and a <tt>block</tt> that yields.
23
+ def around(observation, &block)
24
+ build_methods(:around, observation, &block)
25
+ end
26
+
27
+ private
28
+
29
+ def build_methods(before_after, observation, &block)
30
+ mname = build_method_name(before_after, observation)
31
+ validate_observation! mname
32
+ define_method(mname, &block)
33
+ end
34
+
35
+ def build_method_name(before_after, observation)
36
+ "#{before_after}_#{observation}"
37
+ end
38
+
39
+ def validate_observation!(observation)
40
+ raise "#{observation} is not an observable action" unless ActiveRecord::Callbacks::CALLBACKS.include? observation.to_sym
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,41 @@
1
+ require 'rails'
2
+
3
+ module Injection
4
+ class Railtie < ::Rails::Railtie
5
+ config.before_configuration do
6
+ Injection.context_file = ::Rails.root.to_s + '/config/objects.yml'
7
+ end
8
+
9
+ config.after_initialize do
10
+ # Let Rails do the auto loading
11
+ DIY::Context.auto_require = false
12
+
13
+ # Reload the context from the file
14
+ Injection.reset_context
15
+ end
16
+
17
+ # Reload the context after each request in development
18
+ initializer "injection.clear_context", :before => :set_clear_dependencies_hook do |app|
19
+ unless app.config.cache_classes
20
+ ::ActionDispatch::Callbacks.after do
21
+ Injection.reset_context
22
+ end
23
+ end
24
+ end
25
+
26
+ initializer "injection.initialize_action_controller" do |app|
27
+ ::ActiveSupport.on_load(:action_controller) do
28
+ include ClassInject
29
+ end
30
+ end
31
+
32
+ initializer "injection.initialize_observers" do |app|
33
+ ::ActiveSupport.on_load(:active_record) do
34
+ ::ActiveRecord::Observer.class_eval do
35
+ include ClassInject
36
+ include ObserverExtension
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,3 @@
1
+ module Injection
2
+ VERSION = "2.0.0"
3
+ end
@@ -0,0 +1,197 @@
1
+ require 'spec_helper'
2
+
3
+ class NormalController < ActionController::Base
4
+ end
5
+
6
+ class InjectedController < ActionController::Base
7
+ inject :foo, :bar
8
+ attr_reader :foo, :bar
9
+ end
10
+
11
+ class PoorlyInjectedController < ActionController::Base
12
+ inject :foo, :bar, :qux
13
+ attr_reader :foo, :bar, :qux
14
+ end
15
+
16
+ class GalaxyController < ActionController::Base
17
+ inject :foo
18
+ attr_accessor :foo
19
+ end
20
+
21
+ class SpiralGalaxyController < GalaxyController
22
+ inject :bar
23
+ attr_accessor :bar
24
+ end
25
+
26
+ class MilkyWayGalaxyController < SpiralGalaxyController
27
+ inject :foo_piece_two
28
+ attr_accessor :foo_piece_two
29
+ end
30
+
31
+ class FutureMilkyWayGalaxyController < MilkyWayGalaxyController;
32
+ constructor
33
+ end
34
+
35
+ class CommercialController < ActionController::Base
36
+ inject :old, :readers => true
37
+ end
38
+
39
+ describe 'class inject' do
40
+ before do
41
+ # Instantiate a controller to kick start Rails into loading Injection
42
+ InjectedController.new
43
+ end
44
+
45
+ it "does not mess up construction of a normal controller" do
46
+ controller = NormalController.new
47
+ controller.should_not be_nil
48
+ end
49
+
50
+ it 'injects objects from context' do
51
+ controller = InjectedController.new
52
+ controller.should_not be_nil
53
+ controller.foo.should equal(injection_context[:foo])
54
+ controller.bar.should equal(injection_context[:bar])
55
+ end
56
+
57
+ it 'uses the params in injected object' do
58
+ foo, bar = 'foo', 'bar'
59
+ controller = InjectedController.new :foo => foo, :bar => bar
60
+ controller.should_not be_nil
61
+ controller.foo.should equal(foo)
62
+ controller.bar.should equal(bar)
63
+ end
64
+
65
+ it "raises an error for a poorly injected object when a component is not found" do
66
+ lambda { PoorlyInjectedController.new }.should raise_error(DIY::ConstructionError, /qux/i)
67
+ end
68
+
69
+ it "constructs poorly injected object given params" do
70
+ foo, bar, qux = 'foo', 'bar', 'qux'
71
+ controller = PoorlyInjectedController.new :foo => foo, :bar => bar, :qux => qux
72
+ controller.foo.should equal(foo)
73
+ controller.bar.should equal(bar)
74
+ controller.qux.should equal(qux)
75
+ end
76
+
77
+
78
+ it "raises for an injected object given only partial params" do
79
+ lambda { InjectedController.new(:foo => 'foo') }.should raise_error(Constructor::ArgumentError, /bar/i)
80
+ err = assert_raise Constructor::ArgumentError do
81
+ InjectedController.new :foo => 'foo'
82
+ end
83
+ assert_match(/bar/i, err.message)
84
+ end
85
+
86
+ context 'with an invalid context file' do
87
+ before do
88
+ set_context 'not a file path'
89
+ end
90
+
91
+ after do
92
+ set_context "#{::Rails.root}/config/objects.yml"
93
+ end
94
+
95
+ it "allows an injected object that is given params to have a non-existent context file" do
96
+ foo, bar = 'foo', 'bar'
97
+ controller = InjectedController.new :foo => foo, :bar => bar
98
+ controller.should_not be_nil
99
+ controller.foo.should equal(foo)
100
+ controller.bar.should equal(bar)
101
+ end
102
+
103
+ it "does not affect a normal object when set to a non-existent context file" do
104
+ foo, bar = 'foo', 'bar'
105
+ NormalController.new.should_not be_nil
106
+ end
107
+ end
108
+
109
+ it "supports object inheritance" do
110
+ GalaxyController.new.foo.should be_a_kind_of(TheFooClass)
111
+
112
+ controller = SpiralGalaxyController.new
113
+ injected_controller = SpiralGalaxyController.new(:foo => "foo", :bar => "bar")
114
+ SpiralGalaxyController.superclass.should == GalaxyController
115
+
116
+ controller.foo.should be_a_kind_of(TheFooClass)
117
+ controller.bar.should be_a_kind_of(Bar)
118
+ injected_controller.foo.should == "foo"
119
+ injected_controller.bar.should == "bar"
120
+
121
+ controller = MilkyWayGalaxyController.new
122
+ injected_controller = MilkyWayGalaxyController.new(:foo => "foo", :bar => "bar", :foo_piece_two => "foo2")
123
+ MilkyWayGalaxyController.superclass.should == SpiralGalaxyController
124
+ controller.foo.should be_a_kind_of(TheFooClass)
125
+ assert_kind_of(TheFooClass, controller.foo)
126
+ controller.bar.should be_a_kind_of(Bar)
127
+ controller.foo_piece_two.should be_a_kind_of(FooPieceTwo)
128
+ injected_controller.foo.should == "foo"
129
+ injected_controller.bar.should == "bar"
130
+ injected_controller.foo_piece_two.should == "foo2"
131
+
132
+ controller = FutureMilkyWayGalaxyController.new
133
+ injected_controller = FutureMilkyWayGalaxyController.new(:foo => "foo", :bar => "bar", :foo_piece_two => "foo2")
134
+ FutureMilkyWayGalaxyController.superclass.should == MilkyWayGalaxyController
135
+ controller.foo.should be_a_kind_of(TheFooClass)
136
+ assert_kind_of(TheFooClass, controller.foo)
137
+ controller.bar.should be_a_kind_of(Bar)
138
+ controller.foo_piece_two.should be_a_kind_of(FooPieceTwo)
139
+ injected_controller.foo.should == "foo"
140
+ injected_controller.bar.should == "bar"
141
+ injected_controller.foo_piece_two.should == "foo2"
142
+ end
143
+
144
+ it "resets the context to having no built components" do
145
+ ic = InjectedController.new
146
+ ic.foo.should eql(Injection.context['foo'])
147
+ ic.bar.should eql(Injection.context['bar'])
148
+
149
+ # See that the inject context is being reused, as well as its previously built components:
150
+ ic2 = InjectedController.new
151
+ ic.foo.should eql(Injection.context['foo'])
152
+ ic.bar.should eql(Injection.context['bar'])
153
+ ic2.foo.should eql(Injection.context['foo'])
154
+ ic2.bar.should eql(Injection.context['bar'])
155
+ ic.foo.foo_piece_one.should eql(Injection.context['foo_piece_one'])
156
+ ic2.foo.foo_piece_one.should eql(Injection.context['foo_piece_one'])
157
+
158
+ Injection.reset_context
159
+ Injection.context['foo_piece_one'] = "Mock Foo Piece One"
160
+
161
+ ic3 = InjectedController.new
162
+ ic3.foo.should eql(Injection.context['foo'])
163
+ ic2.foo.should_not eql(Injection.context['foo'])
164
+ ic.foo.should_not eql(Injection.context['foo'])
165
+ ic3.foo.foo_piece_one.should == "Mock Foo Piece One"
166
+ end
167
+
168
+ it 'allows extra inputs to be provided when context is reset' do
169
+ lambda { injection_context[:old] }.should raise_error(/failed/i)
170
+ Injection.extra_inputs = {:old => 'spice'}
171
+ Injection.reset_context
172
+ injection_context[:old].should == 'spice'
173
+
174
+ CommercialController.new.old.should == 'spice'
175
+ end
176
+
177
+ it "does not reset context after ActionDispatch::Callbacks" do
178
+ ic = InjectedController.new
179
+ ic.foo.should eql(Injection.context['foo'])
180
+ ic.bar.should eql(Injection.context['bar'])
181
+
182
+ # See that the inject context is being reused, as well as its previously built components:
183
+ ic2 = InjectedController.new
184
+ ic.foo.should eql(Injection.context['foo'])
185
+ ic.bar.should eql(Injection.context['bar'])
186
+ ic2.foo.should eql(Injection.context['foo'])
187
+ ic2.bar.should eql(Injection.context['bar'])
188
+ ic.foo.foo_piece_one.should eql(Injection.context['foo_piece_one'])
189
+ ic2.foo.foo_piece_one.should eql(Injection.context['foo_piece_one'])
190
+
191
+ # This will trigger the class reloading
192
+ ActionDispatch::Callbacks.new(Proc.new {}, false).call({})
193
+
194
+ lambda { Injection.context['foo_piece_one'] = "Mock Foo Piece One" }.should raise_error(/already exists/i)
195
+ end
196
+
197
+ end