audited 4.3.0 → 4.6.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of audited might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.travis.yml +19 -2
- data/Appraisals +8 -2
- data/CHANGELOG.md +260 -0
- data/README.md +32 -12
- data/gemfiles/rails40.gemfile +1 -1
- data/gemfiles/rails41.gemfile +1 -1
- data/gemfiles/rails42.gemfile +1 -1
- data/gemfiles/rails50.gemfile +1 -2
- data/gemfiles/rails51.gemfile +7 -0
- data/gemfiles/rails52.gemfile +8 -0
- data/lib/audited.rb +6 -4
- data/lib/audited/audit.rb +57 -8
- data/lib/audited/auditor.rb +54 -59
- data/lib/audited/rspec_matchers.rb +2 -2
- data/lib/audited/sweeper.rb +18 -29
- data/lib/audited/version.rb +1 -1
- data/lib/generators/audited/install_generator.rb +5 -0
- data/lib/generators/audited/migration_helper.rb +9 -0
- data/lib/generators/audited/templates/add_association_to_audits.rb +1 -1
- data/lib/generators/audited/templates/add_comment_to_audits.rb +1 -1
- data/lib/generators/audited/templates/add_remote_address_to_audits.rb +1 -1
- data/lib/generators/audited/templates/add_request_uuid_to_audits.rb +1 -1
- data/lib/generators/audited/templates/install.rb +5 -5
- data/lib/generators/audited/templates/rename_association_to_associated.rb +1 -1
- data/lib/generators/audited/templates/rename_changes_to_audited_changes.rb +1 -1
- data/lib/generators/audited/templates/rename_parent_to_association.rb +1 -1
- data/lib/generators/audited/templates/revert_polymorphic_indexes_order.rb +20 -0
- data/lib/generators/audited/upgrade_generator.rb +7 -0
- data/spec/audited/audit_spec.rb +71 -2
- data/spec/audited/auditor_spec.rb +69 -3
- data/spec/audited/sweeper_spec.rb +24 -6
- data/spec/audited_spec_helpers.rb +1 -1
- data/spec/spec_helper.rb +1 -1
- data/spec/support/active_record/models.rb +2 -1
- data/spec/support/active_record/postgres/1_change_audited_changes_type_to_json.rb +12 -0
- data/spec/support/active_record/postgres/2_change_audited_changes_type_to_jsonb.rb +12 -0
- data/spec/support/active_record/schema.rb +1 -0
- data/test/install_generator_test.rb +49 -3
- data/test/upgrade_generator_test.rb +16 -1
- metadata +14 -22
- data/CHANGELOG +0 -34
data/lib/audited/version.rb
CHANGED
@@ -3,13 +3,18 @@ require 'rails/generators/migration'
|
|
3
3
|
require 'active_record'
|
4
4
|
require 'rails/generators/active_record'
|
5
5
|
require 'generators/audited/migration'
|
6
|
+
require 'generators/audited/migration_helper'
|
6
7
|
|
7
8
|
module Audited
|
8
9
|
module Generators
|
9
10
|
class InstallGenerator < Rails::Generators::Base
|
10
11
|
include Rails::Generators::Migration
|
12
|
+
include Audited::Generators::MigrationHelper
|
11
13
|
extend Audited::Generators::Migration
|
12
14
|
|
15
|
+
class_option :audited_changes_column_type, type: :string, default: "text", required: false
|
16
|
+
class_option :audited_user_id_column_type, type: :string, default: "integer", required: false
|
17
|
+
|
13
18
|
source_root File.expand_path("../templates", __FILE__)
|
14
19
|
|
15
20
|
def copy_migration
|
@@ -1,15 +1,15 @@
|
|
1
|
-
class <%= migration_class_name %> <
|
1
|
+
class <%= migration_class_name %> < <%= migration_parent %>
|
2
2
|
def self.up
|
3
3
|
create_table :audits, :force => true do |t|
|
4
4
|
t.column :auditable_id, :integer
|
5
5
|
t.column :auditable_type, :string
|
6
6
|
t.column :associated_id, :integer
|
7
7
|
t.column :associated_type, :string
|
8
|
-
t.column :user_id, :
|
8
|
+
t.column :user_id, :<%= options[:audited_user_id_column_type] %>
|
9
9
|
t.column :user_type, :string
|
10
10
|
t.column :username, :string
|
11
11
|
t.column :action, :string
|
12
|
-
t.column :audited_changes, :
|
12
|
+
t.column :audited_changes, :<%= options[:audited_changes_column_type] %>
|
13
13
|
t.column :version, :integer, :default => 0
|
14
14
|
t.column :comment, :string
|
15
15
|
t.column :remote_address, :string
|
@@ -17,8 +17,8 @@ class <%= migration_class_name %> < ActiveRecord::Migration
|
|
17
17
|
t.column :created_at, :datetime
|
18
18
|
end
|
19
19
|
|
20
|
-
add_index :audits, [:
|
21
|
-
add_index :audits, [:
|
20
|
+
add_index :audits, [:auditable_type, :auditable_id], :name => 'auditable_index'
|
21
|
+
add_index :audits, [:associated_type, :associated_id], :name => 'associated_index'
|
22
22
|
add_index :audits, [:user_id, :user_type], :name => 'user_index'
|
23
23
|
add_index :audits, :request_uuid
|
24
24
|
add_index :audits, :created_at
|
@@ -1,4 +1,4 @@
|
|
1
|
-
class <%= migration_class_name %> <
|
1
|
+
class <%= migration_class_name %> < <%= migration_parent %>
|
2
2
|
def self.up
|
3
3
|
if index_exists? :audits, [:association_id, :association_type], :name => 'association_index'
|
4
4
|
remove_index :audits, :name => 'association_index'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class <%= migration_class_name %> < <%= migration_parent %>
|
2
|
+
def self.up
|
3
|
+
fix_index_order_for [:associated_id, :associated_type], 'associated_index'
|
4
|
+
fix_index_order_for [:auditable_id, :auditable_type], 'auditable_index'
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.down
|
8
|
+
fix_index_order_for [:associated_type, :associated_id], 'associated_index'
|
9
|
+
fix_index_order_for [:auditable_type, :auditable_id], 'auditable_index'
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def fix_index_order_for(columns, index_name)
|
15
|
+
if index_exists? :audits, columns, name: index_name
|
16
|
+
remove_index :audits, name: index_name
|
17
|
+
add_index :audits, columns.reverse, name: index_name
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -3,11 +3,13 @@ require 'rails/generators/migration'
|
|
3
3
|
require 'active_record'
|
4
4
|
require 'rails/generators/active_record'
|
5
5
|
require 'generators/audited/migration'
|
6
|
+
require 'generators/audited/migration_helper'
|
6
7
|
|
7
8
|
module Audited
|
8
9
|
module Generators
|
9
10
|
class UpgradeGenerator < Rails::Generators::Base
|
10
11
|
include Rails::Generators::Migration
|
12
|
+
include Audited::Generators::MigrationHelper
|
11
13
|
extend Audited::Generators::Migration
|
12
14
|
|
13
15
|
source_root File.expand_path("../templates", __FILE__)
|
@@ -23,6 +25,7 @@ module Audited
|
|
23
25
|
def migrations_to_be_applied
|
24
26
|
Audited::Audit.reset_column_information
|
25
27
|
columns = Audited::Audit.columns.map(&:name)
|
28
|
+
indexes = Audited::Audit.connection.indexes(Audited::Audit.table_name)
|
26
29
|
|
27
30
|
yield :add_comment_to_audits unless columns.include?('comment')
|
28
31
|
|
@@ -51,6 +54,10 @@ module Audited
|
|
51
54
|
if columns.include?('association_id')
|
52
55
|
yield :rename_association_to_associated
|
53
56
|
end
|
57
|
+
|
58
|
+
if indexes.any? { |i| i.columns == %w[associated_id associated_type] }
|
59
|
+
yield :revert_polymorphic_indexes_order
|
60
|
+
end
|
54
61
|
end
|
55
62
|
end
|
56
63
|
end
|
data/spec/audited/audit_spec.rb
CHANGED
@@ -3,6 +3,76 @@ require "spec_helper"
|
|
3
3
|
describe Audited::Audit do
|
4
4
|
let(:user) { Models::ActiveRecord::User.new name: "Testing" }
|
5
5
|
|
6
|
+
describe "audit class" do
|
7
|
+
around(:example) do |example|
|
8
|
+
original_audit_class = Audited.audit_class
|
9
|
+
|
10
|
+
class CustomAudit < Audited::Audit
|
11
|
+
def custom_method
|
12
|
+
"I'm custom!"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class TempModel < ::ActiveRecord::Base
|
17
|
+
self.table_name = :companies
|
18
|
+
end
|
19
|
+
|
20
|
+
example.run
|
21
|
+
|
22
|
+
Audited.config { |config| config.audit_class = original_audit_class }
|
23
|
+
Audited::Audit.audited_class_names.delete("TempModel")
|
24
|
+
Object.send(:remove_const, :TempModel)
|
25
|
+
Object.send(:remove_const, :CustomAudit)
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when a custom audit class is configured" do
|
29
|
+
it "should be used in place of #{described_class}" do
|
30
|
+
Audited.config { |config| config.audit_class = CustomAudit }
|
31
|
+
TempModel.audited
|
32
|
+
|
33
|
+
record = TempModel.create
|
34
|
+
|
35
|
+
audit = record.audits.first
|
36
|
+
expect(audit).to be_a CustomAudit
|
37
|
+
expect(audit.custom_method).to eq "I'm custom!"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should undo changes" do
|
42
|
+
user = Models::ActiveRecord::User.create(name: "John")
|
43
|
+
user.update_attribute(:name, 'Joe')
|
44
|
+
user.audits.last.undo
|
45
|
+
user.reload
|
46
|
+
|
47
|
+
expect(user.name).to eq("John")
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should undo destroyed model" do
|
51
|
+
user = Models::ActiveRecord::User.create(name: "John")
|
52
|
+
user.destroy
|
53
|
+
user.audits.last.undo
|
54
|
+
user = Models::ActiveRecord::User.find_by(name: "John")
|
55
|
+
expect(user.name).to eq("John")
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should undo created model" do
|
59
|
+
user = Models::ActiveRecord::User.create(name: "John")
|
60
|
+
expect {user.audits.last.undo}.to change(Models::ActiveRecord::User, :count).by(-1)
|
61
|
+
end
|
62
|
+
|
63
|
+
context "when a custom audit class is not configured" do
|
64
|
+
it "should default to #{described_class}" do
|
65
|
+
TempModel.audited
|
66
|
+
|
67
|
+
record = TempModel.create
|
68
|
+
|
69
|
+
audit = record.audits.first
|
70
|
+
expect(audit).to be_a Audited::Audit
|
71
|
+
expect(audit.respond_to?(:custom_method)).to be false
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
6
76
|
describe "user=" do
|
7
77
|
|
8
78
|
it "should be able to set the user to a model object" do
|
@@ -191,9 +261,8 @@ describe Audited::Audit do
|
|
191
261
|
raise StandardError.new('expected')
|
192
262
|
end
|
193
263
|
}.to raise_exception('expected')
|
194
|
-
expect(
|
264
|
+
expect(Audited.store[:audited_user]).to be_nil
|
195
265
|
end
|
196
266
|
|
197
267
|
end
|
198
|
-
|
199
268
|
end
|
@@ -17,7 +17,7 @@ describe Audited::Auditor do
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
it "should be configurable which attributes are not audited" do
|
20
|
+
it "should be configurable which attributes are not audited via ignored_attributes" do
|
21
21
|
Audited.ignored_attributes = ['delta', 'top_secret', 'created_at']
|
22
22
|
class Secret < ::ActiveRecord::Base
|
23
23
|
audited
|
@@ -26,8 +26,19 @@ describe Audited::Auditor do
|
|
26
26
|
expect(Secret.non_audited_columns).to include('delta', 'top_secret', 'created_at')
|
27
27
|
end
|
28
28
|
|
29
|
+
it "should be configurable which attributes are not audited via non_audited_columns=" do
|
30
|
+
class Secret2 < ::ActiveRecord::Base
|
31
|
+
audited
|
32
|
+
self.non_audited_columns = ['delta', 'top_secret', 'created_at']
|
33
|
+
end
|
34
|
+
|
35
|
+
expect(Secret2.non_audited_columns).to include('delta', 'top_secret', 'created_at')
|
36
|
+
end
|
37
|
+
|
29
38
|
it "should not save non-audited columns" do
|
30
|
-
|
39
|
+
Models::ActiveRecord::User.non_audited_columns = (Models::ActiveRecord::User.non_audited_columns << :favourite_device)
|
40
|
+
|
41
|
+
expect(create_user.audits.first.audited_changes.keys.any? { |col| ['favourite_device', 'created_at', 'updated_at', 'password'].include?( col ) }).to eq(false)
|
31
42
|
end
|
32
43
|
|
33
44
|
it "should not save other columns than specified in 'only' option" do
|
@@ -48,6 +59,57 @@ describe Audited::Auditor do
|
|
48
59
|
user.save!
|
49
60
|
expect(user.audits.last.audited_changes.keys).to eq(%w{password})
|
50
61
|
end
|
62
|
+
|
63
|
+
it "should save attributes not specified in 'except' option" do
|
64
|
+
user = Models::ActiveRecord::User.create
|
65
|
+
user.instance_eval do
|
66
|
+
def non_column_attr
|
67
|
+
@non_column_attr
|
68
|
+
end
|
69
|
+
|
70
|
+
def non_column_attr=(val)
|
71
|
+
attribute_will_change!("non_column_attr")
|
72
|
+
@non_column_attr = val
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
user.password = "password"
|
77
|
+
user.non_column_attr = "some value"
|
78
|
+
user.save!
|
79
|
+
expect(user.audits.last.audited_changes.keys).to eq(%w{non_column_attr})
|
80
|
+
end
|
81
|
+
|
82
|
+
if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' && Rails.version >= "4.2.0.0" # Postgres json and jsonb support was added in Rails 4.2
|
83
|
+
describe "'json' and 'jsonb' audited_changes column type" do
|
84
|
+
let(:migrations_path) { SPEC_ROOT.join("support/active_record/postgres") }
|
85
|
+
|
86
|
+
after do
|
87
|
+
ActiveRecord::Migrator.rollback([migrations_path])
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should work if column type is 'json'" do
|
91
|
+
ActiveRecord::Migrator.up([migrations_path], 1)
|
92
|
+
Audited::Audit.reset_column_information
|
93
|
+
expect(Audited::Audit.columns_hash["audited_changes"].sql_type).to eq("json")
|
94
|
+
|
95
|
+
user = Models::ActiveRecord::User.create
|
96
|
+
user.name = "new name"
|
97
|
+
user.save!
|
98
|
+
expect(user.audits.last.audited_changes).to eq({"name" => [nil, "new name"]})
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should work if column type is 'jsonb'" do
|
102
|
+
ActiveRecord::Migrator.up([migrations_path], 2)
|
103
|
+
Audited::Audit.reset_column_information
|
104
|
+
expect(Audited::Audit.columns_hash["audited_changes"].sql_type).to eq("jsonb")
|
105
|
+
|
106
|
+
user = Models::ActiveRecord::User.create
|
107
|
+
user.name = "new name"
|
108
|
+
user.save!
|
109
|
+
expect(user.audits.last.audited_changes).to eq({"name" => [nil, "new name"]})
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
51
113
|
end
|
52
114
|
|
53
115
|
describe :new do
|
@@ -409,6 +471,10 @@ describe Audited::Auditor do
|
|
409
471
|
user.revision(1).save!
|
410
472
|
}.to change( Models::ActiveRecord::User, :count ).by(1)
|
411
473
|
end
|
474
|
+
|
475
|
+
it "should return nil for values greater than the number of revisions" do
|
476
|
+
expect(user.revision(user.revisions.count + 1)).to be_nil
|
477
|
+
end
|
412
478
|
end
|
413
479
|
|
414
480
|
describe "revision_at" do
|
@@ -601,7 +667,7 @@ describe Audited::Auditor do
|
|
601
667
|
Models::ActiveRecord::Company.auditing_enabled = false
|
602
668
|
company.update_attributes name: 'STI auditors'
|
603
669
|
Models::ActiveRecord::Company.auditing_enabled = true
|
604
|
-
}.to_not change( Audited
|
670
|
+
}.to_not change( Audited::Audit, :count )
|
605
671
|
end
|
606
672
|
end
|
607
673
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
class AuditsController < ActionController::Base
|
4
|
+
before_action :populate_user
|
5
|
+
|
4
6
|
attr_reader :company
|
5
7
|
|
6
8
|
def create
|
@@ -17,6 +19,8 @@ class AuditsController < ActionController::Base
|
|
17
19
|
|
18
20
|
attr_accessor :current_user
|
19
21
|
attr_accessor :custom_user
|
22
|
+
|
23
|
+
def populate_user; end
|
20
24
|
end
|
21
25
|
|
22
26
|
describe AuditsController do
|
@@ -69,6 +73,18 @@ describe AuditsController do
|
|
69
73
|
expect(controller.company.audits.last.request_uuid).to eq("abc123")
|
70
74
|
end
|
71
75
|
|
76
|
+
it "should call current_user after controller callbacks" do
|
77
|
+
expect(controller).to receive(:populate_user) do
|
78
|
+
controller.send(:current_user=, user)
|
79
|
+
end
|
80
|
+
|
81
|
+
expect {
|
82
|
+
post :create
|
83
|
+
}.to change( Audited::Audit, :count )
|
84
|
+
|
85
|
+
expect(controller.company.audits.last.user).to eq(user)
|
86
|
+
end
|
87
|
+
|
72
88
|
end
|
73
89
|
|
74
90
|
describe "PUT update" do
|
@@ -76,7 +92,7 @@ describe AuditsController do
|
|
76
92
|
controller.send(:current_user=, user)
|
77
93
|
|
78
94
|
expect {
|
79
|
-
put :update, id: 123
|
95
|
+
put :update, Rails::VERSION::MAJOR == 4 ? {id: 123} : {params: {id: 123}}
|
80
96
|
}.to_not change( Audited::Audit, :count )
|
81
97
|
end
|
82
98
|
end
|
@@ -86,21 +102,23 @@ end
|
|
86
102
|
describe Audited::Sweeper do
|
87
103
|
|
88
104
|
it "should be thread-safe" do
|
105
|
+
instance = Audited::Sweeper.new
|
106
|
+
|
89
107
|
t1 = Thread.new do
|
90
108
|
sleep 0.5
|
91
|
-
|
92
|
-
expect(
|
109
|
+
instance.controller = 'thread1 controller instance'
|
110
|
+
expect(instance.controller).to eq('thread1 controller instance')
|
93
111
|
end
|
94
112
|
|
95
113
|
t2 = Thread.new do
|
96
|
-
|
114
|
+
instance.controller = 'thread2 controller instance'
|
97
115
|
sleep 1
|
98
|
-
expect(
|
116
|
+
expect(instance.controller).to eq('thread2 controller instance')
|
99
117
|
end
|
100
118
|
|
101
119
|
t1.join; t2.join
|
102
120
|
|
103
|
-
expect(
|
121
|
+
expect(instance.controller).to be_nil
|
104
122
|
end
|
105
123
|
|
106
124
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module AuditedSpecHelpers
|
2
2
|
|
3
3
|
def create_user(attrs = {})
|
4
|
-
Models::ActiveRecord::User.create({name: 'Brandon', username: 'brandon', password: 'password'}.merge(attrs))
|
4
|
+
Models::ActiveRecord::User.create({name: 'Brandon', username: 'brandon', password: 'password', favourite_device: 'Android Phone'}.merge(attrs))
|
5
5
|
end
|
6
6
|
|
7
7
|
def build_user(attrs = {})
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
ENV['RAILS_ENV'] = 'test'
|
2
2
|
|
3
|
+
require 'bundler'
|
3
4
|
if Bundler.definition.dependencies.map(&:name).include?('protected_attributes')
|
4
5
|
require 'protected_attributes'
|
5
6
|
end
|
@@ -8,7 +9,6 @@ require 'rspec/rails'
|
|
8
9
|
require 'audited'
|
9
10
|
require 'audited_spec_helpers'
|
10
11
|
require 'support/active_record/models'
|
11
|
-
load "audited/sweeper.rb" # force to reload sweeper
|
12
12
|
|
13
13
|
SPEC_ROOT = Pathname.new(File.expand_path('../', __FILE__))
|
14
14
|
|