mongoid-audit_log 0.3.0 → 0.6.1
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 +5 -5
- data/README.md +45 -0
- data/lib/mongoid/audit_log.rb +45 -13
- data/lib/mongoid/audit_log/changes.rb +11 -1
- data/lib/mongoid/audit_log/entry.rb +25 -5
- data/lib/mongoid/audit_log/restore.rb +94 -0
- data/lib/mongoid/audit_log/version.rb +1 -1
- data/mongoid-audit_log.gemspec +2 -2
- data/spec/mongoid/audit_log/entry_spec.rb +96 -0
- data/spec/mongoid/audit_log/restore_spec.rb +81 -0
- data/spec/mongoid/audit_log_spec.rb +90 -1
- metadata +12 -12
- data/lib/mongoid/audit_log/embedded_changes.rb +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 61de8364fc9a0e8554f5c421560717892f0031b3034fc6978df71de6f106d87d
|
4
|
+
data.tar.gz: '053874e715158f7623cf52121bce0aad06696517c9abaffe1c9891ecc67011c0'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 32c4792b0a76084ad7c3c6f493be3caf796000facb5b82fc0fe4b7948fc85a10c062b85f7dfc5f503fd051d890b50bb6b4d008ec44adb651e0e43431671b6b7c
|
7
|
+
data.tar.gz: 04ed8d08325d4a606764a39e5ac9134381883324ced95b70f3cd4915a8cba28dce0a517694f59110a33837f8a8e680011de25d2d5b610b287210551c6d9c3c86
|
data/README.md
CHANGED
@@ -44,6 +44,11 @@ If you want to log the user who made the change, pass that user to the record me
|
|
44
44
|
Mongoid::AuditLog.record(current_user) do
|
45
45
|
Model.create!
|
46
46
|
end
|
47
|
+
|
48
|
+
# or
|
49
|
+
|
50
|
+
Mongoid::AuditLog.current_modifier = current_user
|
51
|
+
Mongoid::AuditLog.enable
|
47
52
|
```
|
48
53
|
|
49
54
|
A basic implementation in a Rails app might look something like:
|
@@ -123,6 +128,46 @@ model.audit_log_entries.first.create? # => true
|
|
123
128
|
model.audit_log_entries.first.model_attributes # => {"name"=>"foo bar"}
|
124
129
|
```
|
125
130
|
|
131
|
+
### Restoring
|
132
|
+
|
133
|
+
You can restore models for `Mongoid::AuditLog::Entry` instances for deletions.
|
134
|
+
This works for both root and embedded documents.
|
135
|
+
|
136
|
+
Examples:
|
137
|
+
```ruby
|
138
|
+
model = Model.create!(:name => 'foo bar')
|
139
|
+
Mongoid::AuditLog.record { model.destroy }
|
140
|
+
|
141
|
+
entry = Mongoid::AuditLog::Entry.first
|
142
|
+
entry.restore!
|
143
|
+
|
144
|
+
model == Model.find_by(name: 'foo bar') # => true
|
145
|
+
```
|
146
|
+
|
147
|
+
It's possible to end up in a situation where a destroy entry cannot be restored, e.g. an entry deleting an embedded document for a root document that's already been deleted. In these scenarios, `Mongoid::AuditLog::Restore::InvalidRestore` will be raised.
|
148
|
+
|
149
|
+
### Disabling
|
150
|
+
|
151
|
+
The `AuditLog` module provides methods to included classes to allow explicit disabling or enabling of logging. This can be useful if a model includes the mixin indirectly through another mixin or inheritance.
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
class Parent
|
155
|
+
include Mongoid::Document
|
156
|
+
include Mongoid::AuditLog
|
157
|
+
end
|
158
|
+
|
159
|
+
class Child < Parent
|
160
|
+
disable_audit_log
|
161
|
+
end
|
162
|
+
|
163
|
+
class Grandchild < Child
|
164
|
+
enable_audit_log
|
165
|
+
end
|
166
|
+
|
167
|
+
Parent.audit_log_enabled? # => true
|
168
|
+
Child.audit_log_enabled? # => false
|
169
|
+
Grandchild.audit_log_enabled? # => true
|
170
|
+
```
|
126
171
|
|
127
172
|
## Contributing
|
128
173
|
|
data/lib/mongoid/audit_log.rb
CHANGED
@@ -2,7 +2,7 @@ require "mongoid/audit_log/version"
|
|
2
2
|
require "mongoid/audit_log/config"
|
3
3
|
require "mongoid/audit_log/entry"
|
4
4
|
require "mongoid/audit_log/changes"
|
5
|
-
require "mongoid/audit_log/
|
5
|
+
require "mongoid/audit_log/restore"
|
6
6
|
|
7
7
|
module Mongoid
|
8
8
|
module AuditLog
|
@@ -14,30 +14,54 @@ module Mongoid
|
|
14
14
|
|
15
15
|
AuditLog.actions.each do |action|
|
16
16
|
send("before_#{action}") do
|
17
|
-
set_audit_log_changes if
|
17
|
+
set_audit_log_changes if record_audit_log?
|
18
18
|
end
|
19
19
|
|
20
20
|
send("after_#{action}") do
|
21
|
-
save_audit_log_entry(action) if
|
21
|
+
save_audit_log_entry(action) if record_audit_log?
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
+
class_methods do
|
27
|
+
def enable_audit_log
|
28
|
+
@audit_log_enabled = true
|
29
|
+
end
|
30
|
+
|
31
|
+
def disable_audit_log
|
32
|
+
@audit_log_enabled = false
|
33
|
+
end
|
34
|
+
|
35
|
+
def audit_log_enabled?
|
36
|
+
!defined?(@audit_log_enabled) || @audit_log_enabled
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
26
40
|
def self.record(modifier = nil)
|
27
|
-
|
28
|
-
|
41
|
+
already_recording = recording?
|
42
|
+
enable unless already_recording
|
43
|
+
self.current_modifier = modifier
|
29
44
|
yield
|
30
45
|
ensure
|
31
|
-
|
32
|
-
|
46
|
+
disable unless already_recording
|
47
|
+
self.current_modifier = nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.enable
|
51
|
+
Thread.current[:mongoid_audit_log_recording] = true
|
33
52
|
end
|
34
53
|
|
35
54
|
def self.disable
|
36
|
-
|
55
|
+
already_recording = recording?
|
37
56
|
Thread.current[:mongoid_audit_log_recording] = false
|
38
|
-
|
39
|
-
|
40
|
-
|
57
|
+
|
58
|
+
if block_given?
|
59
|
+
begin
|
60
|
+
yield
|
61
|
+
ensure
|
62
|
+
Thread.current[:mongoid_audit_log_recording] = already_recording
|
63
|
+
end
|
64
|
+
end
|
41
65
|
end
|
42
66
|
|
43
67
|
def self.recording?
|
@@ -48,6 +72,14 @@ module Mongoid
|
|
48
72
|
Thread.current[:mongoid_audit_log_modifier]
|
49
73
|
end
|
50
74
|
|
75
|
+
def self.current_modifier=(modifier)
|
76
|
+
Thread.current[:mongoid_audit_log_modifier] = modifier
|
77
|
+
end
|
78
|
+
|
79
|
+
def record_audit_log?
|
80
|
+
AuditLog.recording? && self.class.audit_log_enabled?
|
81
|
+
end
|
82
|
+
|
51
83
|
private
|
52
84
|
|
53
85
|
def set_audit_log_changes
|
@@ -61,14 +93,14 @@ module Mongoid
|
|
61
93
|
:audited_type => self.class,
|
62
94
|
:audited_id => id,
|
63
95
|
:tracked_changes => @_audit_log_changes.all,
|
64
|
-
:model_attributes => attributes.
|
96
|
+
:model_attributes => attributes.deep_dup,
|
65
97
|
:document_path => traverse_association_chain
|
66
98
|
)
|
67
99
|
end
|
68
100
|
end
|
69
101
|
|
70
102
|
def traverse_association_chain(node = self, current_relation = nil)
|
71
|
-
relation = node.embedded? ? node.
|
103
|
+
relation = node.embedded? ? node.association_name.to_s : nil
|
72
104
|
list = node._parent ? traverse_association_chain(node._parent, relation) : []
|
73
105
|
list << { class_name: node.class.name, id: node.id, relation: current_relation }
|
74
106
|
list
|
@@ -61,7 +61,17 @@ module Mongoid
|
|
61
61
|
private
|
62
62
|
|
63
63
|
def embedded_changes
|
64
|
-
@embedded_changes ||=
|
64
|
+
@embedded_changes ||= model.embedded_relations.inject({}) do |memo, t|
|
65
|
+
name = t.first
|
66
|
+
embedded = model.send(name)
|
67
|
+
changes = Mongoid::AuditLog::Changes.extract_from(embedded)
|
68
|
+
|
69
|
+
if embedded.present? && changes.present?
|
70
|
+
memo[name] = changes
|
71
|
+
end
|
72
|
+
|
73
|
+
memo
|
74
|
+
end
|
65
75
|
end
|
66
76
|
end
|
67
77
|
end
|
@@ -9,8 +9,13 @@ module Mongoid
|
|
9
9
|
field :modifier_id, :type => String
|
10
10
|
field :model_attributes, :type => Hash
|
11
11
|
field :document_path, :type => Array
|
12
|
+
field :restored, :type => Boolean, default: false
|
12
13
|
|
13
|
-
|
14
|
+
if Gem::Version.new(Mongoid::VERSION) < Gem::Version.new('6.0.0.beta')
|
15
|
+
belongs_to :audited, :polymorphic => true
|
16
|
+
else
|
17
|
+
belongs_to :audited, :polymorphic => true, :optional => true
|
18
|
+
end
|
14
19
|
|
15
20
|
index({ :audited_id => 1, :audited_type => 1 })
|
16
21
|
index({ :modifier_id => 1 })
|
@@ -28,7 +33,9 @@ module Mongoid
|
|
28
33
|
|
29
34
|
def valid?(*)
|
30
35
|
result = super
|
31
|
-
|
36
|
+
if result && modifier.blank?
|
37
|
+
self.modifier = Mongoid::AuditLog.current_modifier
|
38
|
+
end
|
32
39
|
result
|
33
40
|
end
|
34
41
|
|
@@ -37,7 +44,7 @@ module Mongoid
|
|
37
44
|
nil
|
38
45
|
else
|
39
46
|
klass = Mongoid::AuditLog.modifier_class_name.constantize
|
40
|
-
klass.find(modifier_id)
|
47
|
+
klass.find(modifier_id) rescue nil
|
41
48
|
end
|
42
49
|
end
|
43
50
|
|
@@ -56,6 +63,8 @@ module Mongoid
|
|
56
63
|
end
|
57
64
|
|
58
65
|
def audited
|
66
|
+
return nil if audited_type.blank? || audited_id.blank?
|
67
|
+
|
59
68
|
if for_embedded_doc?
|
60
69
|
lookup_from_document_path
|
61
70
|
else
|
@@ -68,12 +77,23 @@ module Mongoid
|
|
68
77
|
return audited if root.blank?
|
69
78
|
|
70
79
|
if for_embedded_doc?
|
71
|
-
root['class_name'].constantize.
|
80
|
+
root['class_name'].constantize.where(id: root['id']).first
|
72
81
|
else
|
73
82
|
audited
|
74
83
|
end
|
75
84
|
end
|
76
85
|
|
86
|
+
def restore!
|
87
|
+
raise Restore::InvalidRestore if restored? || !destroy?
|
88
|
+
|
89
|
+
Restore.new(self).perform
|
90
|
+
update_attributes!(:restored => true)
|
91
|
+
end
|
92
|
+
|
93
|
+
def restorable?
|
94
|
+
destroy? && !restored? && Restore.new(self).valid?
|
95
|
+
end
|
96
|
+
|
77
97
|
def respond_to?(sym, *args)
|
78
98
|
key = sym.to_s
|
79
99
|
(model_attributes.present? && model_attributes.has_key?(key)) || super
|
@@ -103,7 +123,7 @@ module Mongoid
|
|
103
123
|
end
|
104
124
|
end
|
105
125
|
|
106
|
-
if path['relation'].blank?
|
126
|
+
if path['relation'].blank? || relation_match.blank?
|
107
127
|
return relation_match
|
108
128
|
else
|
109
129
|
relation_match.send(path['relation'])
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module AuditLog
|
3
|
+
class Restore
|
4
|
+
class DuplicateError < StandardError; end
|
5
|
+
class InvalidRestore < StandardError; end
|
6
|
+
|
7
|
+
attr_reader :entry
|
8
|
+
delegate :name, to: :restored
|
9
|
+
delegate :for_embedded_doc?, to: :entry
|
10
|
+
|
11
|
+
def initialize(entry)
|
12
|
+
@entry = entry
|
13
|
+
end
|
14
|
+
|
15
|
+
def valid?
|
16
|
+
!entry.for_embedded_doc? || restored_root.present?
|
17
|
+
end
|
18
|
+
|
19
|
+
def perform
|
20
|
+
restored.attributes = attributes
|
21
|
+
restored.save!
|
22
|
+
end
|
23
|
+
|
24
|
+
def attributes
|
25
|
+
@attributes ||=
|
26
|
+
begin
|
27
|
+
attrs = entry.model_attributes.deep_dup
|
28
|
+
restored.send(:process_localized_attributes, model_class, attrs)
|
29
|
+
attrs
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def model_class
|
34
|
+
entry.audited_type.constantize
|
35
|
+
end
|
36
|
+
|
37
|
+
def restored
|
38
|
+
@restored ||=
|
39
|
+
if entry.for_embedded_doc?
|
40
|
+
find_embedded_restored
|
41
|
+
else
|
42
|
+
find_root_restored
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def find_root_restored
|
47
|
+
model_class.new
|
48
|
+
end
|
49
|
+
|
50
|
+
def find_embedded_restored
|
51
|
+
raise InvalidRestore if restored_root.blank?
|
52
|
+
|
53
|
+
last_path = document_path.last
|
54
|
+
metadata = restored_root.class.reflect_on_association(last_path['relation'])
|
55
|
+
relation = restored_root.send(last_path['relation'])
|
56
|
+
|
57
|
+
if metadata.is_a?(Association::Embedded::EmbedsMany)
|
58
|
+
relation.build
|
59
|
+
elsif relation.present?
|
60
|
+
raise DuplicateError
|
61
|
+
else
|
62
|
+
restored_root.send("build_#{metadata.name}")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def restored_root
|
67
|
+
document_path.reduce(entry.root) do |current, path|
|
68
|
+
match = if document_path_matches?(path, current)
|
69
|
+
current
|
70
|
+
elsif current.respond_to?(:detect)
|
71
|
+
current.detect do |model|
|
72
|
+
document_path_matches?(path, model)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
if path == document_path.last
|
77
|
+
return match
|
78
|
+
else
|
79
|
+
match.send(path['relation'])
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def document_path
|
85
|
+
# don't need last because that entry represents the deleted doc
|
86
|
+
entry.document_path[0..-2]
|
87
|
+
end
|
88
|
+
|
89
|
+
def document_path_matches?(path, object)
|
90
|
+
object.class.name == path['class_name'] && object.id == path['id']
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/mongoid-audit_log.gemspec
CHANGED
@@ -21,8 +21,8 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.required_ruby_version = ">= 1.9.2"
|
22
22
|
spec.required_rubygems_version = ">= 1.3.6"
|
23
23
|
|
24
|
-
spec.add_runtime_dependency "mongoid", [">=
|
24
|
+
spec.add_runtime_dependency "mongoid", [">= 7.0"]
|
25
25
|
|
26
|
-
spec.add_development_dependency "bundler"
|
26
|
+
spec.add_development_dependency "bundler"
|
27
27
|
spec.add_development_dependency "rake"
|
28
28
|
end
|
@@ -68,11 +68,24 @@ module Mongoid
|
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
|
+
describe '#valid?' do
|
72
|
+
it 'does not override a manually set modifier' do
|
73
|
+
entry = Entry.new(:modifier_id => user.id)
|
74
|
+
entry.valid?
|
75
|
+
entry.modifier.should == user
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
71
79
|
describe '#modifier' do
|
72
80
|
it 'finds the modifier based on the configured class' do
|
73
81
|
entry = Entry.new(:modifier_id => user.id)
|
74
82
|
entry.modifier.should == user
|
75
83
|
end
|
84
|
+
|
85
|
+
it 'can handle a missing modifier' do
|
86
|
+
entry = Entry.new(:modifier_id => 'foo')
|
87
|
+
entry.modifier.should be_nil
|
88
|
+
end
|
76
89
|
end
|
77
90
|
|
78
91
|
describe '#audited' do
|
@@ -84,6 +97,10 @@ module Mongoid
|
|
84
97
|
entry = Entry.desc(:created_at).first
|
85
98
|
entry.audited.should == option
|
86
99
|
end
|
100
|
+
|
101
|
+
it 'returns nil if the audited info is blank' do
|
102
|
+
Entry.new.audited.should be_nil
|
103
|
+
end
|
87
104
|
end
|
88
105
|
|
89
106
|
describe '#modifier_id=' do
|
@@ -121,6 +138,85 @@ module Mongoid
|
|
121
138
|
entry.other.should == nil
|
122
139
|
end
|
123
140
|
end
|
141
|
+
|
142
|
+
describe '#root' do
|
143
|
+
it 'returns nil if cannot be found' do
|
144
|
+
product = Product.create!(:name => 'Foo bar')
|
145
|
+
AuditLog.record { product.variants.create! }
|
146
|
+
|
147
|
+
entry = Entry.desc(:created_at).first
|
148
|
+
product.destroy
|
149
|
+
entry.root.should be_nil
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe '#restore!' do
|
154
|
+
it 'marks the entry as restored' do
|
155
|
+
product = Product.create!(:name => 'Foo bar')
|
156
|
+
AuditLog.record { product.destroy }
|
157
|
+
|
158
|
+
entry = Entry.desc(:created_at).first
|
159
|
+
entry.restore!
|
160
|
+
entry.should be_restored
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'raises InvalidRestore if already restored' do
|
164
|
+
product = Product.create!(:name => 'Foo bar')
|
165
|
+
AuditLog.record { product.destroy }
|
166
|
+
|
167
|
+
entry = Entry.desc(:created_at).first
|
168
|
+
entry.restore!
|
169
|
+
|
170
|
+
expect { entry.restore! }.to raise_error(Restore::InvalidRestore)
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'raises InvalidRestore if not a destroy' do
|
174
|
+
product = Product.create!(:name => 'Foo bar')
|
175
|
+
AuditLog.record { product.variants.create! }
|
176
|
+
|
177
|
+
entry = Entry.desc(:created_at).first
|
178
|
+
expect { entry.restore! }.to raise_error(Restore::InvalidRestore)
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'raises InvalidRestore if the root is blank' do
|
182
|
+
product = Product.create!(:name => 'Foo bar')
|
183
|
+
product.variants.create!
|
184
|
+
AuditLog.record { product.variants.first.destroy }
|
185
|
+
product.destroy
|
186
|
+
|
187
|
+
entry = Entry.desc(:created_at).first
|
188
|
+
expect { entry.restore! }.to raise_error(Restore::InvalidRestore)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
describe '#restorable?' do
|
193
|
+
it 'is false if already restored' do
|
194
|
+
product = Product.create!(:name => 'Foo bar')
|
195
|
+
AuditLog.record { product.destroy }
|
196
|
+
|
197
|
+
entry = Entry.desc(:created_at).first
|
198
|
+
entry.restore!
|
199
|
+
entry.should_not be_restorable
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'is false if not a destroy' do
|
203
|
+
product = Product.create!(:name => 'Foo bar')
|
204
|
+
AuditLog.record { product.variants.create! }
|
205
|
+
|
206
|
+
entry = Entry.desc(:created_at).first
|
207
|
+
entry.should_not be_restorable
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'is false is root is blank' do
|
211
|
+
product = Product.create!(:name => 'Foo bar')
|
212
|
+
product.variants.create!
|
213
|
+
AuditLog.record { product.variants.first.destroy }
|
214
|
+
product.destroy
|
215
|
+
|
216
|
+
entry = Entry.desc(:created_at).first
|
217
|
+
entry.should_not be_restorable
|
218
|
+
end
|
219
|
+
end
|
124
220
|
end
|
125
221
|
end
|
126
222
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module AuditLog
|
5
|
+
describe Restore do
|
6
|
+
before(:all) do
|
7
|
+
class ::First
|
8
|
+
include Mongoid::Document
|
9
|
+
include Mongoid::AuditLog
|
10
|
+
|
11
|
+
field :name, type: String, localize: true
|
12
|
+
embeds_many :seconds
|
13
|
+
end
|
14
|
+
|
15
|
+
class ::Second
|
16
|
+
include Mongoid::Document
|
17
|
+
include Mongoid::AuditLog
|
18
|
+
|
19
|
+
field :name, type: String, localize: true
|
20
|
+
embedded_in :first
|
21
|
+
embeds_many :thirds
|
22
|
+
end
|
23
|
+
|
24
|
+
class ::Third
|
25
|
+
include Mongoid::Document
|
26
|
+
include Mongoid::AuditLog
|
27
|
+
|
28
|
+
field :name, type: String, localize: true
|
29
|
+
embedded_in :second
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
after(:all) do
|
34
|
+
[:First, :Second, :Third].each { |c| Object.send(:remove_const, c) }
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '.perform' do
|
38
|
+
it 'restores a root document' do
|
39
|
+
root = First.create!(name: 'Foo')
|
40
|
+
Mongoid::AuditLog.record { root.destroy }
|
41
|
+
restore = Restore.new(Mongoid::AuditLog::Entry.first)
|
42
|
+
restore.perform
|
43
|
+
|
44
|
+
restore.restored.should be_persisted
|
45
|
+
First.count.should == 1
|
46
|
+
restore.restored.should == First.first
|
47
|
+
restore.restored.name.should == 'Foo'
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'restores an embedded array document' do
|
51
|
+
root = First.create!(name: 'Foo', seconds: [{ name: 'Bar' }])
|
52
|
+
Mongoid::AuditLog.record { root.seconds.first.destroy }
|
53
|
+
restore = Restore.new(Mongoid::AuditLog::Entry.first)
|
54
|
+
restore.perform
|
55
|
+
root.reload
|
56
|
+
|
57
|
+
restore.restored.should be_persisted
|
58
|
+
root.seconds.length.should == 1
|
59
|
+
root.seconds.first.should == restore.restored
|
60
|
+
root.seconds.first.name.should == 'Bar'
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'restores a nested array document' do
|
64
|
+
root = First.create!(
|
65
|
+
name: 'Foo',
|
66
|
+
seconds: [{ name: 'Bar', thirds: [{ name: 'Baz' }] }]
|
67
|
+
)
|
68
|
+
Mongoid::AuditLog.record { root.seconds.first.thirds.first.destroy }
|
69
|
+
restore = Restore.new(Mongoid::AuditLog::Entry.first)
|
70
|
+
restore.perform
|
71
|
+
root.reload
|
72
|
+
|
73
|
+
restore.restored.should be_persisted
|
74
|
+
root.seconds.first.thirds.length.should == 1
|
75
|
+
root.seconds.first.thirds.first.should == restore.restored
|
76
|
+
root.seconds.first.thirds.first.name.should == 'Baz'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -16,6 +16,8 @@ module Mongoid
|
|
16
16
|
field :sku, :type => String
|
17
17
|
embedded_in :product
|
18
18
|
end
|
19
|
+
|
20
|
+
AuditLog.disable
|
19
21
|
end
|
20
22
|
|
21
23
|
after(:all) do
|
@@ -23,6 +25,10 @@ module Mongoid
|
|
23
25
|
Object.send(:remove_const, :Variant)
|
24
26
|
end
|
25
27
|
|
28
|
+
after(:each) do
|
29
|
+
Product.enable_audit_log
|
30
|
+
end
|
31
|
+
|
26
32
|
describe '.record' do
|
27
33
|
it 'does not save an entry if not recording' do
|
28
34
|
product = Product.create!
|
@@ -95,8 +101,31 @@ module Mongoid
|
|
95
101
|
end
|
96
102
|
end
|
97
103
|
|
104
|
+
describe '.enable' do
|
105
|
+
after(:each) do
|
106
|
+
AuditLog.disable
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'starts recording' do
|
110
|
+
AuditLog.enable
|
111
|
+
|
112
|
+
product = Product.create!(:name => 'Foo bar')
|
113
|
+
product.audit_log_entries.count.should == 1
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
98
117
|
describe '.disable' do
|
99
|
-
it '
|
118
|
+
it 'stops recording' do
|
119
|
+
AuditLog.enable
|
120
|
+
AuditLog.disable
|
121
|
+
|
122
|
+
product = Product.create!(:name => 'Foo bar')
|
123
|
+
product.audit_log_entries.should be_empty
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'can disable recording for a block' do
|
127
|
+
AuditLog.disable
|
128
|
+
|
100
129
|
AuditLog.record do
|
101
130
|
product = Product.create!(:name => 'Foo bar')
|
102
131
|
|
@@ -173,6 +202,15 @@ module Mongoid
|
|
173
202
|
entry.document_path.second['id'].should == variant.id
|
174
203
|
entry.document_path.second['relation'].should == nil
|
175
204
|
end
|
205
|
+
|
206
|
+
it 'does not record if model disables logging' do
|
207
|
+
product = Product.create!(:name => 'Foo bar')
|
208
|
+
product.audit_log_entries.count.should eq(1)
|
209
|
+
|
210
|
+
Product.disable_audit_log
|
211
|
+
product.update!(name: 'Foo bar baz')
|
212
|
+
product.audit_log_entries.count.should eq(1)
|
213
|
+
end
|
176
214
|
end
|
177
215
|
|
178
216
|
context 'update' do
|
@@ -256,5 +294,56 @@ module Mongoid
|
|
256
294
|
end
|
257
295
|
end
|
258
296
|
end
|
297
|
+
|
298
|
+
describe '.disable_audit_log' do
|
299
|
+
it 'disables recording for model' do
|
300
|
+
AuditLog.record do
|
301
|
+
product = Product.create!(name: 'Foo bar')
|
302
|
+
product.audit_log_entries.count.should eq(1)
|
303
|
+
|
304
|
+
Product.disable_audit_log
|
305
|
+
product.record_audit_log?.should be_false
|
306
|
+
|
307
|
+
product.update!(name: 'Bar foo')
|
308
|
+
product.audit_log_entries.count.should eq(1)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
describe '.enable_audit_log' do
|
314
|
+
it 'enables recording for model' do
|
315
|
+
AuditLog.record do
|
316
|
+
Product.disable_audit_log
|
317
|
+
|
318
|
+
product = Product.create!(name: 'Foo bar')
|
319
|
+
product.audit_log_entries.count.should eq(0)
|
320
|
+
|
321
|
+
Product.enable_audit_log
|
322
|
+
product.record_audit_log?.should be_true
|
323
|
+
|
324
|
+
product.update!(name: 'Bar foo')
|
325
|
+
product.audit_log_entries.count.should eq(1)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
describe '#record_audit_log?' do
|
331
|
+
it 'returns true while recording' do
|
332
|
+
Product.new.record_audit_log?.should be_false
|
333
|
+
|
334
|
+
AuditLog.record do
|
335
|
+
Product.new.record_audit_log?.should be_true
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
it 'returns false when audit log is disabled' do
|
340
|
+
Product.disable_audit_log
|
341
|
+
Product.new.record_audit_log?.should be_false
|
342
|
+
|
343
|
+
AuditLog.record do
|
344
|
+
Product.new.record_audit_log?.should be_false
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
259
348
|
end
|
260
349
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongoid-audit_log
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Crouse
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-09-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mongoid
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '7.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '7.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -68,12 +68,13 @@ files:
|
|
68
68
|
- lib/mongoid/audit_log.rb
|
69
69
|
- lib/mongoid/audit_log/changes.rb
|
70
70
|
- lib/mongoid/audit_log/config.rb
|
71
|
-
- lib/mongoid/audit_log/embedded_changes.rb
|
72
71
|
- lib/mongoid/audit_log/entry.rb
|
72
|
+
- lib/mongoid/audit_log/restore.rb
|
73
73
|
- lib/mongoid/audit_log/version.rb
|
74
74
|
- mongoid-audit_log.gemspec
|
75
75
|
- spec/mongoid/audit_log/changes_spec.rb
|
76
76
|
- spec/mongoid/audit_log/entry_spec.rb
|
77
|
+
- spec/mongoid/audit_log/restore_spec.rb
|
77
78
|
- spec/mongoid/audit_log_spec.rb
|
78
79
|
- spec/spec_helper.rb
|
79
80
|
- spec/support/models.rb
|
@@ -96,8 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
97
|
- !ruby/object:Gem::Version
|
97
98
|
version: 1.3.6
|
98
99
|
requirements: []
|
99
|
-
|
100
|
-
rubygems_version: 2.5.1
|
100
|
+
rubygems_version: 3.0.3
|
101
101
|
signing_key:
|
102
102
|
specification_version: 4
|
103
103
|
summary: No fancy versioning, undo, redo, etc. Just saves changes to Mongoid models
|
@@ -105,7 +105,7 @@ summary: No fancy versioning, undo, redo, etc. Just saves changes to Mongoid mod
|
|
105
105
|
test_files:
|
106
106
|
- spec/mongoid/audit_log/changes_spec.rb
|
107
107
|
- spec/mongoid/audit_log/entry_spec.rb
|
108
|
+
- spec/mongoid/audit_log/restore_spec.rb
|
108
109
|
- spec/mongoid/audit_log_spec.rb
|
109
110
|
- spec/spec_helper.rb
|
110
111
|
- spec/support/models.rb
|
111
|
-
has_rdoc:
|
@@ -1,33 +0,0 @@
|
|
1
|
-
module Mongoid
|
2
|
-
module AuditLog
|
3
|
-
class EmbeddedChanges
|
4
|
-
attr_reader :model
|
5
|
-
|
6
|
-
def initialize(model)
|
7
|
-
@model = model
|
8
|
-
end
|
9
|
-
|
10
|
-
def relations
|
11
|
-
model.relations.inject({}) do |memo, t|
|
12
|
-
name, relation = *t
|
13
|
-
memo[name] = relation if relation.embedded?
|
14
|
-
memo
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def all
|
19
|
-
@all ||= relations.inject({}) do |memo, t|
|
20
|
-
name = t.first
|
21
|
-
embedded = model.send(name)
|
22
|
-
changes = Mongoid::AuditLog::Changes.extract_from(embedded)
|
23
|
-
|
24
|
-
if embedded.present? && changes.present?
|
25
|
-
memo[name] = changes
|
26
|
-
end
|
27
|
-
|
28
|
-
memo
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|