injection 2.0.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.
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