paper_trail 1.6.5 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. data/.gitignore +6 -0
  2. data/Gemfile +2 -0
  3. data/Gemfile.lock +107 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +11 -5
  6. data/Rakefile +1 -1
  7. data/lib/paper_trail.rb +9 -4
  8. data/lib/paper_trail/controller.rb +0 -2
  9. data/lib/paper_trail/has_paper_trail.rb +2 -3
  10. data/lib/paper_trail/version.rb +15 -18
  11. data/lib/paper_trail/version_number.rb +1 -1
  12. data/paper_trail.gemspec +17 -24
  13. data/test/dummy/Rakefile +7 -0
  14. data/test/dummy/app/controllers/application_controller.rb +16 -0
  15. data/test/dummy/app/controllers/test_controller.rb +5 -0
  16. data/test/dummy/app/controllers/widgets_controller.rb +18 -0
  17. data/test/dummy/app/helpers/application_helper.rb +2 -0
  18. data/test/dummy/app/models/article.rb +11 -0
  19. data/test/dummy/app/models/authorship.rb +5 -0
  20. data/test/dummy/app/models/book.rb +5 -0
  21. data/test/dummy/app/models/fluxor.rb +3 -0
  22. data/test/dummy/app/models/foo_widget.rb +2 -0
  23. data/test/dummy/app/models/person.rb +5 -0
  24. data/test/dummy/app/models/song.rb +12 -0
  25. data/test/dummy/app/models/widget.rb +5 -0
  26. data/test/dummy/app/models/wotsit.rb +4 -0
  27. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  28. data/test/dummy/config.ru +4 -0
  29. data/test/dummy/config/application.rb +45 -0
  30. data/test/dummy/config/boot.rb +10 -0
  31. data/test/dummy/config/database.yml +22 -0
  32. data/test/dummy/config/environment.rb +5 -0
  33. data/test/dummy/config/environments/development.rb +26 -0
  34. data/test/dummy/config/environments/production.rb +49 -0
  35. data/test/dummy/config/environments/test.rb +35 -0
  36. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  37. data/test/dummy/config/initializers/inflections.rb +10 -0
  38. data/test/dummy/config/initializers/mime_types.rb +5 -0
  39. data/test/dummy/config/initializers/secret_token.rb +7 -0
  40. data/test/dummy/config/initializers/session_store.rb +8 -0
  41. data/test/dummy/config/locales/en.yml +5 -0
  42. data/test/dummy/config/routes.rb +3 -0
  43. data/test/dummy/db/development.sqlite3 +0 -0
  44. data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +84 -0
  45. data/test/dummy/db/schema.rb +82 -0
  46. data/test/dummy/db/test.sqlite3 +0 -0
  47. data/test/dummy/public/404.html +26 -0
  48. data/test/dummy/public/422.html +26 -0
  49. data/test/dummy/public/500.html +26 -0
  50. data/test/dummy/public/favicon.ico +0 -0
  51. data/test/dummy/public/javascripts/application.js +2 -0
  52. data/test/dummy/public/javascripts/controls.js +965 -0
  53. data/test/dummy/public/javascripts/dragdrop.js +974 -0
  54. data/test/dummy/public/javascripts/effects.js +1123 -0
  55. data/test/dummy/public/javascripts/prototype.js +6001 -0
  56. data/test/dummy/public/javascripts/rails.js +175 -0
  57. data/test/dummy/public/stylesheets/.gitkeep +0 -0
  58. data/test/dummy/script/rails +6 -0
  59. data/test/{paper_trail_controller_test.rb → functional/controller_test.rb} +7 -44
  60. data/test/{thread_safe_test.rb → functional/thread_safety_test.rb} +1 -7
  61. data/test/integration/navigation_test.rb +7 -0
  62. data/test/paper_trail_test.rb +7 -0
  63. data/test/support/integration_case.rb +5 -0
  64. data/test/test_helper.rb +27 -33
  65. data/test/{paper_trail_model_test.rb → unit/model_test.rb} +5 -68
  66. metadata +138 -123
  67. data/generators/paper_trail/USAGE +0 -2
  68. data/generators/paper_trail/paper_trail_generator.rb +0 -9
  69. data/generators/paper_trail/templates/create_versions.rb +0 -18
  70. data/init.rb +0 -1
  71. data/install.rb +0 -1
  72. data/test/paper_trail_schema_test.rb +0 -15
  73. data/test/schema.rb +0 -71
  74. data/test/schema_change.rb +0 -3
@@ -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'
@@ -1,47 +1,9 @@
1
1
  require 'test_helper'
