paper_trail_without_deprecated 3.0.0.beta1

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 (105) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +21 -0
  5. data/CHANGELOG.md +68 -0
  6. data/Gemfile +2 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +979 -0
  9. data/Rakefile +18 -0
  10. data/gemfiles/3.0.gemfile +31 -0
  11. data/lib/generators/paper_trail/USAGE +2 -0
  12. data/lib/generators/paper_trail/install_generator.rb +23 -0
  13. data/lib/generators/paper_trail/templates/add_object_changes_column_to_versions.rb +9 -0
  14. data/lib/generators/paper_trail/templates/create_versions.rb +18 -0
  15. data/lib/paper_trail.rb +115 -0
  16. data/lib/paper_trail/cleaner.rb +34 -0
  17. data/lib/paper_trail/config.rb +14 -0
  18. data/lib/paper_trail/frameworks/cucumber.rb +31 -0
  19. data/lib/paper_trail/frameworks/rails.rb +79 -0
  20. data/lib/paper_trail/frameworks/rspec.rb +24 -0
  21. data/lib/paper_trail/frameworks/rspec/extensions.rb +20 -0
  22. data/lib/paper_trail/frameworks/sinatra.rb +31 -0
  23. data/lib/paper_trail/has_paper_trail.rb +308 -0
  24. data/lib/paper_trail/serializers/json.rb +17 -0
  25. data/lib/paper_trail/serializers/yaml.rb +17 -0
  26. data/lib/paper_trail/version.rb +200 -0
  27. data/lib/paper_trail/version_number.rb +3 -0
  28. data/paper_trail.gemspec +36 -0
  29. data/spec/models/widget_spec.rb +13 -0
  30. data/spec/paper_trail_spec.rb +47 -0
  31. data/spec/spec_helper.rb +41 -0
  32. data/test/custom_json_serializer.rb +13 -0
  33. data/test/dummy/Rakefile +7 -0
  34. data/test/dummy/app/controllers/application_controller.rb +17 -0
  35. data/test/dummy/app/controllers/test_controller.rb +5 -0
  36. data/test/dummy/app/controllers/widgets_controller.rb +31 -0
  37. data/test/dummy/app/helpers/application_helper.rb +2 -0
  38. data/test/dummy/app/models/animal.rb +4 -0
  39. data/test/dummy/app/models/article.rb +16 -0
  40. data/test/dummy/app/models/authorship.rb +5 -0
  41. data/test/dummy/app/models/book.rb +5 -0
  42. data/test/dummy/app/models/cat.rb +2 -0
  43. data/test/dummy/app/models/document.rb +4 -0
  44. data/test/dummy/app/models/dog.rb +2 -0
  45. data/test/dummy/app/models/elephant.rb +3 -0
  46. data/test/dummy/app/models/fluxor.rb +3 -0
  47. data/test/dummy/app/models/foo_widget.rb +2 -0
  48. data/test/dummy/app/models/legacy_widget.rb +4 -0
  49. data/test/dummy/app/models/person.rb +28 -0
  50. data/test/dummy/app/models/post.rb +4 -0
  51. data/test/dummy/app/models/protected_widget.rb +3 -0
  52. data/test/dummy/app/models/song.rb +12 -0
  53. data/test/dummy/app/models/translation.rb +4 -0
  54. data/test/dummy/app/models/widget.rb +10 -0
  55. data/test/dummy/app/models/wotsit.rb +4 -0
  56. data/test/dummy/app/versions/post_version.rb +3 -0
  57. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  58. data/test/dummy/config.ru +4 -0
  59. data/test/dummy/config/application.rb +63 -0
  60. data/test/dummy/config/boot.rb +10 -0
  61. data/test/dummy/config/database.yml +22 -0
  62. data/test/dummy/config/environment.rb +5 -0
  63. data/test/dummy/config/environments/development.rb +40 -0
  64. data/test/dummy/config/environments/production.rb +73 -0
  65. data/test/dummy/config/environments/test.rb +37 -0
  66. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  67. data/test/dummy/config/initializers/inflections.rb +10 -0
  68. data/test/dummy/config/initializers/mime_types.rb +5 -0
  69. data/test/dummy/config/initializers/paper_trail.rb +5 -0
  70. data/test/dummy/config/initializers/secret_token.rb +7 -0
  71. data/test/dummy/config/initializers/session_store.rb +8 -0
  72. data/test/dummy/config/locales/en.yml +5 -0
  73. data/test/dummy/config/routes.rb +3 -0
  74. data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +136 -0
  75. data/test/dummy/db/schema.rb +101 -0
  76. data/test/dummy/public/404.html +26 -0
  77. data/test/dummy/public/422.html +26 -0
  78. data/test/dummy/public/500.html +26 -0
  79. data/test/dummy/public/favicon.ico +0 -0
  80. data/test/dummy/public/javascripts/application.js +2 -0
  81. data/test/dummy/public/javascripts/controls.js +965 -0
  82. data/test/dummy/public/javascripts/dragdrop.js +974 -0
  83. data/test/dummy/public/javascripts/effects.js +1123 -0
  84. data/test/dummy/public/javascripts/prototype.js +6001 -0
  85. data/test/dummy/public/javascripts/rails.js +175 -0
  86. data/test/dummy/public/stylesheets/.gitkeep +0 -0
  87. data/test/dummy/script/rails +6 -0
  88. data/test/functional/controller_test.rb +90 -0
  89. data/test/functional/modular_sinatra_test.rb +44 -0
  90. data/test/functional/sinatra_test.rb +45 -0
  91. data/test/functional/thread_safety_test.rb +26 -0
  92. data/test/paper_trail_test.rb +27 -0
  93. data/test/test_helper.rb +40 -0
  94. data/test/unit/cleaner_test.rb +143 -0
  95. data/test/unit/inheritance_column_test.rb +43 -0
  96. data/test/unit/model_test.rb +1314 -0
  97. data/test/unit/protected_attrs_test.rb +46 -0
  98. data/test/unit/serializer_test.rb +117 -0
  99. data/test/unit/serializers/json_test.rb +40 -0
  100. data/test/unit/serializers/mixin_json_test.rb +36 -0
  101. data/test/unit/serializers/mixin_yaml_test.rb +49 -0
  102. data/test/unit/serializers/yaml_test.rb +40 -0
  103. data/test/unit/timestamp_test.rb +44 -0
  104. data/test/unit/version_test.rb +74 -0
  105. metadata +286 -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,90 @@
