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
@@ -0,0 +1,175 @@
1
+ (function() {
2
+ // Technique from Juriy Zaytsev
3
+ // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
4
+ function isEventSupported(eventName) {
5
+ var el = document.createElement('div');
6
+ eventName = 'on' + eventName;
7
+ var isSupported = (eventName in el);
8
+ if (!isSupported) {
9
+ el.setAttribute(eventName, 'return;');
10
+ isSupported = typeof el[eventName] == 'function';
11
+ }
12
+ el = null;
13
+ return isSupported;
14
+ }
15
+
16
+ function isForm(element) {
17
+ return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM'
18
+ }
19
+
20
+ function isInput(element) {
21
+ if (Object.isElement(element)) {
22
+ var name = element.nodeName.toUpperCase()
23
+ return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA'
24
+ }
25
+ else return false
26
+ }
27
+
28
+ var submitBubbles = isEventSupported('submit'),
29
+ changeBubbles = isEventSupported('change')
30
+
31
+ if (!submitBubbles || !changeBubbles) {
32
+ // augment the Event.Handler class to observe custom events when needed
33
+ Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap(
34
+ function(init, element, eventName, selector, callback) {
35
+ init(element, eventName, selector, callback)
36
+ // is the handler being attached to an element that doesn't support this event?
37
+ if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) ||
38
+ (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) {
39
+ // "submit" => "emulated:submit"
40
+ this.eventName = 'emulated:' + this.eventName
41
+ }
42
+ }
43
+ )
44
+ }
45
+
46
+ if (!submitBubbles) {
47
+ // discover forms on the page by observing focus events which always bubble
48
+ document.on('focusin', 'form', function(focusEvent, form) {
49
+ // special handler for the real "submit" event (one-time operation)
50
+ if (!form.retrieve('emulated:submit')) {
51
+ form.on('submit', function(submitEvent) {
52
+ var emulated = form.fire('emulated:submit', submitEvent, true)
53
+ // if custom event received preventDefault, cancel the real one too
54
+ if (emulated.returnValue === false) submitEvent.preventDefault()
55
+ })
56
+ form.store('emulated:submit', true)
57
+ }
58
+ })
59
+ }
60
+
61
+ if (!changeBubbles) {
62
+ // discover form inputs on the page
63
+ document.on('focusin', 'input, select, texarea', function(focusEvent, input) {
64
+ // special handler for real "change" events
65
+ if (!input.retrieve('emulated:change')) {
66
+ input.on('change', function(changeEvent) {
67
+ input.fire('emulated:change', changeEvent, true)
68
+ })
69
+ input.store('emulated:change', true)
70
+ }
71
+ })
72
+ }
73
+
74
+ function handleRemote(element) {
75
+ var method, url, params;
76
+
77
+ var event = element.fire("ajax:before");
78
+ if (event.stopped) return false;
79
+
80
+ if (element.tagName.toLowerCase() === 'form') {
81
+ method = element.readAttribute('method') || 'post';
82
+ url = element.readAttribute('action');
83
+ params = element.serialize();
84
+ } else {
85
+ method = element.readAttribute('data-method') || 'get';
86
+ url = element.readAttribute('href');
87
+ params = {};
88
+ }
89
+
90
+ new Ajax.Request(url, {
91
+ method: method,
92
+ parameters: params,
93
+ evalScripts: true,
94
+
95
+ onComplete: function(request) { element.fire("ajax:complete", request); },
96
+ onSuccess: function(request) { element.fire("ajax:success", request); },
97
+ onFailure: function(request) { element.fire("ajax:failure", request); }
98
+ });
99
+
100
+ element.fire("ajax:after");
101
+ }
102
+
103
+ function handleMethod(element) {
104
+ var method = element.readAttribute('data-method'),
105
+ url = element.readAttribute('href'),
106
+ csrf_param = $$('meta[name=csrf-param]')[0],
107
+ csrf_token = $$('meta[name=csrf-token]')[0];
108
+
109
+ var form = new Element('form', { method: "POST", action: url, style: "display: none;" });
110
+ element.parentNode.insert(form);
111
+
112
+ if (method !== 'post') {
113
+ var field = new Element('input', { type: 'hidden', name: '_method', value: method });
114
+ form.insert(field);
115
+ }
116
+
117
+ if (csrf_param) {
118
+ var param = csrf_param.readAttribute('content'),
119
+ token = csrf_token.readAttribute('content'),
120
+ field = new Element('input', { type: 'hidden', name: param, value: token });
121
+ form.insert(field);
122
+ }
123
+
124
+ form.submit();
125
+ }
126
+
127
+
128
+ document.on("click", "*[data-confirm]", function(event, element) {
129
+ var message = element.readAttribute('data-confirm');
130
+ if (!confirm(message)) event.stop();
131
+ });
132
+
133
+ document.on("click", "a[data-remote]", function(event, element) {
134
+ if (event.stopped) return;
135
+ handleRemote(element);
136
+ event.stop();
137
+ });
138
+
139
+ document.on("click", "a[data-method]", function(event, element) {
140
+ if (event.stopped) return;
141
+ handleMethod(element);
142
+ event.stop();
143
+ });
144
+
145
+ document.on("submit", function(event) {
146
+ var element = event.findElement(),
147
+ message = element.readAttribute('data-confirm');
148
+ if (message && !confirm(message)) {
149
+ event.stop();
150
+ return false;
151
+ }
152
+
153
+ var inputs = element.select("input[type=submit][data-disable-with]");
154
+ inputs.each(function(input) {
155
+ input.disabled = true;
156
+ input.writeAttribute('data-original-value', input.value);
157
+ input.value = input.readAttribute('data-disable-with');
158
+ });
159
+
160
+ var element = event.findElement("form[data-remote]");
161
+ if (element) {
162
+ handleRemote(element);
163
+ event.stop();
164
+ }
165
+ });
166
+
167
+ document.on("ajax:after", "form", function(event, element) {
168
+ var inputs = element.select("input[type=submit][disabled=true][data-disable-with]");
169
+ inputs.each(function(input) {
170
+ input.value = input.readAttribute('data-original-value');
171
+ input.removeAttribute('data-original-value');
172
+ input.disabled = false;
173
+ });
174
+ });
175
+ })();
File without changes
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -0,0 +1,228 @@
1
+ require 'spec_helper'
2
+
3
+ describe Injection::ObserverExtension do
4
+ subject { MyObserver.instance }
5
+ before do
6
+ @observations = []
7
+ end
8
+
9
+ it "should be able to define observations in a declaritive way" do
10
+ ActiveRecord::Callbacks::CALLBACKS.sort {|a,b| a.to_s <=> b.to_s}.each do |callback|
11
+ subject.update(callback, @observations)
12
+ end
13
+
14
+ @observations.size.should == ActiveRecord::Callbacks::CALLBACKS.size
15
+ verify_after_observations
16
+ verify_before_observations
17
+ @observations.should be_empty
18
+ end
19
+
20
+ it "should be able to define multiple on conditionals for validation observations" do
21
+ number_of_after_observations = 0
22
+ number_of_before_observations = 0
23
+ saved_record = nil
24
+
25
+ EmptyObserver.class_eval do
26
+ after :validation do |record|
27
+ saved_record = record
28
+ number_of_after_observations = number_of_after_observations + 1
29
+ end
30
+
31
+ before :validation do |record|
32
+ saved_record = record
33
+ number_of_before_observations = number_of_before_observations + 1
34
+ end
35
+ end
36
+
37
+ observer = EmptyObserver.instance
38
+
39
+ observer.update(:after_validation, "foo")
40
+ assert_equal "foo", saved_record, "wrong record"
41
+
42
+ observer.update(:after_validation, "bar")
43
+ assert_equal "bar", saved_record, "wrong record"
44
+
45
+ assert_equal 2, number_of_after_observations
46
+
47
+ observer.update(:before_validation, "foo2")
48
+ assert_equal "foo2", saved_record, "wrong record"
49
+ observer.update(:before_validation, "bar2")
50
+ assert_equal "bar2", saved_record, "wrong record"
51
+
52
+ assert_equal 2, number_of_before_observations
53
+ end
54
+
55
+ it "should raise an error if invalid observations are given" do
56
+ assert_error(RuntimeError, "after_foo is not an observable action") do
57
+ MyObserver.class_eval do
58
+ after :foo do
59
+ end
60
+ end
61
+ end
62
+
63
+ assert_error(RuntimeError, "before_bar is not an observable action") do
64
+ MyObserver.class_eval do
65
+ before :bar do
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ it "should inject from context when calling instance" do
72
+ observer = InjectedRecordObserver.instance
73
+ assert_same observer, InjectedRecordObserver.instance
74
+ verify_record_has_observer(observer, InjectedRecord)
75
+ verify_record_has_observer(observer, SubClassedInjectedRecord)
76
+ verify_record_has_observer(observer, DistantAncestorOfInjectedRecord)
77
+
78
+ assert_kind_of MyObject, observer.my_object, "should be a MyObject instance"
79
+ assert_kind_of AnotherObject, observer.another_object, "should be AnotherObject instance"
80
+ end
81
+
82
+ it "supports inheritance" do
83
+ observer = BigBangObserver.instance
84
+ assert_kind_of(MyObject, observer.my_object)
85
+ verify_record_has_observer(observer, BigBang)
86
+
87
+ observer = GalaxyObserver.instance
88
+ assert_kind_of(MyObject, observer.my_object)
89
+ assert_kind_of(AnotherObject, observer.another_object)
90
+ verify_record_has_observer(observer, Galaxy)
91
+
92
+ observer = SolarSystemObserver.instance
93
+ assert_kind_of(MyObject, observer.my_object)
94
+ assert_kind_of(AnotherObject, observer.another_object)
95
+ verify_record_has_observer(observer, SolarSystem)
96
+ end
97
+
98
+ it "runs its own example" do
99
+ monkey = mock(:monkey)
100
+ target = BananaObserver.send(:new, :monkey => monkey)
101
+ banana = Banana.new
102
+
103
+ monkey.should_receive(:peels).with(banana)
104
+ target.update(:before_create, banana)
105
+ end
106
+
107
+ #
108
+ # HELPERS
109
+ #
110
+
111
+ def verify_record_has_observer(observer, record)
112
+ record.count_observers.should > 0
113
+ assert_equal 1, record.count_observers
114
+ observers = record.add_observer(observer)
115
+ assert_same observer, observers.shift
116
+ assert_same observer, observers.shift
117
+ end
118
+
119
+ def verify_after_observations
120
+ verify_observation(@observations.shift, :when => "after", :operation => "commit")
121
+ verify_observation(@observations.shift, :when => "after", :operation => "create")
122
+ verify_observation(@observations.shift, :when => "after", :operation => "destroy")
123
+ verify_observation(@observations.shift, :when => "after", :operation => "find")
124
+ verify_observation(@observations.shift, :when => "after", :operation => "initialize")
125
+ verify_observation(@observations.shift, :when => "after", :operation => "rollback")
126
+ verify_observation(@observations.shift, :when => "after", :operation => "save")
127
+ verify_observation(@observations.shift, :when => "after", :operation => "touch")
128
+ verify_observation(@observations.shift, :when => "after", :operation => "update")
129
+ verify_observation(@observations.shift, :when => "after", :operation => "validation")
130
+
131
+ verify_observation(@observations.shift, :when => "around", :operation => "create")
132
+ verify_observation(@observations.shift, :when => "around", :operation => "destroy")
133
+ verify_observation(@observations.shift, :when => "around", :operation => "save")
134
+ verify_observation(@observations.shift, :when => "around", :operation => "update")
135
+
136
+ end
137
+
138
+ def verify_before_observations
139
+ verify_observation(@observations.shift, :when => "before", :operation => "create")
140
+ verify_observation(@observations.shift, :when => "before", :operation => "destroy")
141
+ verify_observation(@observations.shift, :when => "before", :operation => "save")
142
+ verify_observation(@observations.shift, :when => "before", :operation => "update")
143
+ verify_observation(@observations.shift, :when => "before", :operation => "validation")
144
+ end
145
+
146
+ def verify_observation(actual, expected)
147
+ assert_equal expected, actual, "observations were not the same"
148
+ end
149
+
150
+ def assert_error(err_type,*patterns,&block)
151
+ assert_not_nil block, "assert_error requires a block"
152
+ assert((err_type and err_type.kind_of?(Class)), "First argument to assert_error has to be an error type")
153
+ err = assert_raise(err_type) do
154
+ block.call
155
+ end
156
+ patterns.each do |pattern|
157
+ case pattern
158
+ when Regexp
159
+ assert_match(pattern, err.message)
160
+ else
161
+ assert_equal pattern, err.message
162
+ end
163
+ end
164
+ end
165
+ end
166
+
167
+ class ObservableRecord < ActiveRecord::Base; end
168
+ class MyObserver < ActiveRecord::Observer
169
+ observe ObservableRecord
170
+
171
+ ActiveRecord::Callbacks::CALLBACKS.each do |callback|
172
+ actions = callback.to_s.split("_")
173
+ if actions.size == 2
174
+ case actions[0]
175
+ when "after"
176
+ after actions[1] do |record|
177
+ record << {:when => "after", :operation => actions[1]}
178
+ end
179
+ when "before"
180
+ before actions[1] do |record|
181
+ record << {:when => "before", :operation => actions[1]}
182
+ end
183
+ when "around"
184
+ around actions[1] do |record|
185
+ record << {:when => "around", :operation => actions[1]}
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
191
+
192
+ class EmptyObserver < ActiveRecord::Observer
193
+ observe ObservableRecord
194
+ end
195
+
196
+ class InjectedRecord < ActiveRecord::Base; end
197
+ class SubClassedInjectedRecord < InjectedRecord; end
198
+ class DistantAncestorOfInjectedRecord < SubClassedInjectedRecord; end
199
+ class InjectedRecordObserver < ActiveRecord::Observer
200
+ inject :my_object, :another_object
201
+ attr_reader :my_object, :another_object
202
+ end
203
+
204
+ # Bad inheritance hierarchy, but whatever
205
+ class BigBang < ActiveRecord::Base; end
206
+ class BigBangObserver < ActiveRecord::Observer
207
+ inject :my_object
208
+ attr_accessor :my_object
209
+ end
210
+
211
+ class Galaxy < ActiveRecord::Base; end
212
+ class GalaxyObserver < BigBangObserver
213
+ inject :another_object
214
+ attr_accessor :another_object, :my_object
215
+ end
216
+
217
+ class SolarSystem < ActiveRecord::Base; end
218
+ class SolarSystemObserver < GalaxyObserver
219
+ end
220
+
221
+ class Banana < ActiveRecord::Base; end
222
+ class BananaObserver < ActiveRecord::Observer
223
+ inject :monkey
224
+
225
+ before :create do |banana|
226
+ @monkey.peels(banana)
227
+ end
228
+ end
@@ -0,0 +1,40 @@
1
+ # Configure Rails Envinronment
2
+ ENV["RAILS_ENV"] = "test"
3
+
4
+ require File.expand_path("../dummy/config/environment.rb", __FILE__)
5
+ require "rails/test_help"
6
+ require "rspec/rails"
7
+
8
+ # ActionMailer::Base.delivery_method = :test
9
+ # ActionMailer::Base.perform_deliveries = true
10
+ # ActionMailer::Base.default_url_options[:host] = "test.com"
11
+
12
+ Rails.backtrace_cleaner.remove_silencers!
13
+
14
+ # Run any available migration
15
+ ActiveRecord::Migrator.migrate File.expand_path("../dummy/db/migrate/", __FILE__)
16
+
17
+ # Load support files
18
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
19
+
20
+ module Helpers
21
+ def injection_context
22
+ Injection.context
23
+ end
24
+
25
+ def set_context(path)
26
+ Injection.context_file = path
27
+ Injection.reset_context
28
+ end
29
+ end
30
+
31
+ RSpec.configure do |config|
32
+ # Remove this line if you don't want RSpec's should and should_not
33
+ # methods or matchers
34
+ require 'rspec/expectations'
35
+ config.include RSpec::Matchers
36
+ config.include Helpers
37
+
38
+ # == Mock Framework
39
+ config.mock_with :rspec
40
+ end