2
2
 
3
- ActionController::Routing::Routes.draw do |map|
4
- map.resources :widgets
5
- end
6
-
7
- class ApplicationController < ActionController::Base
8
- def rescue_action(e)
9
- raise e
10
- end
11
-
12
- # Returns id of hypothetical current user
13
- def current_user
14
- 153
15
- end
16
-
17
- def info_for_paper_trail
18
- {:ip => request.remote_ip, :user_agent => request.user_agent}
19
- end
20
- end
21
-
22
- class WidgetsController < ApplicationController
23
- def create
24
- @widget = Widget.create params[:widget]
25
- head :ok
26
- end
27
-
28
- def update
29
- @widget = Widget.find params[:id]
30
- @widget.update_attributes params[:widget]
31
- head :ok
32
- end
33
-
34
- def destroy
35
- @widget = Widget.find params[:id]
36
- @widget.destroy
37
- head :ok
38
- end
39
- end
40
-
41
- class PaperTrailControllerTest < ActionController::TestCase
3
+ class ControllerTest < ActionController::TestCase
42
4
  tests WidgetsController
43
5
 
44
- def setup
6
+ setup do
45
7
  @request.env['REMOTE_ADDR'] = '127.0.0.1'
46
8
  end
47
9
 
@@ -70,9 +32,10 @@ class PaperTrailControllerTest < ActionController::TestCase
70
32
  assert_equal 1, w.versions.length
71
33
  delete :destroy, :id => w.id
72
34
  widget = assigns(:widget)
73
- assert_equal 2, widget.versions.length
74
- assert_equal 153, widget.versions.last.whodunnit.to_i
75
- assert_equal '127.0.0.1', widget.versions.last.ip
76
- assert_equal 'Rails Testing', widget.versions.last.user_agent
35
+ versions_for_widget = Version.with_item_keys('Widget', w.id)
36
+ assert_equal 2, versions_for_widget.length
37
+ assert_equal 153, versions_for_widget.last.whodunnit.to_i
38
+ assert_equal '127.0.0.1', versions_for_widget.last.ip
39
+ assert_equal 'Rails Testing', versions_for_widget.last.user_agent
77
40
  end
78
41
  end
@@ -1,12 +1,6 @@
1
1
  require 'test_helper'
2
2
 
3
- class TestController < ActionController::Base
4
- def current_user
5
- Thread.current.object_id
6
- end
7
- end
8
-
9
- class ThreadSafeTest < Test::Unit::TestCase
3
+ class ThreadSafetyTest < ActionController::TestCase
10
4
  should "be thread safe" do
11
5
  blocked = true
12
6
 
@@ -0,0 +1,7 @@
1
+ require 'test_helper'
2
+
3
+ class NavigationTest < ActiveSupport::IntegrationCase
4
+ test 'Sanity test' do
5
+ assert_kind_of Dummy::Application, Rails.application
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ require 'test_helper'
2
+
3
+ class PaperTrailTest < ActiveSupport::TestCase
4
+ test 'Sanity test' do
5
+ assert_kind_of Module, PaperTrail
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ # Define a bare test case to use with Capybara
2
+ class ActiveSupport::IntegrationCase < ActiveSupport::TestCase
3
+ include Capybara
4
+ include Rails.application.routes.url_helpers
5
+ end
data/test/test_helper.rb CHANGED
@@ -1,43 +1,37 @@
1
- require 'rubygems'
1
+ # Configure Rails Envinronment
2
+ ENV["RAILS_ENV"] = "test"
2
3
 
3
- require 'test/unit'
4
- require 'shoulda'
4
+ require File.expand_path("../dummy/config/environment.rb", __FILE__)
5
+ require "rails/test_help"
5
6
 
6
- require 'active_record'
7
- require 'action_controller'
8
- require 'action_controller/test_process'
9
- require 'active_support'
10
- require 'active_support/test_case'
7
+ #ActionMailer::Base.delivery_method = :test
8
+ #ActionMailer::Base.perform_deliveries = true
9
+ #ActionMailer::Base.default_url_options[:host] = "test.com"
11
10
 
12
- require File.expand_path('../../lib/paper_trail', __FILE__)
11
+ Rails.backtrace_cleaner.remove_silencers!
13
12
 
14
- def connect_to_database
15
- ActiveRecord::Base.establish_connection(
16
- :adapter => "sqlite3",
17
- :database => ":memory:"
18
- )
19
- ActiveRecord::Migration.verbose = false
20
- end
13
+ require 'shoulda'
21
14
 
