audited 4.7.1 → 4.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6d424ccbbbd38610298c61507f9a31e8333a11b9
4
- data.tar.gz: 144e1fe365c5e745b5205184de45f9882d8d2cb5
3
+ metadata.gz: bd000e9aee82f6d9bc8f26875cf255276e1c03c4
4
+ data.tar.gz: 71228c6fd0026f14f1c79ffc092fa212c680ec12
5
5
  SHA512:
6
- metadata.gz: 5d555a6c56d918fd09a8b691f5bc9634949aa7b0c409646e593bca23e0971da6d57b11577dbd50f9d31c271f790875783c64379b79a77d2d43c3f059b8ae7339
7
- data.tar.gz: d09615fefeb971c56f5fec72ec2b3674d316bd2acbaa4e80fccb0cc6a6c27bcb11ad0fd8c9d83e69872f7bfe900e6fba140522cb99d561d41bbb0e380f19d6d0
6
+ metadata.gz: 85c69841d137bc726c684589a1b4ec6e2352fa3f1751cd20c38431f66dd67c459766c842f7582cda9cabfadd235d3c1b2b37548408a23118d14b194ac45e6f1b
7
+ data.tar.gz: 1c1a0854e666f9255972563fe2c32bb708fd1f4ad271144d1062a765c22a40d929a8bc2301278ae30bb162d214b19ea24046c35478f10c56af2748ef7205582a
@@ -1,6 +1,6 @@
1
1
  # Audited ChangeLog
2
2
 
3
- ## Unreleased
3
+ ## 4.8.0 (2018-08-19)
4
4
 
5
5
  Breaking changes
6
6
 
@@ -8,11 +8,21 @@ Breaking changes
8
8
 
9
9
  Added
10
10
 