1
+ require 'test_helper'
2
+
3
+ class ControllerTest < ActionController::TestCase
4
+ tests WidgetsController
5
+
6
+ setup do
7
+ @request.env['REMOTE_ADDR'] = '127.0.0.1'
8
+ end
9
+
10
+ teardown do
11
+ PaperTrail.enabled_for_controller = true
12
+ end
13
+
14
+ test 'disable on create' do
15
+ @request.env['HTTP_USER_AGENT'] = 'Disable User-Agent'
16
+ post :create, :widget => { :name => 'Flugel' }
17
+ assert_equal 0, assigns(:widget).versions.length
18
+ end
19
+
20
+ test 'disable on update' do
21
+ @request.env['HTTP_USER_AGENT'] = 'Disable User-Agent'
22
+ post :create, :widget => { :name => 'Flugel' }
23
+ w = assigns(:widget)
24
+ assert_equal 0, w.versions.length
25
+ put :update, :id => w.id, :widget => { :name => 'Bugle' }
26
+ widget = assigns(:widget)
27
+ assert_equal 0, widget.versions.length
28
+ end
29
+
30
+ test 'disable on destroy' do
31
+ @request.env['HTTP_USER_AGENT'] = 'Disable User-Agent'
32
+ post :create, :widget => { :name => 'Flugel' }
33
+ w = assigns(:widget)
34
+ assert_equal 0, w.versions.length
35
+ delete :destroy, :id => w.id
36
+ widget = assigns(:widget)
37
+ assert_equal 0, PaperTrail::Version.with_item_keys('Widget', w.id).size
38
+ end
39
+
40
+ test 'create' do
41
+ post :create, :widget => { :name => 'Flugel' }
42
+ widget = assigns(:widget)
43
+ assert_equal 1, widget.versions.length
44
+ assert_equal 153, widget.versions.last.whodunnit.to_i
45
+ assert_equal '127.0.0.1', widget.versions.last.ip
46
+ assert_equal 'Rails Testing', widget.versions.last.user_agent
47
+ end
48
+
49
+ test 'update' do
50
+ w = Widget.create :name => 'Duvel'
51
+ assert_equal 1, w.versions.length
52
+ put :update, :id => w.id, :widget => { :name => 'Bugle' }
53
+ widget = assigns(:widget)
54
+ assert_equal 2, widget.versions.length
55
+ assert_equal 153, widget.versions.last.whodunnit.to_i
56
+ assert_equal '127.0.0.1', widget.versions.last.ip
57
+ assert_equal 'Rails Testing', widget.versions.last.user_agent
58
+ end
59
+
60
+ test 'destroy' do
61
+ w = Widget.create :name => 'Roundel'
62
+ assert_equal 1, w.versions.length
63
+ delete :destroy, :id => w.id
64
+ widget = assigns(:widget)
65
+ versions_for_widget = PaperTrail::Version.with_item_keys('Widget', w.id)
66
+ assert_equal 2, versions_for_widget.length
67
+ assert_equal 153, versions_for_widget.last.whodunnit.to_i
68
+ assert_equal '127.0.0.1', versions_for_widget.last.ip
69
+ assert_equal 'Rails Testing', versions_for_widget.last.user_agent
70
+ end
71
+
72
+ test "controller metadata methods should get evaluated if paper trail is enabled for controller" do
73
+ @request.env['HTTP_USER_AGENT'] = 'User-Agent'
74
+ post :create, :widget => { :name => 'Flugel' }
75
+ assert PaperTrail.enabled_for_controller?
76
+ assert_equal 153, PaperTrail.whodunnit
77
+ assert PaperTrail.controller_info.present?
78
+ assert PaperTrail.controller_info.keys.include?(:ip)
79
+ assert PaperTrail.controller_info.keys.include?(:user_agent)
80
+ end
81
+
82
+ test "controller metadata methods should not get evaluated if paper trail is disabled for controller" do
83
+ @request.env['HTTP_USER_AGENT'] = 'Disable User-Agent'
84
+ post :create, :widget => { :name => 'Flugel' }
85
+ assert_equal 0, assigns(:widget).versions.length
86
+ assert !PaperTrail.enabled_for_controller?
87
+ assert PaperTrail.whodunnit.nil?
88
+ assert PaperTrail.controller_info.nil?
89
+ end
90
+ end
@@ -0,0 +1,44 @@
1
+ require 'test_helper'
2
+ require 'sinatra/base'
3
+
4
+ # --- Tests for modular `Sinatra::Base` style ----
5
+ class BaseApp < Sinatra::Base
6
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => File.expand_path('../../dummy/db/test.sqlite3', __FILE__))
7
+ register Sinatra::PaperTrail
8
+
9
+ get '/test' do
10
+ Widget.create!(:name => 'foo')
11
+ 'Hello'
12
+ end
13
+
14
+ def current_user
15
+ 'foobar'
16
+ end
17
+ end
18
+
19
+ class ModularSinatraTest < ActionDispatch::IntegrationTest
20
+ include Rack::Test::Methods
21
+
22
+ def app
23
+ @app ||= BaseApp
24
+ end
25
+
26
+ test 'baseline' do
27
+ assert_nil Widget.first
28
+ assert_nil Widget.create.versions.first.whodunnit
29
+ end
30
+
31
+ context "`PaperTrail::Sinatra` in a `Sinatra::Base` application" do
32
+
33
+ should "sets the `user_for_paper_trail` from the `current_user` method" do
34
+ get '/test'
35
+ assert_equal 'Hello', last_response.body
36
+ widget = Widget.first
37
+ assert_not_nil widget
38
+ assert_equal 'foo', widget.name
39
+ assert_equal 1, widget.versions.size
40
+ assert_equal 'foobar', widget.versions.first.whodunnit
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,45 @@
1
+ require 'test_helper'
2
+ # require 'sinatra/main'
3
+
4
+ # --- Tests for non-modular `Sinatra::Application` style ----
5
+ class Sinatra::Application
6
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => File.expand_path('../../dummy/db/test.sqlite3', __FILE__))
7
+ register Sinatra::PaperTrail # we shouldn't actually need this line if I'm not mistaken but the tests seem to fail without it ATM
8
+
9
+ get '/test' do
10
+ Widget.create!(:name => 'bar')
11
+ 'Hai'
12
+ end
13
+
14
+ def current_user
15
+ 'raboof'
16
+ end
17
+
18
+ end
19
+
20
+ class SinatraTest < ActionDispatch::IntegrationTest
21
+ include Rack::Test::Methods
22
+
23
+ def app
24
+ @app ||= Sinatra::Application
25
+ end
26
+
27
+ test 'baseline' do
28
+ assert_nil Widget.first
29
+ assert_nil Widget.create.versions.first.whodunnit
30
+ end
31
+
32
+ context "`PaperTrail::Sinatra` in a `Sinatra::Application` application" do
33
+
34
+ should "sets the `user_for_paper_trail` from the `current_user` method" do
35
+ get '/test'
36
+ assert_equal 'Hai', last_response.body
37
+ widget = Widget.first
38
+ assert_not_nil widget
39
+ assert_equal 'bar', widget.name
40
+ assert_equal 1, widget.versions.size
41
+ assert_equal 'raboof', widget.versions.first.whodunnit
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,26 @@
1
+ require 'test_helper'
2
+
3
+ class ThreadSafetyTest < ActionController::TestCase
4
+ test "be thread safe" do
5
+ blocked = true
6
+
7
+ slow_thread = Thread.new do
8
+ controller = TestController.new
9
+ controller.send :set_whodunnit
10
+ begin
11
+ sleep 0.001
12
+ end while blocked
13
+ PaperTrail.whodunnit
14
+ end
15
+
16
+ fast_thread = Thread.new do
17
+ controller = TestController.new
18
+ controller.send :set_whodunnit
19
+ who = PaperTrail.whodunnit
20
+ blocked = false
21
+ who
22
+ end
23
+
24
+ assert_not_equal slow_thread.value, fast_thread.value
25
+ end
26
+ end
@@ -0,0 +1,27 @@
1
+ require 'test_helper'
2
+
3
+ class PaperTrailTest < ActiveSupport::TestCase
4
+ test 'Sanity test' do
5
+ assert_kind_of Module, PaperTrail::Version
6
+ end
7
+
8
+ test 'create with plain model class' do
9
+ widget = Widget.create
10
+ assert_equal 1, widget.versions.length
11
+ end
12
+
13
+ test 'update with plain model class' do
14
+ widget = Widget.create
15
+ assert_equal 1, widget.versions.length
16
+ widget.update_attributes(:name => 'Bugle')
17
+ assert_equal 2, widget.versions.length
18
+ end
19
+
20
+ test 'destroy with plain model class' do
21
+ widget = Widget.create
22
+ assert_equal 1, widget.versions.length
23
+ widget.destroy
24
+ versions_for_widget = PaperTrail::Version.with_item_keys('Widget', widget.id)
25
+ assert_equal 2, versions_for_widget.length
26
+ end
27
+ 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
+
7
+ #ActionMailer::Base.delivery_method = :test
8
+ #ActionMailer::Base.perform_deliveries = true
9
+ #ActionMailer::Base.default_url_options[:host] = "test.com"
10
+
11
+ Rails.backtrace_cleaner.remove_silencers!
12
+
13
+ require 'shoulda'
14
+ require 'ffaker'
15
+
16
+ # Run any available migration
17
+ ActiveRecord::Migrator.migrate File.expand_path("../dummy/db/migrate/", __FILE__)
18
+
19
+ # Load support files
20
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
21
+
22
+ # global setup block resetting Thread.current
23
+ class ActiveSupport::TestCase
24
+ teardown do
25
+ Thread.current[:paper_trail] = nil
26
+ end
27
+ end
28
+
29
+ #
30
+ # Helpers
31
+ #
32
+
33
+ def change_schema
34
+ ActiveRecord::Migration.verbose = false
35
+ ActiveRecord::Schema.define do
36
+ remove_column :widgets, :sacrificial_column
37
+ add_column :versions, :custom_created_at, :datetime
38
+ end
39
+ ActiveRecord::Migration.verbose = true
40
+ end