22
- def load_schema
23
- connect_to_database
24
- load File.dirname(__FILE__) + '/schema.rb'
25
- end
15
+ # Configure capybara for integration testing
16
+ require "capybara/rails"
17
+ Capybara.default_driver = :rack_test
18
+ Capybara.default_selector = :css
26
19
 
27
- def change_schema
28
- load File.dirname(__FILE__) + '/schema_change.rb'
29
- end
20
+ # Run any available migration
21
+ ActiveRecord::Migrator.migrate File.expand_path("../dummy/db/migrate/", __FILE__)
30
22
 
31
- class ActiveRecord::Base
32
- def logger
33
- @logger ||= Logger.new(nil)
34
- end
35
- end
23
+ # Load support files
24
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
25
+
26
+
27
+ #
28
+ # Helpers
29
+ #
36
30
 
37
- class ActionController::Base
38
- def logger
39
- @logger ||= Logger.new(nil)
31
+ def change_schema
32
+ ActiveRecord::Migration.verbose = false
33
+ ActiveRecord::Schema.define do
34
+ remove_column :widgets, :sacrificial_column
40
35
  end
36
+ ActiveRecord::Migration.verbose = true
41
37
  end
42
-
43
- load_schema
@@ -1,69 +1,6 @@
1
1
  require 'test_helper'
2
2
 
3
- class Widget < ActiveRecord::Base
4
- has_paper_trail
5
- has_one :wotsit
6
- has_many :fluxors, :order => :name
7
- end
8
-
9
- class FooWidget < Widget
10
- end
11
-
12
- class Wotsit < ActiveRecord::Base
13
- has_paper_trail
14
- belongs_to :widget
15
- end
16
-
17
- class Fluxor < ActiveRecord::Base
18
- belongs_to :widget
19
- end
20
-
21
- class Article < ActiveRecord::Base
22
- has_paper_trail :ignore => [:title],
23
- :meta => {:answer => 42,
24
- :action => :action_data_provider_method,
25
- :question => Proc.new { "31 + 11 = #{31 + 11}" },
26
- :article_id => Proc.new { |article| article.id } }
27
-
28
- def action_data_provider_method
29
- self.object_id.to_s
30
- end
31
- end
32
-
33
- class Book < ActiveRecord::Base
34
- has_many :authorships, :dependent => :destroy
35
- has_many :authors, :through => :authorships, :source => :person
36
- has_paper_trail
37
- end
38
-
39
- class Authorship < ActiveRecord::Base
40
- belongs_to :book
41
- belongs_to :person
42
- has_paper_trail
43
- end
44
-
45
- class Person < ActiveRecord::Base
46
- has_many :authorships, :dependent => :destroy
47
- has_many :books, :through => :authorships
48
- has_paper_trail
49
- end
50
-
51
- # Example from 'Overwriting default accessors' in ActiveRecord::Base.
52
- class Song < ActiveRecord::Base
53
- has_paper_trail
54
-
55
- # Uses an integer of seconds to hold the length of the song
56
- def length=(minutes)
57
- write_attribute(:length, minutes.to_i * 60)
58
- end
59
- def length
60
- read_attribute(:length) / 60
61
- end
62
- end
63
-
64
-
65
- class HasPaperTrailModelTest < Test::Unit::TestCase
66
- load_schema
3
+ class HasPaperTrailModelTest < ActiveSupport::TestCase
67
4
 
68
5
  context 'A record' do
69
6
  setup { @article = Article.create }
@@ -184,15 +121,15 @@ class HasPaperTrailModelTest < Test::Unit::TestCase
184
121
  setup do
185
122
  @fluxor = @widget.fluxors.create :name => 'flux'
186
123
  @widget.destroy
187
- @reified_widget = @widget.versions.last.reify
124
+ @reified_widget = Version.last.reify
188
125
  end
189
126
 
190
127
  should 'record the correct event' do
191
- assert_match /destroy/i, @widget.versions.last.event
128
+ assert_match /destroy/i, Version.last.event
192
129
  end
193
130
 
194
131
  should 'have three previous versions' do
195
- assert_equal 3, @widget.versions.length
132
+ assert_equal 3, Version.with_item_keys('Widget', @widget.id).length
196
133
  end
197
134
 
198
135
  should 'be available in its previous version' do
@@ -395,7 +332,7 @@ class HasPaperTrailModelTest < Test::Unit::TestCase
395
332
  setup do
396
333
  PaperTrail.whodunnit = 'Charlie'
397
334
  @widget.destroy
398
- @version = @widget.versions.last
335
+ @version = Version.last
399
336
  end
400
337
 
401
338
  should 'track who made the change' do