11
- - None
11
+ - Add ability to globally disable auditing
12
+ [#426](https://github.com/collectiveidea/audited/pull/426)
13
+ - Add `own_and_associated_audits` method to auditable models
14
+ [#428](https://github.com/collectiveidea/audited/pull/428)
15
+ - Ability to nest `as_user` within itself
16
+ [#450](https://github.com/collectiveidea/audited/pull/450)
17
+ - Private methods can now be used for conditional auditing
18
+ [#454](https://github.com/collectiveidea/audited/pull/454)
12
19
 
13
20
  Changed
14
21
 
15
- - None
22
+ - Add version to `auditable_index`
23
+ [#427](https://github.com/collectiveidea/audited/pull/427)
24
+ - Rename audited resource revision `version` attribute to `audit_version` and deprecate `version` attribute
25
+ [#443](https://github.com/collectiveidea/audited/pull/443)
16
26
 
17
27
  Fixed
18
28
 
@@ -30,7 +40,8 @@ Added
30
40
 
31
41
  Changed
32
42
 
33
- - None
43
+ - Change/deprecate `version` attribute to `audit_version` attribute
44
+ [#443](https://github.com/collectiveidea/audited/pull/443)
34
45
 
35
46
  Fixed
36
47
 
data/README.md CHANGED
@@ -193,7 +193,7 @@ Outside of a request, Audited can still record the user with the `as_user` metho
193
193
 
194
194
  ```ruby
195
195
  Audited.audit_class.as_user(User.find(1)) do
196
- post.update_attribute!(title: "Hello, world!")
196
+ post.update_attributes!(title: "Hello, world!")
197
197
  end
198
198
  post.audits.last.user # => #<User id: 1>
199
199
  ```
@@ -255,6 +255,11 @@ user.audits.last.associated # => #<Company name: "Collective Idea">
255
255
  company.associated_audits.last.auditable # => #<User name: "Steve Richert">
256
256
  ```
257
257
 
258
+ You can access records' own audits and associated audits in one go:
259
+ ```ruby
260
+ company.own_and_associated_audits
261
+ ```
262
+
258
263
  ### Conditional auditing
259
264
 
260
265
  If you want to audit only under specific conditions, you can provide conditional options (similar to ActiveModel callbacks) that will ensure your model is only audited for these conditions.
@@ -312,6 +317,12 @@ To disable auditing on an entire model:
312
317
  User.auditing_enabled = false
313
318
  ```
314
319
 
320
+ To disable auditing on all models:
321
+
322
+ ```ruby
323
+ Audited.auditing_enabled = false
324
+ ```
325
+
315
326
  ### Custom `Audit` model
316
327
 
317
328
  If you want to extend or modify the audit model, create a new class that
@@ -2,7 +2,7 @@ require 'active_record'
2
2
 
3
3
  module Audited
4
4
  class << self
5
- attr_accessor :ignored_attributes, :current_user_method, :max_audits
5
+ attr_accessor :ignored_attributes, :current_user_method, :max_audits, :auditing_enabled
6
6
  attr_writer :audit_class
7
7
 
8
8
  def audit_class
@@ -21,6 +21,7 @@ module Audited
21
21
  @ignored_attributes = %w(lock_version created_at updated_at created_on updated_on)
22
22
 
23
23
  @current_user_method = :current_user
24
+ @auditing_enabled = true
24
25
  end
25
26
 
26
27
  require 'audited/auditor'
@@ -65,7 +65,7 @@ module Audited
65
65
  def revision
66
66
  clazz = auditable_type.constantize
67
67
  (clazz.find_by_id(auditable_id) || clazz.new).tap do |m|
68
- self.class.assign_revision_attributes(m, self.class.reconstruct_attributes(ancestors).merge(version: version))
68
+ self.class.assign_revision_attributes(m, self.class.reconstruct_attributes(ancestors).merge(audit_version: version))
69
69
  end
70
70
  end
71
71
 
@@ -88,20 +88,18 @@ module Audited
88
88
 
89
89
  # Allows user to undo changes
90
90
  def undo
91
- model = self.auditable_type.constantize
92
- if action == 'create'
91
+ case action
92
+ when 'create'
93
93
  # destroys a newly created record
94
- model.find(auditable_id).destroy!
95
- elsif action == 'destroy'
94
+ auditable.destroy!
95
+ when 'destroy'
96
96
  # creates a new record with the destroyed record attributes
97
- model.create(audited_changes)
98
- else
97
+ auditable_type.constantize.create!(audited_changes)
98
+ when 'update'
99
99
  # changes back attributes
100
- audited_object = model.find(auditable_id)
101
- self.audited_changes.each do |k, v|
102
- audited_object[k] = v[0]
103
- end
104
- audited_object.save
100
+ auditable.update_attributes!(audited_changes.transform_values(&:first))
101
+ else
102
+ raise StandardError, "invalid action given #{action}"
105
103
  end
106
104
  end
107
105
 
@@ -133,21 +131,22 @@ module Audited
133
131
  # by +user+. This method is hopefully threadsafe, making it ideal
134
132
  # for background operations that require audit information.
135
133
  def self.as_user(user, &block)
134
+ last_audited_user = ::Audited.store[:audited_user]
136
135
  ::Audited.store[:audited_user] = user
137
136
  yield
138
137
  ensure
139
- ::Audited.store[:audited_user] = nil
138
+ ::Audited.store[:audited_user] = last_audited_user
140
139
  end
141
140
 
142
141
  # @private
143
142
  def self.reconstruct_attributes(audits)
144
143
  attributes = {}
145
144
  result = audits.collect do |audit|
146
- attributes.merge!(audit.new_attributes)[:version] = audit.version
145
+ attributes.merge!(audit.new_attributes)[:audit_version] = audit.version
147
146
  yield attributes if block_given?
148
147
  end
149
148
  block_given? ? result : attributes
150
- end
149
+ end
151
150
 
152
151
  # @private
153
152
  def self.assign_revision_attributes(record, attributes)
@@ -171,7 +170,7 @@ module Audited
171
170
  private
172
171
 
173
172
  def set_version_number
174
- max = self.class.auditable_finder(auditable_id, auditable_type).descending.first.try(:version) || 0
173
+ max = self.class.auditable_finder(auditable_id, auditable_type).maximum(:version) || 0
175
174
  self.version = max + 1
176
175
  end
177
176
 
@@ -55,7 +55,7 @@ module Audited
55
55
 
56
56
  class_attribute :audit_associated_with, instance_writer: false
57
57
  class_attribute :audited_options, instance_writer: false
58
- attr_accessor :version, :audit_comment
58
+ attr_accessor :audit_version, :audit_comment
59
59
 
60
60
  self.audited_options = options
61
61
  normalize_audited_options
@@ -90,6 +90,16 @@ module Audited
90
90
  end
91
91
 
92
92
  module AuditedInstanceMethods
93
+ # Deprecate version attribute in favor of audit_version attribute – preparing for eventual removal.
94
+ def method_missing(method_name, *args, &block)
95
+ if method_name == :version
96
+ ActiveSupport::Deprecation.warn("`version` attribute has been changed to `audit_version`. This attribute will be removed.")
97
+ audit_version
98
+ else
99
+ super
100
+ end
101
+ end
102
+
93
103
  # Temporarily turns off auditing while saving.
94
104
  def save_without_auditing
95
105
  without_auditing { save }
@@ -145,6 +155,14 @@ module Audited
145
155
  attributes.except(*non_audited_columns)
146
156
  end
147
157
 
158
+ # Returns a list combined of record audits and associated audits.
159
+ def own_and_associated_audits
160
+ Audited.audit_class.unscoped
161
+ .where('(auditable_type = :type AND auditable_id = :id) OR (associated_type = :type AND associated_id = :id)',
162
+ type: self.class.name, id: id)
163
+ .order(created_at: :desc)
164
+ end
165
+
148
166
  # Combine multiple audits into one.
149
167
  def combine_audits(audits_to_combine)
150
168
  combine_target = audits_to_combine.last
@@ -210,8 +228,8 @@ module Audited
210
228
 
211
229
  def audits_to(version = nil)
212
230
  if version == :previous
213
- version = if self.version
214
- self.version - 1
231
+ version = if self.audit_version
232
+ self.audit_version - 1
215
233
  else
216
234
  previous = audits.descending.offset(1).first
217
235
  previous ? previous.version : 1
@@ -290,9 +308,8 @@ module Audited
290
308
 
291
309
  def run_conditional_check(condition, matching: true)
292
310
  return true if condition.blank?
293
-
294
311
  return condition.call(self) == matching if condition.respond_to?(:call)
295
- return send(condition) == matching if respond_to?(condition.to_sym)
312
+ return send(condition) == matching if respond_to?(condition.to_sym, true)
296
313
 
297
314
  true
298
315
  end
@@ -355,7 +372,7 @@ module Audited
355
372
  end
356
373
 
357
374
  def auditing_enabled
358
- Audited.store.fetch("#{table_name}_auditing_enabled", true)
375
+ Audited.store.fetch("#{table_name}_auditing_enabled", true) && Audited.auditing_enabled
359
376
  end
360
377
 
361
378
  def auditing_enabled=(val)
@@ -1,3 +1,3 @@
1
1
  module Audited
2
- VERSION = "4.7.1"
2
+ VERSION = "4.8.0"
3
3
  end
@@ -0,0 +1,21 @@
1
+ class <%= migration_class_name %> < <%= migration_parent %>
2
+ def self.up
3
+ if index_exists?(:audits, [:auditable_type, :auditable_id], name: index_name)
4
+ remove_index :audits, name: index_name
5
+ add_index :audits, [:auditable_type, :auditable_id, :version], name: index_name
6
+ end
7
+ end
8
+
9
+ def self.down
10
+ if index_exists?(:audits, [:auditable_type, :auditable_id, :version], name: index_name)
11
+ remove_index :audits, name: index_name
12
+ add_index :audits, [:auditable_type, :auditable_id], name: index_name
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def index_name
19
+ 'auditable_index'
20
+ end
21
+ end
@@ -17,7 +17,7 @@ class <%= migration_class_name %> < <%= migration_parent %>
17
17
  t.column :created_at, :datetime
18
18
  end
19
19
 
20
- add_index :audits, [:auditable_type, :auditable_id], :name => 'auditable_index'
20
+ add_index :audits, [:auditable_type, :auditable_id, :version], :name => 'auditable_index'
21
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
@@ -58,6 +58,10 @@ module Audited
58
58
  if indexes.any? { |i| i.columns == %w[associated_id associated_type] }
59
59
  yield :revert_polymorphic_indexes_order
60
60
  end
61
+
62
+ if indexes.any? { |i| i.columns == %w[auditable_type auditable_id] }
63
+ yield :add_version_to_auditable_index
64
+ end
61
65
  end
62
66
  end
63
67
  end
@@ -213,6 +213,25 @@ describe Audited::Audit do
213
213
  end
214
214
  end
215
215
 
216
+ it "should support nested as_user" do
217
+ Audited::Audit.as_user("sidekiq") do
218
+ company = Models::ActiveRecord::Company.create name: "The auditors"
219
+ company.name = "The Auditors, Inc"
220
+ company.save
221
+ expect(company.audits[-1].user).to eq("sidekiq")
222
+
223
+ Audited::Audit.as_user(user) do
224
+ company.name = "NEW Auditors, Inc"
225
+ company.save
226
+ expect(company.audits[-1].user).to eq(user)
227
+ end
228
+
229
+ company.name = "LAST Auditors, Inc"
230
+ company.save
231
+ expect(company.audits[-1].user).to eq("sidekiq")
232
+ end
233
+ end
234
+
216
235
  it "should record usernames" do
217
236
  Audited::Audit.as_user(user.name) do
218
237
  company = Models::ActiveRecord::Company.create name: "The auditors"
@@ -20,6 +20,24 @@ describe Audited::Auditor do
20
20
  context "should be configurable which conditions are audited" do
21
21
  subject { ConditionalCompany.new.send(:auditing_enabled) }
22
22
 
23
+ context "when condition method is private" do
24
+ subject { ConditionalPrivateCompany.new.send(:auditing_enabled) }
25
+
26
+ before do
27
+ class ConditionalPrivateCompany < ::ActiveRecord::Base
28
+ self.table_name = 'companies'
29
+
30
+ audited if: :foo?
31
+
32
+ private def foo?
33
+ true
34
+ end
35
+ end
36
+ end
37
+
38
+ it { is_expected.to be_truthy }
39
+ end
40
+
23
41
  context "when passing a method name" do
24
42
  before do
25
43
  class ConditionalCompany < ::ActiveRecord::Base
@@ -581,21 +599,21 @@ describe Audited::Auditor do
581
599
  it "should find the given revision" do
582
600
  revision = user.revision(3)
583
601
  expect(revision).to be_a_kind_of( Models::ActiveRecord::User )
584
- expect(revision.version).to eq(3)
602
+ expect(revision.audit_version).to eq(3)
585
603
  expect(revision.name).to eq('Foobar 3')
586
604
  end
587
605
 
588
606
  it "should find the previous revision with :previous" do
589
607
  revision = user.revision(:previous)
590
- expect(revision.version).to eq(4)
608
+ expect(revision.audit_version).to eq(4)
591
609
  #expect(revision).to eq(user.revision(4))
592
610
  expect(revision.attributes).to eq(user.revision(4).attributes)
593
611
  end
594
612
 
595
613
  it "should be able to get the previous revision repeatedly" do
596
614
  previous = user.revision(:previous)
597
- expect(previous.version).to eq(4)
598
- expect(previous.revision(:previous).version).to eq(3)
615
+ expect(previous.audit_version).to eq(4)
616
+ expect(previous.revision(:previous).audit_version).to eq(3)
599
617
  end
600
618
 
601
619
  it "should be able to set protected attributes" do
@@ -669,7 +687,7 @@ describe Audited::Auditor do
669
687
  audit.created_at = 1.hour.ago
670
688
  audit.save!
671
689
  user.update_attributes name: 'updated'
672
- expect(user.revision_at( 2.minutes.ago ).version).to eq(1)
690
+ expect(user.revision_at( 2.minutes.ago ).audit_version).to eq(1)
673
691
  end
674
692
 
675
693
  it "should be nil if given a time before audits" do
@@ -677,6 +695,33 @@ describe Audited::Auditor do
677
695
  end
678
696
  end
679
697
 
698
+ describe "own_and_associated_audits" do
699
+ it "should return audits for self and associated audits" do
700
+ owner = Models::ActiveRecord::Owner.create!
701
+ company = owner.companies.create!
702
+ company.update!(name: "Collective Idea")
703
+
704
+ other_owner = Models::ActiveRecord::Owner.create!
705
+ other_company = other_owner.companies.create!
706
+
707
+ expect(owner.own_and_associated_audits).to match_array(owner.audits + company.audits)
708
+ end
709
+
710
+ it "should order audits by creation time" do
711
+ owner = Models::ActiveRecord::Owner.create!
712
+ first_audit = owner.audits.first
713
+ first_audit.update_column(:created_at, 1.year.ago)
714
+
715
+ company = owner.companies.create!
716
+ second_audit = company.audits.first
717
+ second_audit.update_column(:created_at, 1.month.ago)
718
+
719
+ company.update!(name: "Collective Idea")
720
+ third_audit = company.audits.last
721
+ expect(owner.own_and_associated_audits.to_a).to eq([third_audit, second_audit, first_audit])
722
+ end
723
+ end
724
+
680
725
  describe "without auditing" do
681
726
  it "should not save an audit when calling #save_without_auditing" do
682
727
  expect {
@@ -697,31 +742,44 @@ describe Audited::Auditor do
697
742
  end
698
743
 
699
744
  it "should be thread safe using a #without_auditing block" do
700
- begin
701
- t1 = Thread.new do
702
- expect(Models::ActiveRecord::User.auditing_enabled).to eq(true)
703
- Models::ActiveRecord::User.without_auditing do
704
- expect(Models::ActiveRecord::User.auditing_enabled).to eq(false)
705
- Models::ActiveRecord::User.create!( name: 'Bart' )
706
- sleep 1
707
- expect(Models::ActiveRecord::User.auditing_enabled).to eq(false)
708
- end
709
- expect(Models::ActiveRecord::User.auditing_enabled).to eq(true)
710
- end
711
-
712
- t2 = Thread.new do
713
- sleep 0.5
714
- expect(Models::ActiveRecord::User.auditing_enabled).to eq(true)
715
- Models::ActiveRecord::User.create!( name: 'Lisa' )
745
+ skip if Models::ActiveRecord::User.connection.class.name.include?("SQLite")
746
+
747
+ t1 = Thread.new do
748
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(true)
749
+ Models::ActiveRecord::User.without_auditing do
750
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(false)
751
+ Models::ActiveRecord::User.create!( name: 'Bart' )
752
+ sleep 1
753
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(false)
716
754
  end
717
- t1.join
718
- t2.join
755
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(true)
756
+ end
719
757
 
720
- expect(Models::ActiveRecord::User.find_by_name('Bart').audits.count).to eq(0)
721
- expect(Models::ActiveRecord::User.find_by_name('Lisa').audits.count).to eq(1)
722
- rescue ActiveRecord::StatementInvalid
723
- STDERR.puts "Thread safety tests cannot be run with SQLite"
758
+ t2 = Thread.new do
759
+ sleep 0.5
760
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(true)
761
+ Models::ActiveRecord::User.create!( name: 'Lisa' )
724
762
  end
763
+ t1.join
764
+ t2.join
765
+
766
+ expect(Models::ActiveRecord::User.find_by_name('Bart').audits.count).to eq(0)
767
+ expect(Models::ActiveRecord::User.find_by_name('Lisa').audits.count).to eq(1)
768
+ end
769
+
770
+ it "should not save an audit when auditing is globally disabled" do
771
+ expect(Audited.auditing_enabled).to eq(true)
772
+ Audited.auditing_enabled = false
773
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(false)
774
+
775
+ user = create_user
776
+ expect(user.audits.count).to eq(0)
777
+
778
+ Audited.auditing_enabled = true
779
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(true)
780
+
781
+ user.update_attributes(name: 'Test')
782
+ expect(user.audits.count).to eq(1)
725
783
  end
726
784
  end
727
785
 
@@ -6,3 +6,8 @@ module RailsApp
6
6
  config.i18n.enforce_available_locales = true
7
7
  end
8
8
  end
9
+
10
+ require 'active_record/connection_adapters/sqlite3_adapter'
11
+ if ActiveRecord::ConnectionAdapters::SQLite3Adapter.respond_to?(:represent_boolean_as_integer)
12
+ ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = true
13
+ end
@@ -91,6 +91,7 @@ module Models
91
91
 
92
92
  class Owner < ::ActiveRecord::Base
93
93
  self.table_name = 'users'
94
+ audited
94
95
  has_associated_audits
95
96
  has_many :companies, class_name: "OwnedCompany", dependent: :destroy
96
97
  end
@@ -14,4 +14,6 @@ ActiveRecord::Schema.define do
14
14
  t.column :associated_id, :integer
15
15
  t.column :associated_type, :string
16
16
  end
17
+
18
+ add_index :audits, [:auditable_type, :auditable_id], name: 'auditable_index'
17
19
  end
@@ -1,6 +1,6 @@
1
1
  ENV['RAILS_ENV'] = 'test'
2
2
 
3
- $:.unshift File.dirname(__FILE__)
3
+ $LOAD_PATH.unshift File.dirname(__FILE__)
4
4
 
5
5
  require File.expand_path('../../spec/rails_app/config/environment', __FILE__)
6
6
  require 'rails/test_help'
@@ -8,7 +8,6 @@ require 'rails/test_help'
8
8
  require 'audited'
9
9
 
10
10
  class ActiveSupport::TestCase
11
-
12
11
  setup do
13
12
  ActiveRecord::Migration.verbose = false
14
13
  end
@@ -79,6 +79,16 @@ class UpgradeGeneratorTest < Rails::Generators::TestCase
79
79
  end
80
80
  end
81
81
 
82
+ test "should add 'version' to auditable_index" do
83
+ load_schema 6
84
+
85
+ run_generator %w(upgrade)
86
+
87
+ assert_migration "db/migrate/add_version_to_auditable_index.rb" do |content|
88
+ assert_match(/add_index :audits, \[:auditable_type, :auditable_id, :version\]/, content)
89
+ end
90
+ end
91
+
82
92
  test "generate migration with correct AR migration parent" do
83
93
  load_schema 1
84
94
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: audited
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.7.1
4
+ version: 4.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brandon Keepers
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2018-04-11 00:00:00.000000000 Z
16
+ date: 2018-08-20 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: activerecord
@@ -160,6 +160,7 @@ files:
160
160
  - lib/generators/audited/templates/add_comment_to_audits.rb
161
161
  - lib/generators/audited/templates/add_remote_address_to_audits.rb
162
162
  - lib/generators/audited/templates/add_request_uuid_to_audits.rb
163
+ - lib/generators/audited/templates/add_version_to_auditable_index.rb
163
164
  - lib/generators/audited/templates/install.rb
164
165
  - lib/generators/audited/templates/rename_association_to_associated.rb
165
166
  - lib/generators/audited/templates/rename_changes_to_audited_changes.rb