paper_trail 1.6.5 → 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 (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