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.

Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +19 -2
  3. data/Appraisals +8 -2
  4. data/CHANGELOG.md +260 -0
  5. data/README.md +32 -12
  6. data/gemfiles/rails40.gemfile +1 -1
  7. data/gemfiles/rails41.gemfile +1 -1
  8. data/gemfiles/rails42.gemfile +1 -1
  9. data/gemfiles/rails50.gemfile +1 -2
  10. data/gemfiles/rails51.gemfile +7 -0
  11. data/gemfiles/rails52.gemfile +8 -0
  12. data/lib/audited.rb +6 -4
  13. data/lib/audited/audit.rb +57 -8
  14. data/lib/audited/auditor.rb +54 -59
  15. data/lib/audited/rspec_matchers.rb +2 -2
  16. data/lib/audited/sweeper.rb +18 -29
  17. data/lib/audited/version.rb +1 -1
  18. data/lib/generators/audited/install_generator.rb +5 -0
  19. data/lib/generators/audited/migration_helper.rb +9 -0
  20. data/lib/generators/audited/templates/add_association_to_audits.rb +1 -1
  21. data/lib/generators/audited/templates/add_comment_to_audits.rb +1 -1
  22. data/lib/generators/audited/templates/add_remote_address_to_audits.rb +1 -1
  23. data/lib/generators/audited/templates/add_request_uuid_to_audits.rb +1 -1
  24. data/lib/generators/audited/templates/install.rb +5 -5
  25. data/lib/generators/audited/templates/rename_association_to_associated.rb +1 -1
  26. data/lib/generators/audited/templates/rename_changes_to_audited_changes.rb +1 -1
  27. data/lib/generators/audited/templates/rename_parent_to_association.rb +1 -1
  28. data/lib/generators/audited/templates/revert_polymorphic_indexes_order.rb +20 -0
  29. data/lib/generators/audited/upgrade_generator.rb +7 -0
  30. data/spec/audited/audit_spec.rb +71 -2
  31. data/spec/audited/auditor_spec.rb +69 -3
  32. data/spec/audited/sweeper_spec.rb +24 -6
  33. data/spec/audited_spec_helpers.rb +1 -1
  34. data/spec/spec_helper.rb +1 -1
  35. data/spec/support/active_record/models.rb +2 -1
  36. data/spec/support/active_record/postgres/1_change_audited_changes_type_to_json.rb +12 -0
  37. data/spec/support/active_record/postgres/2_change_audited_changes_type_to_jsonb.rb +12 -0
  38. data/spec/support/active_record/schema.rb +1 -0
  39. data/test/install_generator_test.rb +49 -3
  40. data/test/upgrade_generator_test.rb +16 -1
  41. metadata +14 -22
  42. data/CHANGELOG +0 -34
@@ -1,3 +1,3 @@
1
1
  module Audited
2
- VERSION = "4.3.0"
2
+ VERSION = "4.6.0"
3
3
  end
@@ -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
@@ -0,0 +1,9 @@
1
+ module Audited
2
+ module Generators
3
+ module MigrationHelper
4
+ def migration_parent
5
+ Rails::VERSION::MAJOR == 4 ? 'ActiveRecord::Migration' : "ActiveRecord::Migration[#{ActiveRecord::Migration.current_version}]"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -1,4 +1,4 @@
1
- class <%= migration_class_name %> < ActiveRecord::Migration
1
+ class <%= migration_class_name %> < <%= migration_parent %>
2
2
  def self.up
3
3
  add_column :audits, :association_id, :integer
4
4
  add_column :audits, :association_type, :string
@@ -1,4 +1,4 @@
1
- class <%= migration_class_name %> < ActiveRecord::Migration
1
+ class <%= migration_class_name %> < <%= migration_parent %>
2
2
  def self.up
3
3
  add_column :audits, :comment, :string
4
4
  end
@@ -1,4 +1,4 @@
1
- class <%= migration_class_name %> < ActiveRecord::Migration
1
+ class <%= migration_class_name %> < <%= migration_parent %>
2
2
  def self.up
3
3
  add_column :audits, :remote_address, :string
4
4
  end
@@ -1,4 +1,4 @@
1
- class <%= migration_class_name %> < ActiveRecord::Migration
1
+ class <%= migration_class_name %> < <%= migration_parent %>
2
2
  def self.up
3
3
  add_column :audits, :request_uuid, :string
4
4
  add_index :audits, :request_uuid
@@ -1,15 +1,15 @@
1
- class <%= migration_class_name %> < ActiveRecord::Migration
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, :integer
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, :text
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, [:auditable_id, :auditable_type], :name => 'auditable_index'
21
- add_index :audits, [:associated_id, :associated_type], :name => 'associated_index'
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 %> < ActiveRecord::Migration
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'
@@ -1,4 +1,4 @@
1
- class <%= migration_class_name %> < ActiveRecord::Migration
1
+ class <%= migration_class_name %> < <%= migration_parent %>
2
2
  def self.up
3
3
  rename_column :audits, :changes, :audited_changes
4
4
  end
@@ -1,4 +1,4 @@
1
- class <%= migration_class_name %> < ActiveRecord::Migration
1
+ class <%= migration_class_name %> < <%= migration_parent %>
2
2
  def self.up
3
3
  rename_column :audits, :auditable_parent_id, :association_id
4
4
  rename_column :audits, :auditable_parent_type, :association_type
@@ -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
@@ -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(Thread.current[:audited_user]).to be_nil
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
- expect(create_user.audits.first.audited_changes.keys.any? { |col| ['created_at', 'updated_at', 'password'].include?( col ) }).to eq(false)
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.audit_class, :count )
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
- Audited::Sweeper.instance.controller = 'thread1 controller instance'
92
- expect(Audited::Sweeper.instance.controller).to eq('thread1 controller instance')
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
- Audited::Sweeper.instance.controller = 'thread2 controller instance'
114
+ instance.controller = 'thread2 controller instance'
97
115
  sleep 1
98
- expect(Audited::Sweeper.instance.controller).to eq('thread2 controller instance')
116
+ expect(instance.controller).to eq('thread2 controller instance')
99
117
  end
100
118
 
101
119
  t1.join; t2.join
102
120
 
103
- expect(Audited::Sweeper.instance.controller).to be_nil
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 = {})
@@ -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