mongoid-audit_log 0.2.0 → 0.5.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 +22 -0
- data/lib/mongoid/audit_log.rb +32 -9
- data/lib/mongoid/audit_log/entry.rb +73 -3
- data/lib/mongoid/audit_log/restore.rb +94 -0
- data/lib/mongoid/audit_log/version.rb +1 -1
- data/mongoid-audit_log.gemspec +3 -3
- data/spec/mongoid/audit_log/entry_spec.rb +125 -0
- data/spec/mongoid/audit_log/restore_spec.rb +81 -0
- data/spec/mongoid/audit_log_spec.rb +81 -2
- metadata +19 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ff3af4d1f95aaf1d746dbaa69f13499aeeddacd82617903a7c39cab41ccb19f1
|
4
|
+
data.tar.gz: 8aa212d93cc1797ffa0d00311f61cb529bb7a43b0fcca4398663c1ead4d538a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b1b9ccdb25f8bd5de48aa18ead6b7fc12f65fe40eea47cdd9ffe7594c86be327a860614452fe663e1613c46a634472ff74a235fae16d35b3e00dbb7ad9e9d0a7
|
7
|
+
data.tar.gz: d317c56de028253096e9e97febe256688e6b6ea0f4bea53d7aff41451fc1b66fb48d2cb0415ba9c3aa6e70fa35f6edf3f3bf11ad797adf75cd1765aa871a285f
|
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,23 @@ 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.
|
126
148
|
|
127
149
|
## Contributing
|
128
150
|
|
data/lib/mongoid/audit_log.rb
CHANGED
@@ -3,6 +3,7 @@ require "mongoid/audit_log/config"
|
|
3
3
|
require "mongoid/audit_log/entry"
|
4
4
|
require "mongoid/audit_log/changes"
|
5
5
|
require "mongoid/audit_log/embedded_changes"
|
6
|
+
require "mongoid/audit_log/restore"
|
6
7
|
|
7
8
|
module Mongoid
|
8
9
|
module AuditLog
|
@@ -24,20 +25,30 @@ module Mongoid
|
|
24
25
|
end
|
25
26
|
|
26
27
|
def self.record(modifier = nil)
|
27
|
-
|
28
|
-
|
28
|
+
already_recording = recording?
|
29
|
+
enable unless already_recording
|
30
|
+
self.current_modifier = modifier
|
29
31
|
yield
|
30
32
|
ensure
|
31
|
-
|
32
|
-
|
33
|
+
disable unless already_recording
|
34
|
+
self.current_modifier = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.enable
|
38
|
+
Thread.current[:mongoid_audit_log_recording] = true
|
33
39
|
end
|
34
40
|
|
35
41
|
def self.disable
|
36
|
-
|
42
|
+
already_recording = recording?
|
37
43
|
Thread.current[:mongoid_audit_log_recording] = false
|
38
|
-
|
39
|
-
|
40
|
-
|
44
|
+
|
45
|
+
if block_given?
|
46
|
+
begin
|
47
|
+
yield
|
48
|
+
ensure
|
49
|
+
Thread.current[:mongoid_audit_log_recording] = already_recording
|
50
|
+
end
|
51
|
+
end
|
41
52
|
end
|
42
53
|
|
43
54
|
def self.recording?
|
@@ -48,6 +59,10 @@ module Mongoid
|
|
48
59
|
Thread.current[:mongoid_audit_log_modifier]
|
49
60
|
end
|
50
61
|
|
62
|
+
def self.current_modifier=(modifier)
|
63
|
+
Thread.current[:mongoid_audit_log_modifier] = modifier
|
64
|
+
end
|
65
|
+
|
51
66
|
private
|
52
67
|
|
53
68
|
def set_audit_log_changes
|
@@ -61,9 +76,17 @@ module Mongoid
|
|
61
76
|
:audited_type => self.class,
|
62
77
|
:audited_id => id,
|
63
78
|
:tracked_changes => @_audit_log_changes.all,
|
64
|
-
:model_attributes => attributes.
|
79
|
+
:model_attributes => attributes.deep_dup,
|
80
|
+
:document_path => traverse_association_chain
|
65
81
|
)
|
66
82
|
end
|
67
83
|
end
|
84
|
+
|
85
|
+
def traverse_association_chain(node = self, current_relation = nil)
|
86
|
+
relation = node.embedded? ? node.metadata_name.to_s : nil
|
87
|
+
list = node._parent ? traverse_association_chain(node._parent, relation) : []
|
88
|
+
list << { class_name: node.class.name, id: node.id, relation: current_relation }
|
89
|
+
list
|
90
|
+
end
|
68
91
|
end
|
69
92
|
end
|
@@ -8,8 +8,14 @@ module Mongoid
|
|
8
8
|
field :tracked_changes, :type => Hash, :default => {}
|
9
9
|
field :modifier_id, :type => String
|
10
10
|
field :model_attributes, :type => Hash
|
11
|
+
field :document_path, :type => Array
|
12
|
+
field :restored, :type => Boolean, default: false
|
11
13
|
|
12
|
-
|
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
|
13
19
|
|
14
20
|
index({ :audited_id => 1, :audited_type => 1 })
|
15
21
|
index({ :modifier_id => 1 })
|
@@ -27,7 +33,9 @@ module Mongoid
|
|
27
33
|
|
28
34
|
def valid?(*)
|
29
35
|
result = super
|
30
|
-
|
36
|
+
if result && modifier.blank?
|
37
|
+
self.modifier = Mongoid::AuditLog.current_modifier
|
38
|
+
end
|
31
39
|
result
|
32
40
|
end
|
33
41
|
|
@@ -36,7 +44,7 @@ module Mongoid
|
|
36
44
|
nil
|
37
45
|
else
|
38
46
|
klass = Mongoid::AuditLog.modifier_class_name.constantize
|
39
|
-
klass.find(modifier_id)
|
47
|
+
klass.find(modifier_id) rescue nil
|
40
48
|
end
|
41
49
|
end
|
42
50
|
|
@@ -50,6 +58,42 @@ module Mongoid
|
|
50
58
|
@modifier = modifier
|
51
59
|
end
|
52
60
|
|
61
|
+
def for_embedded_doc?
|
62
|
+
document_path.try(:length).to_i > 1
|
63
|
+
end
|
64
|
+
|
65
|
+
def audited
|
66
|
+
return nil if audited_type.blank? || audited_id.blank?
|
67
|
+
|
68
|
+
if for_embedded_doc?
|
69
|
+
lookup_from_document_path
|
70
|
+
else
|
71
|
+
audited_type.constantize.where(id: audited_id).first
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def root
|
76
|
+
root = document_path.first
|
77
|
+
return audited if root.blank?
|
78
|
+
|
79
|
+
if for_embedded_doc?
|
80
|
+
root['class_name'].constantize.where(id: root['id']).first
|
81
|
+
else
|
82
|
+
audited
|
83
|
+
end
|
84
|
+
end
|
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
|
+
|
53
97
|
def respond_to?(sym, *args)
|
54
98
|
key = sym.to_s
|
55
99
|
(model_attributes.present? && model_attributes.has_key?(key)) || super
|
@@ -64,6 +108,32 @@ module Mongoid
|
|
64
108
|
super
|
65
109
|
end
|
66
110
|
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def lookup_from_document_path
|
115
|
+
return nil if document_path.blank?
|
116
|
+
|
117
|
+
document_path.reduce(root) do |current, path|
|
118
|
+
relation_match = if document_path_matches?(path, current)
|
119
|
+
current
|
120
|
+
elsif current.respond_to?(:detect)
|
121
|
+
current.detect do |model|
|
122
|
+
document_path_matches?(path, model)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
if path['relation'].blank? || relation_match.blank?
|
127
|
+
return relation_match
|
128
|
+
else
|
129
|
+
relation_match.send(path['relation'])
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def document_path_matches?(path, object)
|
135
|
+
object.class.name == path['class_name'] && object.id == path['id']
|
136
|
+
end
|
67
137
|
end
|
68
138
|
end
|
69
139
|
end
|
@@ -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.many?
|
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
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ["bencrouse@gmail.com"]
|
11
11
|
spec.description = %q{Stupid simple audit logging for Mongoid}
|
12
12
|
spec.summary = %q{No fancy versioning, undo, redo, etc. Just saves changes to Mongoid models in a separate collection.}
|
13
|
-
spec.homepage = "https://github.com/bencrouse/mongoid-
|
13
|
+
spec.homepage = "https://github.com/bencrouse/mongoid-audit_log"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files`.split($/)
|
@@ -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", [">= 6.0.x", "< 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
|
@@ -10,12 +10,30 @@ module Mongoid
|
|
10
10
|
class ::Product
|
11
11
|
include Mongoid::Document
|
12
12
|
include Mongoid::AuditLog
|
13
|
+
field :name, :type => String
|
14
|
+
embeds_many :variants
|
15
|
+
end
|
16
|
+
|
17
|
+
class ::Variant
|
18
|
+
include Mongoid::Document
|
19
|
+
include Mongoid::AuditLog
|
20
|
+
field :sku, :type => String
|
21
|
+
embedded_in :product
|
22
|
+
embeds_many :options
|
23
|
+
end
|
24
|
+
|
25
|
+
class ::Option
|
26
|
+
include Mongoid::Document
|
27
|
+
include Mongoid::AuditLog
|
28
|
+
field :name, :type => String
|
29
|
+
embedded_in :variant
|
13
30
|
end
|
14
31
|
end
|
15
32
|
|
16
33
|
after(:all) do
|
17
34
|
AuditLog.modifier_class_name = @remember_modifier_class_name
|
18
35
|
Object.send(:remove_const, :Product)
|
36
|
+
Object.send(:remove_const, :Variant)
|
19
37
|
end
|
20
38
|
|
21
39
|
let(:user) { User.create! }
|
@@ -50,11 +68,39 @@ module Mongoid
|
|
50
68
|
end
|
51
69
|
end
|
52
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
|
+
|
53
79
|
describe '#modifier' do
|
54
80
|
it 'finds the modifier based on the configured class' do
|
55
81
|
entry = Entry.new(:modifier_id => user.id)
|
56
82
|
entry.modifier.should == user
|
57
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
|
89
|
+
end
|
90
|
+
|
91
|
+
describe '#audited' do
|
92
|
+
it 'uses the document path to find embedded documents' do
|
93
|
+
product = Product.create!(:name => 'Foo bar')
|
94
|
+
variant = product.variants.create!
|
95
|
+
option = AuditLog.record { variant.options.create! }
|
96
|
+
|
97
|
+
entry = Entry.desc(:created_at).first
|
98
|
+
entry.audited.should == option
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'returns nil if the audited info is blank' do
|
102
|
+
Entry.new.audited.should be_nil
|
103
|
+
end
|
58
104
|
end
|
59
105
|
|
60
106
|
describe '#modifier_id=' do
|
@@ -92,6 +138,85 @@ module Mongoid
|
|
92
138
|
entry.other.should == nil
|
93
139
|
end
|
94
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
|
95
220
|
end
|
96
221
|
end
|
97
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
|
@@ -12,9 +12,12 @@ module Mongoid
|
|
12
12
|
|
13
13
|
class ::Variant
|
14
14
|
include Mongoid::Document
|
15
|
+
include Mongoid::AuditLog
|
15
16
|
field :sku, :type => String
|
16
17
|
embedded_in :product
|
17
18
|
end
|
19
|
+
|
20
|
+
AuditLog.disable
|
18
21
|
end
|
19
22
|
|
20
23
|
after(:all) do
|
@@ -94,8 +97,31 @@ module Mongoid
|
|
94
97
|
end
|
95
98
|
end
|
96
99
|
|
100
|
+
describe '.enable' do
|
101
|
+
after(:each) do
|
102
|
+
AuditLog.disable
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'starts recording' do
|
106
|
+
AuditLog.enable
|
107
|
+
|
108
|
+
product = Product.create!(:name => 'Foo bar')
|
109
|
+
product.audit_log_entries.count.should == 1
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
97
113
|
describe '.disable' do
|
98
|
-
it '
|
114
|
+
it 'stops recording' do
|
115
|
+
AuditLog.enable
|
116
|
+
AuditLog.disable
|
117
|
+
|
118
|
+
product = Product.create!(:name => 'Foo bar')
|
119
|
+
product.audit_log_entries.should be_empty
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'can disable recording for a block' do
|
123
|
+
AuditLog.disable
|
124
|
+
|
99
125
|
AuditLog.record do
|
100
126
|
product = Product.create!(:name => 'Foo bar')
|
101
127
|
|
@@ -124,6 +150,7 @@ module Mongoid
|
|
124
150
|
entry = product.audit_log_entries.first
|
125
151
|
|
126
152
|
entry.create?.should be_true
|
153
|
+
entry.root.should == product
|
127
154
|
|
128
155
|
entry.tracked_changes.should == {
|
129
156
|
'name' => [nil, 'Foo bar']
|
@@ -141,13 +168,14 @@ module Mongoid
|
|
141
168
|
product.save!
|
142
169
|
|
143
170
|
entry = product.audit_log_entries.first
|
171
|
+
entry.root.should == product
|
144
172
|
|
145
173
|
entry.create?.should be_true
|
146
174
|
|
147
175
|
entry.model_attributes.should == {
|
148
176
|
'_id' => product.id,
|
149
177
|
'name' => 'Foo bar',
|
150
|
-
'variants' => [{ '_id' => product.variants.first.id, 'sku'=>'sku' }]
|
178
|
+
'variants' => [{ '_id' => product.variants.first.id, 'sku' => 'sku' }]
|
151
179
|
}
|
152
180
|
|
153
181
|
entry.tracked_changes.should == {
|
@@ -155,6 +183,21 @@ module Mongoid
|
|
155
183
|
'variants' => [{ 'sku' => [nil, 'sku'] }]
|
156
184
|
}
|
157
185
|
end
|
186
|
+
|
187
|
+
it 'tracks parents on embedded creations' do
|
188
|
+
product = Product.create!(:name => 'Foo bar')
|
189
|
+
variant = product.variants.create!(sku: 'sku')
|
190
|
+
|
191
|
+
entry = Mongoid::AuditLog::Entry.desc(:created_at).first
|
192
|
+
entry.root.should == product
|
193
|
+
entry.document_path.length.should == 2
|
194
|
+
entry.document_path.first['class_name'].should == product.class.name
|
195
|
+
entry.document_path.first['id'].should == product.id
|
196
|
+
entry.document_path.first['relation'].should == 'variants'
|
197
|
+
entry.document_path.second['class_name'].should == variant.class.name
|
198
|
+
entry.document_path.second['id'].should == variant.id
|
199
|
+
entry.document_path.second['relation'].should == nil
|
200
|
+
end
|
158
201
|
end
|
159
202
|
|
160
203
|
context 'update' do
|
@@ -164,6 +207,7 @@ module Mongoid
|
|
164
207
|
entry = product.audit_log_entries.desc(:created_at).first
|
165
208
|
|
166
209
|
entry.update?.should be_true
|
210
|
+
entry.root.should == product
|
167
211
|
entry.tracked_changes.should == { 'name' => ['Foo bar', 'Bar baz'] }
|
168
212
|
end
|
169
213
|
|
@@ -179,6 +223,7 @@ module Mongoid
|
|
179
223
|
entry = product.audit_log_entries.desc(:created_at).first
|
180
224
|
|
181
225
|
entry.update?.should be_true
|
226
|
+
entry.root.should == product
|
182
227
|
entry.tracked_changes.should == {
|
183
228
|
'name' => ['Foo bar', 'Bar baz'],
|
184
229
|
'variants' => [{ 'sku' => ['sku', 'newsku'] }]
|
@@ -190,6 +235,23 @@ module Mongoid
|
|
190
235
|
product.update_attributes(:name => 'Foo bar')
|
191
236
|
product.audit_log_entries.length.should == 1
|
192
237
|
end
|
238
|
+
|
239
|
+
it 'tracks parents on embedded updates' do
|
240
|
+
product = Product.create!(:name => 'Foo bar')
|
241
|
+
variant = product.variants.create!(sku: 'sku')
|
242
|
+
variant.sku = 'newsku'
|
243
|
+
variant.save!
|
244
|
+
|
245
|
+
entry = Mongoid::AuditLog::Entry.desc(:created_at).first
|
246
|
+
entry.root.should == product
|
247
|
+
entry.document_path.length.should == 2
|
248
|
+
entry.document_path.first['class_name'].should == product.class.name
|
249
|
+
entry.document_path.first['id'].should == product.id
|
250
|
+
entry.document_path.first['relation'].should == 'variants'
|
251
|
+
entry.document_path.second['class_name'].should == variant.class.name
|
252
|
+
entry.document_path.second['id'].should == variant.id
|
253
|
+
entry.document_path.second['relation'].should == nil
|
254
|
+
end
|
193
255
|
end
|
194
256
|
|
195
257
|
context 'destroy' do
|
@@ -199,6 +261,23 @@ module Mongoid
|
|
199
261
|
entry = product.audit_log_entries.desc(:created_at).first
|
200
262
|
|
201
263
|
entry.destroy?.should be_true
|
264
|
+
entry.root.should == nil
|
265
|
+
end
|
266
|
+
|
267
|
+
it 'tracks parents on embedded destroys' do
|
268
|
+
product = Product.create!(:name => 'Foo bar')
|
269
|
+
variant = product.variants.create!(sku: 'sku')
|
270
|
+
variant.destroy!
|
271
|
+
|
272
|
+
entry = Mongoid::AuditLog::Entry.desc(:created_at).first
|
273
|
+
entry.root.should == product
|
274
|
+
entry.document_path.length.should == 2
|
275
|
+
entry.document_path.first['class_name'].should == product.class.name
|
276
|
+
entry.document_path.first['id'].should == product.id
|
277
|
+
entry.document_path.first['relation'].should == 'variants'
|
278
|
+
entry.document_path.second['class_name'].should == variant.class.name
|
279
|
+
entry.document_path.second['id'].should == variant.id
|
280
|
+
entry.document_path.second['relation'].should == nil
|
202
281
|
end
|
203
282
|
end
|
204
283
|
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.5.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-10-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mongoid
|
@@ -16,28 +16,34 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 6.0.x
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '7.0'
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
27
|
- - ">="
|
25
28
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
29
|
+
version: 6.0.x
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '7.0'
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: bundler
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
30
36
|
requirements:
|
31
|
-
- - "
|
37
|
+
- - ">="
|
32
38
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
39
|
+
version: '0'
|
34
40
|
type: :development
|
35
41
|
prerelease: false
|
36
42
|
version_requirements: !ruby/object:Gem::Requirement
|
37
43
|
requirements:
|
38
|
-
- - "
|
44
|
+
- - ">="
|
39
45
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
46
|
+
version: '0'
|
41
47
|
- !ruby/object:Gem::Dependency
|
42
48
|
name: rake
|
43
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -70,14 +76,16 @@ files:
|
|
70
76
|
- lib/mongoid/audit_log/config.rb
|
71
77
|
- lib/mongoid/audit_log/embedded_changes.rb
|
72
78
|
- lib/mongoid/audit_log/entry.rb
|
79
|
+
- lib/mongoid/audit_log/restore.rb
|
73
80
|
- lib/mongoid/audit_log/version.rb
|
74
81
|
- mongoid-audit_log.gemspec
|
75
82
|
- spec/mongoid/audit_log/changes_spec.rb
|
76
83
|
- spec/mongoid/audit_log/entry_spec.rb
|
84
|
+
- spec/mongoid/audit_log/restore_spec.rb
|
77
85
|
- spec/mongoid/audit_log_spec.rb
|
78
86
|
- spec/spec_helper.rb
|
79
87
|
- spec/support/models.rb
|
80
|
-
homepage: https://github.com/bencrouse/mongoid-
|
88
|
+
homepage: https://github.com/bencrouse/mongoid-audit_log
|
81
89
|
licenses:
|
82
90
|
- MIT
|
83
91
|
metadata: {}
|
@@ -96,8 +104,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
104
|
- !ruby/object:Gem::Version
|
97
105
|
version: 1.3.6
|
98
106
|
requirements: []
|
99
|
-
|
100
|
-
rubygems_version: 2.4.5
|
107
|
+
rubygems_version: 3.0.3
|
101
108
|
signing_key:
|
102
109
|
specification_version: 4
|
103
110
|
summary: No fancy versioning, undo, redo, etc. Just saves changes to Mongoid models
|
@@ -105,6 +112,7 @@ summary: No fancy versioning, undo, redo, etc. Just saves changes to Mongoid mod
|
|
105
112
|
test_files:
|
106
113
|
- spec/mongoid/audit_log/changes_spec.rb
|
107
114
|
- spec/mongoid/audit_log/entry_spec.rb
|
115
|
+
- spec/mongoid/audit_log/restore_spec.rb
|
108
116
|
- spec/mongoid/audit_log_spec.rb
|
109
117
|
- spec/spec_helper.rb
|
110
118
|
- spec/support/models.rb
|