mongoid-audit_log 0.0.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 +15 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +114 -0
- data/Rakefile +1 -0
- data/lib/mongoid/audit_log.rb +60 -0
- data/lib/mongoid/audit_log/actions.rb +6 -0
- data/lib/mongoid/audit_log/changes.rb +68 -0
- data/lib/mongoid/audit_log/embedded_changes.rb +33 -0
- data/lib/mongoid/audit_log/entry.rb +53 -0
- data/lib/mongoid/audit_log/version.rb +5 -0
- data/mongoid-audit_log.gemspec +28 -0
- data/spec/mongoid/audit_log/changes_spec.rb +98 -0
- data/spec/mongoid/audit_log/entry_spec.rb +75 -0
- data/spec/mongoid/audit_log_spec.rb +136 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/models.rb +3 -0
- metadata +111 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
OWYyMWJjOWFkN2ExYzA0ZWNhODkzZjQ2MTIxZDcxODNmMjNkN2QwOQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MjJkOTRiNzcwNTI1NTg3NzI2ZDJjNDFlNjA5ZGQwMTM0OTI1YjdlMg==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NzY2ODJiMmQ3YmQ2MThlYjhiOWJmMWFkN2JkOGVhYjRjYjQ2MmRjMjkzY2E1
|
10
|
+
MjM3Y2Q2MGVlZjQzZmQ5MDg3OGUyZTY3MDA0OTA0OWIxNmFkNTA4ZTY3OWQw
|
11
|
+
MTM5Yzk3YmVjMDYwNDBhMDg3NTc0MzJjODE0NjcyZWM3MWQ2ZTA=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
Nzk0MDY1MGZmYzdmZjIzZjI5ZjQ4ZDJjNjIxNTM2YjBiMGVmMTk1YTliYzY1
|
14
|
+
NmY0ZmJmNTk5NjM3NmYyZDNmNTQxOTQ2NjYzYTJlNmZmZjBlOTc4YTcxMzky
|
15
|
+
YzY2ODQxODgyNDI5MGRjM2M5MTk0N2RmZjQ5Mjc4OTkxMWI0ZTY=
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 bcrouse
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
# Mongoid::AuditLog
|
2
|
+
|
3
|
+
Frustrated with the other options for this, I wrote this gem to handle most basic audit logging for Mongoid. It is intended to be stupidly simple, and offers no fancy functionality.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'mongoid-audit_log'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install mongoid-audit_log
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
### Recording Activity
|
22
|
+
|
23
|
+
Include the `Mongoid::AuditLog` module into your model.
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
class Model
|
27
|
+
include Mongoid::Document
|
28
|
+
include Mongoid::AuditLog
|
29
|
+
field :name
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
This will not enable logging by itself, only changes made within the block passed to the record method will be saved.
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
Mongoid::AuditLog.record do
|
37
|
+
Model.create!
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
If you want to log the user who made the change, pass that user to the record method:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
Mongoid::AuditLog.record(current_user) do
|
45
|
+
Model.create!
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
A basic implementation in a Rails app might look something like:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
class ApplicationController < ActionController::Base
|
53
|
+
around_filter :audit_log
|
54
|
+
|
55
|
+
def current_user
|
56
|
+
@current_user ||= User.find(session[:user_id])
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def audit_log
|
62
|
+
Mongoid::AuditLog.record(current_user) do
|
63
|
+
yield
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
### Viewing Activity
|
70
|
+
|
71
|
+
When an audited model is changed, it will create a record of the `Mongoid::AuditLog::Entry` class.
|
72
|
+
Each class responds to some query methods:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
Mongoid::AuditLog.record do
|
76
|
+
model = Model.create!
|
77
|
+
module.update_attributes(:name => 'model')
|
78
|
+
end
|
79
|
+
|
80
|
+
model.audit_log_entries.length == 2
|
81
|
+
|
82
|
+
model.audit_log_entries.first.create? # => true
|
83
|
+
model.audit_log_entries.first.update? # => false
|
84
|
+
model.audit_log_entries.first.destroy? # => false
|
85
|
+
|
86
|
+
model.audit_log_entries.second.create? # => false
|
87
|
+
model.audit_log_entries.second.update? # => true
|
88
|
+
model.audit_log_entries.second.destroy? # => false
|
89
|
+
|
90
|
+
# And on update you have the tracked changes
|
91
|
+
model.audit_log_entries.second.tracked_changes.should == { 'name' => [nil, 'model'] }
|
92
|
+
```
|
93
|
+
|
94
|
+
There are also some built-in scopes (from the tests):
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
create = Entry.create!(:action => :create, :created_at => 10.minutes.ago) }
|
98
|
+
update = Entry.create!(:action => :update, :created_at => 5.minutes.ago) }
|
99
|
+
destroy = Entry.create!(:action => :destroy, :created_at => 1.minutes.ago) }
|
100
|
+
|
101
|
+
Entry.creates.to_a.should == [create]
|
102
|
+
Entry.updates.to_a.should == [update]
|
103
|
+
Entry.destroys.to_a.should == [destroy]
|
104
|
+
Entry.newest.to_a.should == [destroy, update, create]
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
## Contributing
|
109
|
+
|
110
|
+
1. Fork it
|
111
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
112
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
113
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
114
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require "mongoid/audit_log/version"
|
2
|
+
require "mongoid/audit_log/actions"
|
3
|
+
require "mongoid/audit_log/entry"
|
4
|
+
require "mongoid/audit_log/changes"
|
5
|
+
require "mongoid/audit_log/embedded_changes"
|
6
|
+
|
7
|
+
module Mongoid
|
8
|
+
module AuditLog
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
mattr_accessor :modifier_class_name
|
12
|
+
self.modifier_class_name = 'User'
|
13
|
+
|
14
|
+
included do
|
15
|
+
has_many :audit_log_entries, :as => :audited,
|
16
|
+
:class_name => 'Mongoid::AuditLog::Entry'
|
17
|
+
|
18
|
+
Mongoid::AuditLog.actions.each do |action|
|
19
|
+
send("before_#{action}") do
|
20
|
+
set_audit_log_changes if Mongoid::AuditLog.recording?
|
21
|
+
end
|
22
|
+
|
23
|
+
send("after_#{action}") do
|
24
|
+
save_audit_log_entry(action) if Mongoid::AuditLog.recording?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.record(modifier = nil)
|
30
|
+
Thread.current[:mongoid_audit_log_recording] = true
|
31
|
+
Thread.current[:mongoid_audit_log_modifier] = modifier
|
32
|
+
yield
|
33
|
+
Thread.current[:mongoid_audit_log_recording] = nil
|
34
|
+
Thread.current[:mongoid_audit_log_modifier] = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.recording?
|
38
|
+
!!Thread.current[:mongoid_audit_log_recording]
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.current_modifier
|
42
|
+
Thread.current[:mongoid_audit_log_modifier]
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def set_audit_log_changes
|
48
|
+
@_audit_log_changes = Mongoid::AuditLog::Changes.new(self).tap(&:read)
|
49
|
+
end
|
50
|
+
|
51
|
+
def save_audit_log_entry(action)
|
52
|
+
Mongoid::AuditLog::Entry.create!(
|
53
|
+
:action => action,
|
54
|
+
:audited_type => self.class,
|
55
|
+
:audited_id => id,
|
56
|
+
:tracked_changes => @_audit_log_changes.all
|
57
|
+
)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module AuditLog
|
3
|
+
class Changes
|
4
|
+
attr_reader :model
|
5
|
+
delegate :blank?, :present?, :to => :all
|
6
|
+
|
7
|
+
def self.ch_ch_ch_ch_ch_changes
|
8
|
+
puts "turn and face the strange changes"
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.extract_from(value)
|
12
|
+
if value.is_a?(Hash)
|
13
|
+
raise ArgumentError, 'does not support hashes'
|
14
|
+
elsif value.is_a?(Enumerable)
|
15
|
+
changes = value.map do |model|
|
16
|
+
Mongoid::AuditLog::Changes.new(model).all
|
17
|
+
end
|
18
|
+
|
19
|
+
changes.reject(&:blank?)
|
20
|
+
else
|
21
|
+
Mongoid::AuditLog::Changes.new(value).all
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.clean_fields(*disallowed_fields)
|
26
|
+
options = disallowed_fields.extract_options!
|
27
|
+
|
28
|
+
unless options.has_key?(:from)
|
29
|
+
raise ArgumentError, ':from is a required argument'
|
30
|
+
end
|
31
|
+
|
32
|
+
changes = options[:from]
|
33
|
+
|
34
|
+
if changes.is_a?(Hash)
|
35
|
+
changes.except(*disallowed_fields).inject({}) do |memo, t|
|
36
|
+
key, value = *t
|
37
|
+
memo.merge!(key => clean_fields(*disallowed_fields, :from => value))
|
38
|
+
end
|
39
|
+
elsif changes.is_a?(Enumerable)
|
40
|
+
changes.map { |c| clean_fields(*disallowed_fields, :from => c) }
|
41
|
+
else
|
42
|
+
changes
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(model)
|
47
|
+
@model = model
|
48
|
+
end
|
49
|
+
|
50
|
+
def all
|
51
|
+
@all ||= if !model.changed?
|
52
|
+
{}
|
53
|
+
else
|
54
|
+
result = model.changes
|
55
|
+
result.merge!(embedded_changes) unless embedded_changes.empty?
|
56
|
+
Mongoid::AuditLog::Changes.clean_fields('_id', 'updated_at', :from => result)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
alias_method :read, :all
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def embedded_changes
|
64
|
+
@embedded_changes ||= Mongoid::AuditLog::EmbeddedChanges.new(model).all
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,33 @@
|
|
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.macro.in?(:embeds_one, :embeds_many)
|
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
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module AuditLog
|
3
|
+
class Entry
|
4
|
+
include Mongoid::Document
|
5
|
+
include Mongoid::Timestamps::Created
|
6
|
+
|
7
|
+
field :action, :type => Symbol
|
8
|
+
field :tracked_changes, :type => Hash, :default => {}
|
9
|
+
field :modifier_id, :type => String
|
10
|
+
|
11
|
+
belongs_to :audited, :polymorphic => true
|
12
|
+
|
13
|
+
index({ :audited_id => 1, :audited_type => 1 })
|
14
|
+
index({ :modifier_id => 1 })
|
15
|
+
|
16
|
+
scope :creates, where(:action => :create)
|
17
|
+
scope :updates, where(:action => :update)
|
18
|
+
scope :destroys, where(:action => :destroy)
|
19
|
+
scope :newest, order_by(:created_at.desc)
|
20
|
+
|
21
|
+
Mongoid::AuditLog.actions.each do |action_name|
|
22
|
+
define_method "#{action_name}?" do
|
23
|
+
action == action_name
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def valid?(*)
|
28
|
+
result = super
|
29
|
+
self.modifier = Mongoid::AuditLog.current_modifier if result
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
33
|
+
def modifier
|
34
|
+
@modifier ||= if modifier_id.blank?
|
35
|
+
nil
|
36
|
+
else
|
37
|
+
klass = Mongoid::AuditLog.modifier_class_name.constantize
|
38
|
+
klass.find(modifier_id)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def modifier=(modifier)
|
43
|
+
self.modifier_id = if modifier.present? && modifier.respond_to?(:id)
|
44
|
+
modifier.id
|
45
|
+
else
|
46
|
+
modifier
|
47
|
+
end
|
48
|
+
|
49
|
+
@modifier = modifier
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'mongoid/audit_log/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "mongoid-audit_log"
|
8
|
+
spec.version = Mongoid::AuditLog::VERSION
|
9
|
+
spec.authors = ["Ben Crouse"]
|
10
|
+
spec.email = ["bencrouse@gmail.com"]
|
11
|
+
spec.description = %q{Stupid simple audit logging for Mongoid}
|
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-audit-log"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.required_ruby_version = ">= 1.9.2"
|
22
|
+
spec.required_rubygems_version = ">= 1.3.6"
|
23
|
+
|
24
|
+
spec.add_runtime_dependency "mongoid", ["~> 3.1"]
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
27
|
+
spec.add_development_dependency "rake"
|
28
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module AuditLog
|
5
|
+
describe Changes do
|
6
|
+
before(:all) do
|
7
|
+
class ::Product
|
8
|
+
include Mongoid::Document
|
9
|
+
include Mongoid::AuditLog
|
10
|
+
include Mongoid::Timestamps
|
11
|
+
field :name, :type => String
|
12
|
+
embeds_many :variants
|
13
|
+
end
|
14
|
+
|
15
|
+
class ::Variant
|
16
|
+
include Mongoid::Document
|
17
|
+
field :sku, :type => String
|
18
|
+
embedded_in :product
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
after(:all) do
|
23
|
+
Object.send(:remove_const, :Product)
|
24
|
+
Object.send(:remove_const, :Variant)
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '.extract_from' do
|
28
|
+
context 'enumerable' do
|
29
|
+
it 'returns mapped changes' do
|
30
|
+
models = Array.new(3) { Product.new(name: rand) }
|
31
|
+
results = Changes.extract_from(models)
|
32
|
+
|
33
|
+
results.length.should == 3
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '.clean_fields' do
|
39
|
+
it 'removes the fields from change hashes' do
|
40
|
+
changes = { "_id" => [nil, "52509662ace6ecd79a000009"], "name" => [nil, "Foo bar"] }
|
41
|
+
results = Changes.clean_fields('_id', from: changes)
|
42
|
+
results.should == { "name" => [nil, "Foo bar"] }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#all' do
|
47
|
+
let(:product) do
|
48
|
+
product = Product.new(:name => 'Foo bar')
|
49
|
+
product.variants.build(sku: 'sku1')
|
50
|
+
product.variants.build(sku: 'sku2')
|
51
|
+
product
|
52
|
+
end
|
53
|
+
|
54
|
+
let(:changes) do
|
55
|
+
Changes.new(product).all
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'has model changes' do
|
59
|
+
changes['name'].should == [nil, 'Foo bar']
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'has embedded model changes' do
|
63
|
+
changes['variants'].should == [
|
64
|
+
{ "sku" => [nil, "sku1"] },
|
65
|
+
{ "sku" => [nil, "sku2"] }
|
66
|
+
]
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'is blank if only an ignored field is changed' do
|
70
|
+
product.save!
|
71
|
+
product.updated_at = 1.hour.from_now
|
72
|
+
|
73
|
+
changes.should_not be_present
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'has changes if only the embedded model changes' do
|
77
|
+
product.save!
|
78
|
+
product.variants.first.sku = 'newsku'
|
79
|
+
|
80
|
+
changes.should == {
|
81
|
+
"variants"=> [{ "sku" =>[ "sku1", "newsku"] }]
|
82
|
+
}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe '#present?' do
|
87
|
+
it 'is false if no embedded models have changed' do
|
88
|
+
product = Product.new(:name => 'Foo bar')
|
89
|
+
product.variants.build(sku: 'sku1')
|
90
|
+
product.variants.build(sku: 'sku2')
|
91
|
+
product.save!
|
92
|
+
|
93
|
+
Changes.new(product).should_not be_present
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module AuditLog
|
5
|
+
describe Entry do
|
6
|
+
before(:all) do
|
7
|
+
@remember_modifier_class_name = AuditLog.modifier_class_name
|
8
|
+
AuditLog.modifier_class_name = 'User'
|
9
|
+
|
10
|
+
class ::Product
|
11
|
+
include Mongoid::Document
|
12
|
+
include Mongoid::AuditLog
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
after(:all) do
|
17
|
+
AuditLog.modifier_class_name = @remember_modifier_class_name
|
18
|
+
Object.send(:remove_const, :Product)
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:user) { User.create! }
|
22
|
+
|
23
|
+
describe 'scopes' do
|
24
|
+
let!(:create) { Entry.create!(:action => :create, :created_at => 10.minutes.ago) }
|
25
|
+
let!(:update) { Entry.create!(:action => :update, :created_at => 5.minutes.ago) }
|
26
|
+
let!(:destroy) { Entry.create!(:action => :destroy, :created_at => 1.minutes.ago) }
|
27
|
+
|
28
|
+
describe '.creates' do
|
29
|
+
it 'returns actions which are creates' do
|
30
|
+
Entry.creates.to_a.should == [create]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '.updates' do
|
35
|
+
it 'returns actions which are updates' do
|
36
|
+
Entry.updates.to_a.should == [update]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '.destroys' do
|
41
|
+
it 'returns actions which are destroys' do
|
42
|
+
Entry.destroys.to_a.should == [destroy]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '.newest' do
|
47
|
+
it 'sorts with newest first' do
|
48
|
+
Entry.newest.to_a.should == [destroy, update, create]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#modifier' do
|
54
|
+
it 'finds the modifier based on the configured class' do
|
55
|
+
entry = Entry.new(:modifier_id => user.id)
|
56
|
+
entry.modifier.should == user
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#modifier_id=' do
|
61
|
+
let(:entry) { Entry.new }
|
62
|
+
|
63
|
+
it "sets the modifier's id" do
|
64
|
+
entry.modifier = user
|
65
|
+
entry.modifier_id.should == user.id.to_s
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'sets the modifier if no id' do
|
69
|
+
entry.modifier = 'test'
|
70
|
+
entry.modifier_id.should == 'test'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
describe AuditLog do
|
5
|
+
before(:all) do
|
6
|
+
class ::Product
|
7
|
+
include Mongoid::Document
|
8
|
+
include Mongoid::AuditLog
|
9
|
+
field :name, :type => String
|
10
|
+
embeds_many :variants
|
11
|
+
end
|
12
|
+
|
13
|
+
class ::Variant
|
14
|
+
include Mongoid::Document
|
15
|
+
field :sku, :type => String
|
16
|
+
embedded_in :product
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
after(:all) do
|
21
|
+
Object.send(:remove_const, :Product)
|
22
|
+
Object.send(:remove_const, :Variant)
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '.record' do
|
26
|
+
it 'does not save an entry if not recording' do
|
27
|
+
product = Product.create!
|
28
|
+
product.audit_log_entries.should be_empty
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'saves create entries' do
|
32
|
+
AuditLog.record do
|
33
|
+
product = Product.create!
|
34
|
+
product.audit_log_entries.count.should == 1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'saves update entries' do
|
39
|
+
AuditLog.record do
|
40
|
+
product = Product.create!(:name => 'Foo bar')
|
41
|
+
2.times { |i| product.update_attributes(:name => "Test #{i}") }
|
42
|
+
product.audit_log_entries.count.should == 3
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'saves destroy entries' do
|
47
|
+
AuditLog.record do
|
48
|
+
product = Product.create!
|
49
|
+
product.destroy
|
50
|
+
AuditLog::Entry.count.should == 2
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'saves the modifier if passed' do
|
55
|
+
user = User.create!
|
56
|
+
AuditLog.record(user) do
|
57
|
+
product = Product.create!
|
58
|
+
product.audit_log_entries.first.modifier.should == user
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe 'callbacks' do
|
64
|
+
let(:user) { User.create! }
|
65
|
+
|
66
|
+
around(:each) do |example|
|
67
|
+
AuditLog.record(user) do
|
68
|
+
example.run
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'create' do
|
73
|
+
it 'saves details' do
|
74
|
+
product = Product.create!(:name => 'Foo bar')
|
75
|
+
entry = product.audit_log_entries.first
|
76
|
+
|
77
|
+
entry.create?.should be_true
|
78
|
+
entry.tracked_changes.should == { 'name' => [nil, 'Foo bar'] }
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'saves embedded creations' do
|
82
|
+
product = Product.new(:name => 'Foo bar')
|
83
|
+
product.variants.build(sku: 'sku')
|
84
|
+
product.save!
|
85
|
+
|
86
|
+
entry = product.audit_log_entries.first
|
87
|
+
|
88
|
+
entry.create?.should be_true
|
89
|
+
entry.tracked_changes.should == {
|
90
|
+
'name' => [nil, 'Foo bar'],
|
91
|
+
'variants' => [{ 'sku' => [nil, 'sku'] }]
|
92
|
+
}
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'update' do
|
97
|
+
it 'saves details' do
|
98
|
+
product = Product.create!(:name => 'Foo bar')
|
99
|
+
product.update_attributes(:name => 'Bar baz')
|
100
|
+
entry = product.audit_log_entries.last
|
101
|
+
|
102
|
+
entry.update?.should be_true
|
103
|
+
entry.tracked_changes.should == { 'name' => ['Foo bar', 'Bar baz'] }
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'saves embedded updates' do
|
107
|
+
product = Product.new(:name => 'Foo bar')
|
108
|
+
product.variants.build(sku: 'sku')
|
109
|
+
product.save!
|
110
|
+
|
111
|
+
product.name = 'Bar baz'
|
112
|
+
product.variants.first.sku = 'newsku'
|
113
|
+
product.save!
|
114
|
+
|
115
|
+
entry = product.audit_log_entries.last
|
116
|
+
|
117
|
+
entry.update?.should be_true
|
118
|
+
entry.tracked_changes.should == {
|
119
|
+
'name' => ['Foo bar', 'Bar baz'],
|
120
|
+
'variants' => [{ 'sku' => ['sku', 'newsku'] }]
|
121
|
+
}
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context 'destroy' do
|
126
|
+
it 'saves an entry' do
|
127
|
+
product = Product.create!(:name => 'Foo bar')
|
128
|
+
product.destroy
|
129
|
+
entry = product.audit_log_entries.last
|
130
|
+
|
131
|
+
entry.destroy?.should be_true
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'mongoid'
|
4
|
+
require 'mongoid/audit_log'
|
5
|
+
require 'rspec'
|
6
|
+
|
7
|
+
Mongoid.configure do |config|
|
8
|
+
config.connect_to('mongoid_audit_log_test')
|
9
|
+
end
|
10
|
+
|
11
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
config.mock_with :rspec
|
15
|
+
config.after(:each) { Mongoid.purge! }
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mongoid-audit_log
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ben Crouse
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-10-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: mongoid
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Stupid simple audit logging for Mongoid
|
56
|
+
email:
|
57
|
+
- bencrouse@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- .gitignore
|
63
|
+
- .rspec
|
64
|
+
- Gemfile
|
65
|
+
- LICENSE.txt
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- lib/mongoid/audit_log.rb
|
69
|
+
- lib/mongoid/audit_log/actions.rb
|
70
|
+
- lib/mongoid/audit_log/changes.rb
|
71
|
+
- lib/mongoid/audit_log/embedded_changes.rb
|
72
|
+
- lib/mongoid/audit_log/entry.rb
|
73
|
+
- lib/mongoid/audit_log/version.rb
|
74
|
+
- mongoid-audit_log.gemspec
|
75
|
+
- spec/mongoid/audit_log/changes_spec.rb
|
76
|
+
- spec/mongoid/audit_log/entry_spec.rb
|
77
|
+
- spec/mongoid/audit_log_spec.rb
|
78
|
+
- spec/spec_helper.rb
|
79
|
+
- spec/support/models.rb
|
80
|
+
homepage: https://github.com/bencrouse/mongoid-audit-log
|
81
|
+
licenses:
|
82
|
+
- MIT
|
83
|
+
metadata: {}
|
84
|
+
post_install_message:
|
85
|
+
rdoc_options: []
|
86
|
+
require_paths:
|
87
|
+
- lib
|
88
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 1.9.2
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ! '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: 1.3.6
|
98
|
+
requirements: []
|
99
|
+
rubyforge_project:
|
100
|
+
rubygems_version: 2.0.3
|
101
|
+
signing_key:
|
102
|
+
specification_version: 4
|
103
|
+
summary: No fancy versioning, undo, redo, etc. Just saves changes to Mongoid models
|
104
|
+
in a separate collection.
|
105
|
+
test_files:
|
106
|
+
- spec/mongoid/audit_log/changes_spec.rb
|
107
|
+
- spec/mongoid/audit_log/entry_spec.rb
|
108
|
+
- spec/mongoid/audit_log_spec.rb
|
109
|
+
- spec/spec_helper.rb
|
110
|
+
- spec/support/models.rb
|
111
|
+
has_rdoc:
|