audited 4.2.1 → 4.4.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.
- checksums.yaml +4 -4
- data/.travis.yml +20 -9
- data/Appraisals +11 -6
- data/CHANGELOG.md +190 -0
- data/Gemfile +1 -13
- data/README.md +65 -34
- data/Rakefile +3 -18
- data/gemfiles/rails40.gemfile +1 -5
- data/gemfiles/rails41.gemfile +1 -5
- data/gemfiles/rails42.gemfile +1 -5
- data/gemfiles/rails50.gemfile +7 -0
- data/gemfiles/rails51.gemfile +7 -0
- data/lib/audited/audit.rb +126 -56
- data/lib/audited/auditor.rb +76 -44
- data/lib/audited/rspec_matchers.rb +5 -1
- data/lib/audited/sweeper.rb +24 -46
- data/lib/audited/version.rb +1 -1
- data/lib/audited-rspec.rb +4 -0
- data/lib/audited.rb +16 -2
- data/lib/generators/audited/install_generator.rb +24 -0
- data/lib/generators/audited/migration.rb +15 -0
- data/lib/generators/audited/migration_helper.rb +9 -0
- data/lib/generators/audited/templates/add_association_to_audits.rb +11 -0
- data/lib/generators/audited/templates/add_comment_to_audits.rb +9 -0
- data/lib/generators/audited/templates/add_remote_address_to_audits.rb +10 -0
- data/lib/generators/audited/templates/add_request_uuid_to_audits.rb +10 -0
- data/lib/generators/audited/templates/install.rb +30 -0
- data/lib/generators/audited/templates/rename_association_to_associated.rb +23 -0
- data/lib/generators/audited/templates/rename_changes_to_audited_changes.rb +9 -0
- data/lib/generators/audited/templates/rename_parent_to_association.rb +11 -0
- data/lib/generators/audited/upgrade_generator.rb +59 -0
- data/spec/audited/audit_spec.rb +246 -0
- data/spec/audited/auditor_spec.rb +648 -0
- data/spec/audited/sweeper_spec.rb +108 -0
- data/spec/audited_spec_helpers.rb +6 -22
- data/spec/rails_app/config/environments/test.rb +7 -4
- data/spec/rails_app/config/initializers/secret_token.rb +1 -1
- data/spec/rails_app/config/routes.rb +1 -4
- data/spec/spec_helper.rb +7 -9
- data/spec/support/active_record/models.rb +24 -13
- 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 +37 -12
- data/test/db/version_1.rb +4 -4
- data/test/db/version_2.rb +4 -4
- data/test/db/version_3.rb +4 -4
- data/test/db/version_4.rb +4 -4
- data/test/db/version_5.rb +2 -2
- data/test/db/version_6.rb +2 -2
- data/test/install_generator_test.rb +31 -3
- data/test/upgrade_generator_test.rb +25 -10
- metadata +69 -43
- data/CHANGELOG +0 -34
- data/lib/audited/active_record/version.rb +0 -5
- data/lib/audited/mongo_mapper/version.rb +0 -5
- data/spec/support/mongo_mapper/connection.rb +0 -4
- data/spec/support/mongo_mapper/models.rb +0 -214
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class <%= migration_class_name %> < <%= migration_parent %>
|
|
2
|
+
def self.up
|
|
3
|
+
add_column :audits, :association_id, :integer
|
|
4
|
+
add_column :audits, :association_type, :string
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def self.down
|
|
8
|
+
remove_column :audits, :association_type
|
|
9
|
+
remove_column :audits, :association_id
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
class <%= migration_class_name %> < <%= migration_parent %>
|
|
2
|
+
def self.up
|
|
3
|
+
create_table :audits, :force => true do |t|
|
|
4
|
+
t.column :auditable_id, :integer
|
|
5
|
+
t.column :auditable_type, :string
|
|
6
|
+
t.column :associated_id, :integer
|
|
7
|
+
t.column :associated_type, :string
|
|
8
|
+
t.column :user_id, :integer
|
|
9
|
+
t.column :user_type, :string
|
|
10
|
+
t.column :username, :string
|
|
11
|
+
t.column :action, :string
|
|
12
|
+
t.column :audited_changes, :<%= options[:audited_changes_column_type] %>
|
|
13
|
+
t.column :version, :integer, :default => 0
|
|
14
|
+
t.column :comment, :string
|
|
15
|
+
t.column :remote_address, :string
|
|
16
|
+
t.column :request_uuid, :string
|
|
17
|
+
t.column :created_at, :datetime
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
add_index :audits, [:auditable_id, :auditable_type], :name => 'auditable_index'
|
|
21
|
+
add_index :audits, [:associated_id, :associated_type], :name => 'associated_index'
|
|
22
|
+
add_index :audits, [:user_id, :user_type], :name => 'user_index'
|
|
23
|
+
add_index :audits, :request_uuid
|
|
24
|
+
add_index :audits, :created_at
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.down
|
|
28
|
+
drop_table :audits
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class <%= migration_class_name %> < <%= migration_parent %>
|
|
2
|
+
def self.up
|
|
3
|
+
if index_exists? :audits, [:association_id, :association_type], :name => 'association_index'
|
|
4
|
+
remove_index :audits, :name => 'association_index'
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
rename_column :audits, :association_id, :associated_id
|
|
8
|
+
rename_column :audits, :association_type, :associated_type
|
|
9
|
+
|
|
10
|
+
add_index :audits, [:associated_id, :associated_type], :name => 'associated_index'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.down
|
|
14
|
+
if index_exists? :audits, [:associated_id, :associated_type], :name => 'associated_index'
|
|
15
|
+
remove_index :audits, :name => 'associated_index'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
rename_column :audits, :associated_type, :association_type
|
|
19
|
+
rename_column :audits, :associated_id, :association_id
|
|
20
|
+
|
|
21
|
+
add_index :audits, [:association_id, :association_type], :name => 'association_index'
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class <%= migration_class_name %> < <%= migration_parent %>
|
|
2
|
+
def self.up
|
|
3
|
+
rename_column :audits, :auditable_parent_id, :association_id
|
|
4
|
+
rename_column :audits, :auditable_parent_type, :association_type
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def self.down
|
|
8
|
+
rename_column :audits, :association_type, :auditable_parent_type
|
|
9
|
+
rename_column :audits, :association_id, :auditable_parent_id
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require 'rails/generators'
|
|
2
|
+
require 'rails/generators/migration'
|
|
3
|
+
require 'active_record'
|
|
4
|
+
require 'rails/generators/active_record'
|
|
5
|
+
require 'generators/audited/migration'
|
|
6
|
+
require 'generators/audited/migration_helper'
|
|
7
|
+
|
|
8
|
+
module Audited
|
|
9
|
+
module Generators
|
|
10
|
+
class UpgradeGenerator < Rails::Generators::Base
|
|
11
|
+
include Rails::Generators::Migration
|
|
12
|
+
include Audited::Generators::MigrationHelper
|
|
13
|
+
extend Audited::Generators::Migration
|
|
14
|
+
|
|
15
|
+
source_root File.expand_path("../templates", __FILE__)
|
|
16
|
+
|
|
17
|
+
def copy_templates
|
|
18
|
+
migrations_to_be_applied do |m|
|
|
19
|
+
migration_template "#{m}.rb", "db/migrate/#{m}.rb"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def migrations_to_be_applied
|
|
26
|
+
Audited::Audit.reset_column_information
|
|
27
|
+
columns = Audited::Audit.columns.map(&:name)
|
|
28
|
+
|
|
29
|
+
yield :add_comment_to_audits unless columns.include?('comment')
|
|
30
|
+
|
|
31
|
+
if columns.include?('changes')
|
|
32
|
+
yield :rename_changes_to_audited_changes
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
unless columns.include?('remote_address')
|
|
36
|
+
yield :add_remote_address_to_audits
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
unless columns.include?('request_uuid')
|
|
40
|
+
yield :add_request_uuid_to_audits
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
unless columns.include?('association_id')
|
|
44
|
+
if columns.include?('auditable_parent_id')
|
|
45
|
+
yield :rename_parent_to_association
|
|
46
|
+
else
|
|
47
|
+
unless columns.include?('associated_id')
|
|
48
|
+
yield :add_association_to_audits
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
if columns.include?('association_id')
|
|
54
|
+
yield :rename_association_to_associated
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Audited::Audit do
|
|
4
|
+
let(:user) { Models::ActiveRecord::User.new name: "Testing" }
|
|
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
|
+
context "when a custom audit class is not configured" do
|
|
42
|
+
it "should default to #{described_class}" do
|
|
43
|
+
TempModel.audited
|
|
44
|
+
|
|
45
|
+
record = TempModel.create
|
|
46
|
+
|
|
47
|
+
audit = record.audits.first
|
|
48
|
+
expect(audit).to be_a Audited::Audit
|
|
49
|
+
expect(audit.respond_to?(:custom_method)).to be false
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
describe "user=" do
|
|
55
|
+
|
|
56
|
+
it "should be able to set the user to a model object" do
|
|
57
|
+
subject.user = user
|
|
58
|
+
expect(subject.user).to eq(user)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "should be able to set the user to nil" do
|
|
62
|
+
subject.user_id = 1
|
|
63
|
+
subject.user_type = 'Models::ActiveRecord::User'
|
|
64
|
+
subject.username = 'joe'
|
|
65
|
+
|
|
66
|
+
subject.user = nil
|
|
67
|
+
|
|
68
|
+
expect(subject.user).to be_nil
|
|
69
|
+
expect(subject.user_id).to be_nil
|
|
70
|
+
expect(subject.user_type).to be_nil
|
|
71
|
+
expect(subject.username).to be_nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "should be able to set the user to a string" do
|
|
75
|
+
subject.user = 'test'
|
|
76
|
+
expect(subject.user).to eq('test')
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "should clear model when setting to a string" do
|
|
80
|
+
subject.user = user
|
|
81
|
+
subject.user = 'testing'
|
|
82
|
+
expect(subject.user_id).to be_nil
|
|
83
|
+
expect(subject.user_type).to be_nil
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it "should clear the username when setting to a model" do
|
|
87
|
+
subject.username = 'test'
|
|
88
|
+
subject.user = user
|
|
89
|
+
expect(subject.username).to be_nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
describe "revision" do
|
|
95
|
+
|
|
96
|
+
it "should recreate attributes" do
|
|
97
|
+
user = Models::ActiveRecord::User.create name: "1"
|
|
98
|
+
5.times {|i| user.update_attribute :name, (i + 2).to_s }
|
|
99
|
+
|
|
100
|
+
user.audits.each do |audit|
|
|
101
|
+
expect(audit.revision.name).to eq(audit.version.to_s)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "should set protected attributes" do
|
|
106
|
+
u = Models::ActiveRecord::User.create(name: "Brandon")
|
|
107
|
+
u.update_attribute :logins, 1
|
|
108
|
+
u.update_attribute :logins, 2
|
|
109
|
+
|
|
110
|
+
expect(u.audits[2].revision.logins).to eq(2)
|
|
111
|
+
expect(u.audits[1].revision.logins).to eq(1)
|
|
112
|
+
expect(u.audits[0].revision.logins).to eq(0)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it "should bypass attribute assignment wrappers" do
|
|
116
|
+
u = Models::ActiveRecord::User.create(name: "<Joe>")
|
|
117
|
+
expect(u.audits.first.revision.name).to eq("<Joe>")
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it "should work for deleted records" do
|
|
121
|
+
user = Models::ActiveRecord::User.create name: "1"
|
|
122
|
+
user.destroy
|
|
123
|
+
revision = user.audits.last.revision
|
|
124
|
+
expect(revision.name).to eq(user.name)
|
|
125
|
+
expect(revision).to be_a_new_record
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it "should set the version number on create" do
|
|
130
|
+
user = Models::ActiveRecord::User.create! name: "Set Version Number"
|
|
131
|
+
expect(user.audits.first.version).to eq(1)
|
|
132
|
+
user.update_attribute :name, "Set to 2"
|
|
133
|
+
expect(user.audits.reload.first.version).to eq(1)
|
|
134
|
+
expect(user.audits.reload.last.version).to eq(2)
|
|
135
|
+
user.destroy
|
|
136
|
+
expect(Audited::Audit.where(auditable_type: "Models::ActiveRecord::User", auditable_id: user.id).last.version).to eq(3)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it "should set the request uuid on create" do
|
|
140
|
+
user = Models::ActiveRecord::User.create! name: "Set Request UUID"
|
|
141
|
+
expect(user.audits.reload.first.request_uuid).not_to be_blank
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
describe "reconstruct_attributes" do
|
|
145
|
+
it "should work with the old way of storing just the new value" do
|
|
146
|
+
audits = Audited::Audit.reconstruct_attributes([Audited::Audit.new(audited_changes: {"attribute" => "value"})])
|
|
147
|
+
expect(audits["attribute"]).to eq("value")
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
describe "audited_classes" do
|
|
152
|
+
class Models::ActiveRecord::CustomUser < ::ActiveRecord::Base
|
|
153
|
+
end
|
|
154
|
+
class Models::ActiveRecord::CustomUserSubclass < Models::ActiveRecord::CustomUser
|
|
155
|
+
audited
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it "should include audited classes" do
|
|
159
|
+
expect(Audited::Audit.audited_classes).to include(Models::ActiveRecord::User)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it "should include subclasses" do
|
|
163
|
+
expect(Audited::Audit.audited_classes).to include(Models::ActiveRecord::CustomUserSubclass)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
describe "new_attributes" do
|
|
168
|
+
it "should return a hash of the new values" do
|
|
169
|
+
new_attributes = Audited::Audit.new(audited_changes: {a: [1, 2], b: [3, 4]}).new_attributes
|
|
170
|
+
expect(new_attributes).to eq({"a" => 2, "b" => 4})
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
describe "old_attributes" do
|
|
175
|
+
it "should return a hash of the old values" do
|
|
176
|
+
old_attributes = Audited::Audit.new(audited_changes: {a: [1, 2], b: [3, 4]}).old_attributes
|
|
177
|
+
expect(old_attributes).to eq({"a" => 1, "b" => 3})
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
describe "as_user" do
|
|
182
|
+
it "should record user objects" do
|
|
183
|
+
Audited::Audit.as_user(user) do
|
|
184
|
+
company = Models::ActiveRecord::Company.create name: "The auditors"
|
|
185
|
+
company.name = "The Auditors, Inc"
|
|
186
|
+
company.save
|
|
187
|
+
|
|
188
|
+
company.audits.each do |audit|
|
|
189
|
+
expect(audit.user).to eq(user)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
it "should record usernames" do
|
|
195
|
+
Audited::Audit.as_user(user.name) do
|
|
196
|
+
company = Models::ActiveRecord::Company.create name: "The auditors"
|
|
197
|
+
company.name = "The Auditors, Inc"
|
|
198
|
+
company.save
|
|
199
|
+
|
|
200
|
+
company.audits.each do |audit|
|
|
201
|
+
expect(audit.username).to eq(user.name)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
it "should be thread safe" do
|
|
207
|
+
begin
|
|
208
|
+
expect(user.save).to eq(true)
|
|
209
|
+
|
|
210
|
+
t1 = Thread.new do
|
|
211
|
+
Audited::Audit.as_user(user) do
|
|
212
|
+
sleep 1
|
|
213
|
+
expect(Models::ActiveRecord::Company.create(name: "The Auditors, Inc").audits.first.user).to eq(user)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
t2 = Thread.new do
|
|
218
|
+
Audited::Audit.as_user(user.name) do
|
|
219
|
+
expect(Models::ActiveRecord::Company.create(name: "The Competing Auditors, LLC").audits.first.username).to eq(user.name)
|
|
220
|
+
sleep 0.5
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
t1.join
|
|
225
|
+
t2.join
|
|
226
|
+
end
|
|
227
|
+
end if ActiveRecord::Base.connection.adapter_name != 'SQLite'
|
|
228
|
+
|
|
229
|
+
it "should return the value from the yield block" do
|
|
230
|
+
result = Audited::Audit.as_user('foo') do
|
|
231
|
+
42
|
|
232
|
+
end
|
|
233
|
+
expect(result).to eq(42)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
it "should reset audited_user when the yield block raises an exception" do
|
|
237
|
+
expect {
|
|
238
|
+
Audited::Audit.as_user('foo') do
|
|
239
|
+
raise StandardError.new('expected')
|
|
240
|
+
end
|
|
241
|
+
}.to raise_exception('expected')
|
|
242
|
+
expect(Audited.store[:audited_user]).to be_nil
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
end
|
|
246
|
+
end
|