mongoid-audit 0.3.2 → 1.0.0.alpha.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 +4 -4
- data/.gitignore +0 -1
- data/.ruby-version +1 -1
- data/Gemfile.lock +74 -0
- data/README.md +38 -171
- data/Rakefile +0 -12
- data/config/mongoid.yml +0 -0
- data/lib/mongoid-audit.rb +14 -27
- data/lib/mongoid-audit/history_tracker.rb +6 -0
- data/lib/mongoid-audit/rails_admin.rb +119 -108
- data/lib/mongoid-audit/railtie.rb +6 -16
- data/lib/mongoid-audit/trackable.rb +12 -262
- data/lib/mongoid-audit/version.rb +1 -1
- data/mongoid-audit.gemspec +7 -8
- metadata +41 -85
- data/.coveralls.yml +0 -1
- data/.rspec +0 -1
- data/.travis.yml +0 -15
- data/lib/mongoid-audit/mongoid_observer.rb +0 -172
- data/lib/mongoid-audit/sweeper.rb +0 -46
- data/lib/mongoid-audit/tracker.rb +0 -170
- data/spec/integration/integration_spec.rb +0 -666
- data/spec/spec_helper.rb +0 -28
- data/spec/support/database_cleaner.rb +0 -11
- data/spec/support/mongoid.rb +0 -16
- data/spec/trackable_spec.rb +0 -134
- data/spec/tracker_spec.rb +0 -17
data/.coveralls.yml
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
service_name: travis-ci
|
data/.rspec
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
--color
|
data/.travis.yml
DELETED
@@ -1,172 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
module Mongoid
|
3
|
-
|
4
|
-
# Observer classes respond to life cycle callbacks to implement trigger-like
|
5
|
-
# behavior outside the original class. This is a great way to reduce the
|
6
|
-
# clutter that normally comes when the model class is burdened with
|
7
|
-
# functionality that doesn't pertain to the core responsibility of the
|
8
|
-
# class. Mongoid's observers work similar to ActiveRecord's. Example:
|
9
|
-
#
|
10
|
-
# class CommentObserver < Mongoid::Observer
|
11
|
-
# def after_save(comment)
|
12
|
-
# Notifications.comment(
|
13
|
-
# "admin@do.com", "New comment was posted", comment
|
14
|
-
# ).deliver
|
15
|
-
# end
|
16
|
-
# end
|
17
|
-
#
|
18
|
-
# This Observer sends an email when a Comment#save is finished.
|
19
|
-
#
|
20
|
-
# class ContactObserver < Mongoid::Observer
|
21
|
-
# def after_create(contact)
|
22
|
-
# contact.logger.info('New contact added!')
|
23
|
-
# end
|
24
|
-
#
|
25
|
-
# def after_destroy(contact)
|
26
|
-
# contact.logger.warn("Contact with an id of #{contact.id} was destroyed!")
|
27
|
-
# end
|
28
|
-
# end
|
29
|
-
#
|
30
|
-
# This Observer uses logger to log when specific callbacks are triggered.
|
31
|
-
#
|
32
|
-
# == Observing a class that can't be inferred
|
33
|
-
#
|
34
|
-
# Observers will by default be mapped to the class with which they share a
|
35
|
-
# name. So CommentObserver will be tied to observing Comment,
|
36
|
-
# ProductManagerObserver to ProductManager, and so on. If you want to
|
37
|
-
# name your observer differently than the class you're interested in
|
38
|
-
# observing, you can use the Observer.observe class method which takes
|
39
|
-
# either the concrete class (Product) or a symbol for that class (:product):
|
40
|
-
#
|
41
|
-
# class AuditObserver < Mongoid::Observer
|
42
|
-
# observe :account
|
43
|
-
#
|
44
|
-
# def after_update(account)
|
45
|
-
# AuditTrail.new(account, "UPDATED")
|
46
|
-
# end
|
47
|
-
# end
|
48
|
-
#
|
49
|
-
# If the audit observer needs to watch more than one kind of object,
|
50
|
-
# this can be specified with multiple arguments:
|
51
|
-
#
|
52
|
-
# class AuditObserver < Mongoid::Observer
|
53
|
-
# observe :account, :balance
|
54
|
-
#
|
55
|
-
# def after_update(record)
|
56
|
-
# AuditTrail.new(record, "UPDATED")
|
57
|
-
# end
|
58
|
-
# end
|
59
|
-
#
|
60
|
-
# The AuditObserver will now act on both updates to Account and Balance
|
61
|
-
# by treating them both as records.
|
62
|
-
#
|
63
|
-
# == Available callback methods
|
64
|
-
#
|
65
|
-
# * after_initialize
|
66
|
-
# * before_validation
|
67
|
-
# * after_validation
|
68
|
-
# * before_create
|
69
|
-
# * around_create
|
70
|
-
# * after_create
|
71
|
-
# * before_update
|
72
|
-
# * around_update
|
73
|
-
# * after_update
|
74
|
-
# * before_upsert
|
75
|
-
# * around_upsert
|
76
|
-
# * after_upsert
|
77
|
-
# * before_save
|
78
|
-
# * around_save
|
79
|
-
# * after_save
|
80
|
-
# * before_destroy
|
81
|
-
# * around_destroy
|
82
|
-
# * after_destroy
|
83
|
-
#
|
84
|
-
# == Storing Observers in Rails
|
85
|
-
#
|
86
|
-
# If you're using Mongoid within Rails, observer classes are usually stored
|
87
|
-
# in +app/models+ with the naming convention of +app/models/audit_observer.rb+.
|
88
|
-
#
|
89
|
-
# == Configuration
|
90
|
-
#
|
91
|
-
# In order to activate an observer, list it in the +config.mongoid.observers+
|
92
|
-
# configuration setting in your +config/application.rb+ file.
|
93
|
-
#
|
94
|
-
# config.mongoid.observers = :comment_observer, :signup_observer
|
95
|
-
#
|
96
|
-
# Observers will not be invoked unless you define them in your
|
97
|
-
# application configuration.
|
98
|
-
#
|
99
|
-
# == Loading
|
100
|
-
#
|
101
|
-
# Observers register themselves with the model class that they observe,
|
102
|
-
# since it is the class that notifies them of events when they occur.
|
103
|
-
# As a side-effect, when an observer is loaded, its corresponding model
|
104
|
-
# class is loaded.
|
105
|
-
#
|
106
|
-
# Observers are loaded after the application initializers, so that
|
107
|
-
# observed models can make use of extensions. If by any chance you are
|
108
|
-
# using observed models in the initialization, you can
|
109
|
-
# still load their observers by calling +ModelObserver.instance+ before.
|
110
|
-
# Observers are singletons and that call instantiates and registers them.
|
111
|
-
class Observer < ActiveModel::Observer
|
112
|
-
|
113
|
-
private
|
114
|
-
|
115
|
-
# Adds the specified observer to the class.
|
116
|
-
#
|
117
|
-
# @example Add the observer.
|
118
|
-
# observer.add_observer!(Document)
|
119
|
-
#
|
120
|
-
# @param [ Class ] klass The child observer to add.
|
121
|
-
#
|
122
|
-
# @since 2.0.0.rc.8
|
123
|
-
def add_observer!(klass)
|
124
|
-
super
|
125
|
-
define_callbacks(klass)
|
126
|
-
end
|
127
|
-
|
128
|
-
# Defines all the callbacks for each observer of the model.
|
129
|
-
#
|
130
|
-
# @example Define all the callbacks.
|
131
|
-
# observer.define_callbacks(Document)
|
132
|
-
#
|
133
|
-
# @param [ Class ] klass The model to define them on.
|
134
|
-
#
|
135
|
-
# @since 2.0.0.rc.8
|
136
|
-
def define_callbacks(klass)
|
137
|
-
observer = self
|
138
|
-
observer_name = observer.class.name.underscore.gsub('/', '__')
|
139
|
-
Mongoid::Callbacks.observables.each do |callback|
|
140
|
-
next unless respond_to?(callback)
|
141
|
-
callback_meth = :"_notify_#{observer_name}_for_#{callback}"
|
142
|
-
unless klass.respond_to?(callback_meth)
|
143
|
-
klass.send(:define_method, callback_meth) do |&block|
|
144
|
-
if value = observer.update(callback, self, &block)
|
145
|
-
value
|
146
|
-
else
|
147
|
-
block.call if block
|
148
|
-
end
|
149
|
-
end
|
150
|
-
klass.send(callback, callback_meth)
|
151
|
-
end
|
152
|
-
end
|
153
|
-
self
|
154
|
-
end
|
155
|
-
|
156
|
-
# Are the observers disabled for the object?
|
157
|
-
#
|
158
|
-
# @api private
|
159
|
-
#
|
160
|
-
# @example If the observer disabled?
|
161
|
-
# Observer.disabled_for(band)
|
162
|
-
#
|
163
|
-
# @param [ Document ] object The model instance.
|
164
|
-
#
|
165
|
-
# @return [ true, false ] If the observer is disabled.
|
166
|
-
def disabled_for?(object)
|
167
|
-
klass = object.class
|
168
|
-
return false unless klass.respond_to?(:observers)
|
169
|
-
klass.observers.disabled_for?(self) || Mongoid.observers.disabled_for?(self)
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
@@ -1,46 +0,0 @@
|
|
1
|
-
module Mongoid::Audit
|
2
|
-
class Sweeper < ActiveModel::Observer
|
3
|
-
def controller
|
4
|
-
Thread.current[:mongoid_history_sweeper_controller]
|
5
|
-
end
|
6
|
-
|
7
|
-
def controller=(value)
|
8
|
-
Thread.current[:mongoid_history_sweeper_controller] = value
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.observed_classes
|
12
|
-
[ Mongoid::Audit.tracker_class ]
|
13
|
-
end
|
14
|
-
|
15
|
-
# Hook to ActionController::Base#around_filter.
|
16
|
-
# Runs before a controller action is run.
|
17
|
-
# It should always return true so controller actions
|
18
|
-
# can continue.
|
19
|
-
def before(controller)
|
20
|
-
self.controller = controller
|
21
|
-
true
|
22
|
-
end
|
23
|
-
|
24
|
-
# Hook to ActionController::Base#around_filter.
|
25
|
-
# Runs after a controller action is run.
|
26
|
-
# Clean up so that the controller can
|
27
|
-
# be collected after this request
|
28
|
-
def after(controller)
|
29
|
-
self.controller = nil
|
30
|
-
end
|
31
|
-
|
32
|
-
def before_create(track)
|
33
|
-
track.modifier = audit_current_user if track.modifier.nil?
|
34
|
-
end
|
35
|
-
|
36
|
-
def audit_current_user
|
37
|
-
if controller.nil?
|
38
|
-
nil
|
39
|
-
elsif controller.respond_to?(Mongoid::Audit.current_user_method, true)
|
40
|
-
controller.send Mongoid::Audit.current_user_method
|
41
|
-
else
|
42
|
-
nil
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
@@ -1,170 +0,0 @@
|
|
1
|
-
module Mongoid::Audit
|
2
|
-
module Tracker
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
included do
|
6
|
-
include Mongoid::Document
|
7
|
-
include Mongoid::Timestamps
|
8
|
-
include ActiveModel::Observing
|
9
|
-
|
10
|
-
attr_writer :trackable
|
11
|
-
|
12
|
-
field :association_chain, :type => Array, :default => []
|
13
|
-
field :modified, :type => Hash
|
14
|
-
field :original, :type => Hash
|
15
|
-
field :version, :type => Integer
|
16
|
-
field :action, :type => String
|
17
|
-
field :scope, :type => String
|
18
|
-
belongs_to :modifier, :class_name => Mongoid::Audit.modifier_class_name
|
19
|
-
|
20
|
-
Mongoid::Audit.tracker_class_name = self.name.tableize.singularize.to_sym
|
21
|
-
|
22
|
-
index({'association_chain.name' => 1, 'association_chain.id' => 1})
|
23
|
-
|
24
|
-
Mongoid::Interceptable::CALLBACKS.each do |callback|
|
25
|
-
callback_method = :"_notify_#{Mongoid::Audit.tracker_class_name}_#{callback}"
|
26
|
-
module_eval <<-RUBY, __FILE__, __LINE__+1
|
27
|
-
#{callback} #{callback_method.inspect}
|
28
|
-
def #{callback_method}(&block)
|
29
|
-
if "#{callback}".start_with?( 'around_' )
|
30
|
-
notify_observers(#{callback.inspect}, &block)
|
31
|
-
yield
|
32
|
-
else
|
33
|
-
notify_observers(#{callback.inspect}, &block)
|
34
|
-
true
|
35
|
-
end
|
36
|
-
end
|
37
|
-
private #{callback_method.inspect}
|
38
|
-
RUBY
|
39
|
-
end
|
40
|
-
|
41
|
-
end
|
42
|
-
|
43
|
-
def undo!(modifier)
|
44
|
-
if action.to_sym == :destroy
|
45
|
-
re_create
|
46
|
-
elsif action.to_sym == :create
|
47
|
-
re_destroy
|
48
|
-
else
|
49
|
-
trackable.update_attributes!(undo_attr(modifier))
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def redo!(modifier)
|
54
|
-
if action.to_sym == :destroy
|
55
|
-
re_destroy
|
56
|
-
elsif action.to_sym == :create
|
57
|
-
re_create
|
58
|
-
else
|
59
|
-
trackable.update_attributes!(redo_attr(modifier))
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def undo_attr(modifier)
|
64
|
-
undo_hash = affected.easy_unmerge(modified)
|
65
|
-
undo_hash.easy_merge!(original)
|
66
|
-
modifier_field = trackable.history_trackable_options[:modifier_field]
|
67
|
-
undo_hash[modifier_field] = modifier
|
68
|
-
undo_hash
|
69
|
-
end
|
70
|
-
|
71
|
-
def redo_attr(modifier)
|
72
|
-
redo_hash = affected.easy_unmerge(original)
|
73
|
-
redo_hash.easy_merge!(modified)
|
74
|
-
modifier_field = trackable.history_trackable_options[:modifier_field]
|
75
|
-
redo_hash[modifier_field] = modifier
|
76
|
-
redo_hash
|
77
|
-
end
|
78
|
-
|
79
|
-
def trackable_root
|
80
|
-
@trackable_root ||= trackable_parents_and_trackable.first
|
81
|
-
end
|
82
|
-
|
83
|
-
def trackable
|
84
|
-
@trackable ||= trackable_parents_and_trackable.last
|
85
|
-
end
|
86
|
-
|
87
|
-
def trackable_parents
|
88
|
-
@trackable_parents ||= trackable_parents_and_trackable[0, -1]
|
89
|
-
end
|
90
|
-
|
91
|
-
def trackable_parent
|
92
|
-
@trackable_parent ||= trackable_parents_and_trackable[-2]
|
93
|
-
end
|
94
|
-
|
95
|
-
def affected
|
96
|
-
@affected ||= (modified.keys | original.keys).inject({}){ |h,k| h[k] =
|
97
|
-
trackable ? trackable.attributes[k] : modified[k]; h}
|
98
|
-
end
|
99
|
-
|
100
|
-
private
|
101
|
-
|
102
|
-
def re_create
|
103
|
-
association_chain.length > 1 ? create_on_parent : create_standalone
|
104
|
-
end
|
105
|
-
|
106
|
-
def re_destroy
|
107
|
-
trackable.destroy
|
108
|
-
end
|
109
|
-
|
110
|
-
def create_standalone
|
111
|
-
class_name = association_chain.first["name"]
|
112
|
-
restored = class_name.constantize.new(modified)
|
113
|
-
restored.id = modified["_id"]
|
114
|
-
restored.save!
|
115
|
-
end
|
116
|
-
|
117
|
-
def create_on_parent
|
118
|
-
name = association_chain.last["name"]
|
119
|
-
if embeds_one?(trackable_parent, name)
|
120
|
-
trackable_parent.send("create_#{name}!", modified)
|
121
|
-
elsif embeds_many?(trackable_parent, name)
|
122
|
-
trackable_parent.send(name).create!(modified)
|
123
|
-
else
|
124
|
-
raise "This should never happen. Please report bug!"
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
def trackable_parents_and_trackable
|
129
|
-
@trackable_parents_and_trackable ||= traverse_association_chain
|
130
|
-
end
|
131
|
-
|
132
|
-
def relation_of(doc, name)
|
133
|
-
meta = doc.reflect_on_association(name)
|
134
|
-
meta ? meta.relation : nil
|
135
|
-
end
|
136
|
-
|
137
|
-
def embeds_one?(doc, name)
|
138
|
-
relation_of(doc, name) == Mongoid::Relations::Embedded::One
|
139
|
-
end
|
140
|
-
|
141
|
-
def embeds_many?(doc, name)
|
142
|
-
relation_of(doc, name) == Mongoid::Relations::Embedded::Many
|
143
|
-
end
|
144
|
-
|
145
|
-
def traverse_association_chain
|
146
|
-
chain = association_chain.dup
|
147
|
-
doc = nil
|
148
|
-
documents = []
|
149
|
-
|
150
|
-
begin
|
151
|
-
node = chain.shift
|
152
|
-
name = node['name']
|
153
|
-
|
154
|
-
doc = if doc.nil?
|
155
|
-
# root association. First element of the association chain
|
156
|
-
klass = name.classify.constantize
|
157
|
-
klass.where(:_id => node['id']).first
|
158
|
-
elsif embeds_one?(doc, name)
|
159
|
-
doc.send(name)
|
160
|
-
elsif embeds_many?(doc, name)
|
161
|
-
doc.send(name).where(:_id => node['id']).first
|
162
|
-
else
|
163
|
-
raise "This should never happen. Please report bug."
|
164
|
-
end
|
165
|
-
documents << doc
|
166
|
-
end while( !chain.empty? )
|
167
|
-
documents
|
168
|
-
end
|
169
|
-
end
|
170
|
-
end
|
@@ -1,666 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
-
|
3
|
-
describe Mongoid::Audit do
|
4
|
-
before :all do
|
5
|
-
class Post
|
6
|
-
include Mongoid::Document
|
7
|
-
include Mongoid::Timestamps
|
8
|
-
include Mongoid::Audit::Trackable
|
9
|
-
|
10
|
-
field :title
|
11
|
-
field :body
|
12
|
-
field :rating
|
13
|
-
field :views
|
14
|
-
|
15
|
-
embeds_many :comments
|
16
|
-
embeds_one :section
|
17
|
-
embeds_many :tags
|
18
|
-
|
19
|
-
accepts_nested_attributes_for :tags, :allow_destroy => true
|
20
|
-
|
21
|
-
track_history :on => [:title, :body], :track_destroy => true
|
22
|
-
end
|
23
|
-
|
24
|
-
class Comment
|
25
|
-
include Mongoid::Document
|
26
|
-
include Mongoid::Timestamps
|
27
|
-
include Mongoid::Audit::Trackable
|
28
|
-
|
29
|
-
field :title
|
30
|
-
field :body
|
31
|
-
embedded_in :post
|
32
|
-
track_history :on => [:title, :body], :scope => :post, :track_create => true, :track_destroy => true
|
33
|
-
end
|
34
|
-
|
35
|
-
class Section
|
36
|
-
include Mongoid::Document
|
37
|
-
include Mongoid::Timestamps
|
38
|
-
include Mongoid::Audit::Trackable
|
39
|
-
|
40
|
-
field :title
|
41
|
-
embedded_in :post
|
42
|
-
track_history :on => [:title], :scope => :post, :track_create => true, :track_destroy => true
|
43
|
-
end
|
44
|
-
|
45
|
-
class User
|
46
|
-
include Mongoid::Document
|
47
|
-
include Mongoid::Timestamps
|
48
|
-
include Mongoid::Audit::Trackable
|
49
|
-
|
50
|
-
has_and_belongs_to_many :own_restaurants, class_name: 'Restaurant', inverse_of: :owners
|
51
|
-
|
52
|
-
field :email
|
53
|
-
field :name
|
54
|
-
track_history :except => [:email]
|
55
|
-
end
|
56
|
-
|
57
|
-
class Tag
|
58
|
-
include Mongoid::Document
|
59
|
-
include Mongoid::Timestamps
|
60
|
-
include Mongoid::Audit::Trackable
|
61
|
-
|
62
|
-
belongs_to :updated_by, :class_name => "User"
|
63
|
-
|
64
|
-
field :title
|
65
|
-
track_history :on => [:title], :scope => :post, :track_create => true, :track_destroy => true, :modifier_field => :updated_by
|
66
|
-
end
|
67
|
-
|
68
|
-
class Restaurant
|
69
|
-
include Mongoid::Document
|
70
|
-
include Mongoid::Audit::Trackable
|
71
|
-
|
72
|
-
has_and_belongs_to_many :owners, class_name: 'User', inverse_of: nil
|
73
|
-
|
74
|
-
field :title
|
75
|
-
|
76
|
-
track_history :on => [:title], :track_create => true, :track_destroy => true
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
before :each do
|
81
|
-
@user = User.create(:name => "Aaron", :email => "aaron@randomemail.com")
|
82
|
-
@another_user = User.create(:name => "Another Guy", :email => "anotherguy@randomemail.com")
|
83
|
-
@post = Post.create(:title => "Test", :body => "Post", :modifier => @user, :views => 100)
|
84
|
-
@comment = @post.comments.create(:title => "test", :body => "comment", :modifier => @user)
|
85
|
-
end
|
86
|
-
|
87
|
-
describe "track" do
|
88
|
-
describe "on creation" do
|
89
|
-
it "should have one history track in comment" do
|
90
|
-
@comment.history_tracks.count.should == 1
|
91
|
-
end
|
92
|
-
|
93
|
-
it "should assign title and body on modified" do
|
94
|
-
@comment.history_tracks.first.modified.should == {'title' => "test", 'body' => "comment"}
|
95
|
-
end
|
96
|
-
|
97
|
-
it "should not assign title and body on original" do
|
98
|
-
@comment.history_tracks.first.original.should == {}
|
99
|
-
end
|
100
|
-
|
101
|
-
it "should assign modifier" do
|
102
|
-
@comment.history_tracks.first.modifier.should == @user
|
103
|
-
end
|
104
|
-
|
105
|
-
it "should assign version" do
|
106
|
-
@comment.history_tracks.first.version.should == 1
|
107
|
-
end
|
108
|
-
|
109
|
-
it "should assign scope" do
|
110
|
-
@comment.history_tracks.first.scope.should == "post"
|
111
|
-
end
|
112
|
-
|
113
|
-
it "should assign method" do
|
114
|
-
@comment.history_tracks.first.action.should == "create"
|
115
|
-
end
|
116
|
-
|
117
|
-
it "should assign association_chain" do
|
118
|
-
expected = [
|
119
|
-
{'id' => @post.id, 'name' => "Post"},
|
120
|
-
{'id' => @comment.id, 'name' => "comments"}
|
121
|
-
]
|
122
|
-
@comment.history_tracks.first.association_chain.should == expected
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
describe "on destruction" do
|
127
|
-
it "should have two history track records in post" do
|
128
|
-
lambda {
|
129
|
-
@post.destroy
|
130
|
-
}.should change(HistoryTracker, :count).by(1)
|
131
|
-
end
|
132
|
-
|
133
|
-
it "should assign destroy on track record" do
|
134
|
-
@post.destroy
|
135
|
-
@post.history_tracks.last.action.should == "destroy"
|
136
|
-
end
|
137
|
-
|
138
|
-
it "should return affected attributes from track record" do
|
139
|
-
@post.destroy
|
140
|
-
@post.history_tracks.last.affected["title"].should == "Test"
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
describe "on update non-embedded" do
|
145
|
-
it "should create a history track if changed attributes match tracked attributes" do
|
146
|
-
lambda {
|
147
|
-
@post.update_attributes(:title => "Another Test")
|
148
|
-
}.should change(HistoryTracker, :count).by(1)
|
149
|
-
end
|
150
|
-
|
151
|
-
it "should not create a history track if changed attributes do not match tracked attributes" do
|
152
|
-
lambda {
|
153
|
-
@post.update_attributes(:rating => "untracked")
|
154
|
-
}.should change(HistoryTracker, :count).by(0)
|
155
|
-
end
|
156
|
-
|
157
|
-
it "should assign modified fields" do
|
158
|
-
@post.update_attributes(:title => "Another Test")
|
159
|
-
@post.history_tracks.last.modified.should == {
|
160
|
-
"title" => "Another Test"
|
161
|
-
}
|
162
|
-
end
|
163
|
-
|
164
|
-
it "should assign method field" do
|
165
|
-
@post.update_attributes(:title => "Another Test")
|
166
|
-
@post.history_tracks.last.action.should == "update"
|
167
|
-
end
|
168
|
-
|
169
|
-
it "should assign original fields" do
|
170
|
-
@post.update_attributes(:title => "Another Test")
|
171
|
-
@post.history_tracks.last.original.should == {
|
172
|
-
"title" => "Test"
|
173
|
-
}
|
174
|
-
end
|
175
|
-
|
176
|
-
it "should assign modifier" do
|
177
|
-
@post.update_attributes(:title => "Another Test")
|
178
|
-
@post.history_tracks.first.modifier.should == @user
|
179
|
-
end
|
180
|
-
|
181
|
-
it "should assign version on history tracks" do
|
182
|
-
@post.update_attributes(:title => "Another Test")
|
183
|
-
@post.history_tracks.first.version.should == 1
|
184
|
-
end
|
185
|
-
|
186
|
-
it "should assign version on post" do
|
187
|
-
@post.update_attributes(:title => "Another Test")
|
188
|
-
@post.version.should == 1
|
189
|
-
end
|
190
|
-
|
191
|
-
it "should assign scope" do
|
192
|
-
@post.update_attributes(:title => "Another Test")
|
193
|
-
@post.history_tracks.first.scope.should == "post"
|
194
|
-
end
|
195
|
-
|
196
|
-
it "should assign association_chain" do
|
197
|
-
@post.update_attributes(:title => "Another Test")
|
198
|
-
@post.history_tracks.last.association_chain.should == [{'id' => @post.id, 'name' => "Post"}]
|
199
|
-
end
|
200
|
-
|
201
|
-
it "should exclude defined options" do
|
202
|
-
@user.update_attributes(:name => "Aaron2", :email => "aaronsnewemail@randomemail.com")
|
203
|
-
@user.history_tracks.first.modified.keys.should include "name"
|
204
|
-
@user.history_tracks.first.modified.keys.should_not include "email"
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
describe "on update non-embedded twice" do
|
209
|
-
it "should assign version on post" do
|
210
|
-
@post.update_attributes(:title => "Test2")
|
211
|
-
@post.update_attributes(:title => "Test3")
|
212
|
-
@post.version.should == 2
|
213
|
-
end
|
214
|
-
|
215
|
-
it "should create a history track if changed attributes match tracked attributes" do
|
216
|
-
lambda {
|
217
|
-
@post.update_attributes(:title => "Test2")
|
218
|
-
@post.update_attributes(:title => "Test3")
|
219
|
-
}.should change(HistoryTracker, :count).by(2)
|
220
|
-
end
|
221
|
-
|
222
|
-
it "should create a history track of version 2" do
|
223
|
-
@post.update_attributes(:title => "Test2")
|
224
|
-
@post.update_attributes(:title => "Test3")
|
225
|
-
@post.history_tracks.where(:version => 2).first.should_not be_nil
|
226
|
-
end
|
227
|
-
|
228
|
-
it "should assign modified fields" do
|
229
|
-
@post.update_attributes(:title => "Test2")
|
230
|
-
@post.update_attributes(:title => "Test3")
|
231
|
-
@post.history_tracks.where(:version => 2).first.modified.should == {
|
232
|
-
"title" => "Test3"
|
233
|
-
}
|
234
|
-
end
|
235
|
-
|
236
|
-
it "should assign original fields" do
|
237
|
-
@post.update_attributes(:title => "Test2")
|
238
|
-
@post.update_attributes(:title => "Test3")
|
239
|
-
@post.history_tracks.where(:version => 2).first.original.should == {
|
240
|
-
"title" => "Test2"
|
241
|
-
}
|
242
|
-
end
|
243
|
-
|
244
|
-
|
245
|
-
it "should assign modifier" do
|
246
|
-
@post.update_attributes(:title => "Another Test", :modifier => @another_user)
|
247
|
-
@post.history_tracks.last.modifier.should == @another_user
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
describe "on update embedded 1..N (embeds_many)" do
|
252
|
-
it "should assign version on comment" do
|
253
|
-
@comment.update_attributes(:title => "Test2")
|
254
|
-
@comment.version.should == 2 # first track generated on creation
|
255
|
-
end
|
256
|
-
|
257
|
-
it "should create a history track of version 2" do
|
258
|
-
@comment.update_attributes(:title => "Test2")
|
259
|
-
@comment.history_tracks.where(:version => 2).first.should_not be_nil
|
260
|
-
end
|
261
|
-
|
262
|
-
it "should assign modified fields" do
|
263
|
-
@comment.update_attributes(:title => "Test2")
|
264
|
-
@comment.history_tracks.where(:version => 2).first.modified.should == {
|
265
|
-
"title" => "Test2"
|
266
|
-
}
|
267
|
-
end
|
268
|
-
|
269
|
-
it "should assign original fields" do
|
270
|
-
@comment.update_attributes(:title => "Test2")
|
271
|
-
@comment.history_tracks.where(:version => 2).first.original.should == {
|
272
|
-
"title" => "test"
|
273
|
-
}
|
274
|
-
end
|
275
|
-
|
276
|
-
it "should be possible to undo from parent" do
|
277
|
-
@comment.update_attributes(:title => "Test 2")
|
278
|
-
@post.history_tracks.last.undo!(@user)
|
279
|
-
@comment.reload
|
280
|
-
@comment.title.should == "test"
|
281
|
-
end
|
282
|
-
|
283
|
-
it "should assign modifier" do
|
284
|
-
@post.update_attributes(:title => "Another Test", :modifier => @another_user)
|
285
|
-
@post.history_tracks.last.modifier.should == @another_user
|
286
|
-
end
|
287
|
-
end
|
288
|
-
|
289
|
-
describe "on update embedded 1..1 (embeds_one)" do
|
290
|
-
before(:each) do
|
291
|
-
@section = Section.new(:title => 'Technology')
|
292
|
-
@post.section = @section
|
293
|
-
@post.save!
|
294
|
-
@post.reload
|
295
|
-
@section = @post.section
|
296
|
-
end
|
297
|
-
|
298
|
-
it "should assign version on create section" do
|
299
|
-
@section.version.should == 1
|
300
|
-
end
|
301
|
-
|
302
|
-
it "should assign version on section" do
|
303
|
-
@section.update_attributes(:title => 'Technology 2')
|
304
|
-
@section.version.should == 2 # first track generated on creation
|
305
|
-
end
|
306
|
-
|
307
|
-
it "should create a history track of version 2" do
|
308
|
-
@section.update_attributes(:title => 'Technology 2')
|
309
|
-
@section.history_tracks.where(:version => 2).first.should_not be_nil
|
310
|
-
end
|
311
|
-
|
312
|
-
it "should assign modified fields" do
|
313
|
-
@section.update_attributes(:title => 'Technology 2')
|
314
|
-
@section.history_tracks.where(:version => 2).first.modified.should == {
|
315
|
-
"title" => "Technology 2"
|
316
|
-
}
|
317
|
-
end
|
318
|
-
|
319
|
-
it "should assign original fields" do
|
320
|
-
@section.update_attributes(:title => 'Technology 2')
|
321
|
-
@section.history_tracks.where(:version => 2).first.original.should == {
|
322
|
-
"title" => "Technology"
|
323
|
-
}
|
324
|
-
end
|
325
|
-
|
326
|
-
it "should be possible to undo from parent" do
|
327
|
-
@section.update_attributes(:title => 'Technology 2')
|
328
|
-
@post.history_tracks.last.undo!(@user)
|
329
|
-
@section.reload
|
330
|
-
@section.title.should == "Technology"
|
331
|
-
end
|
332
|
-
|
333
|
-
it "should assign modifier" do
|
334
|
-
@section.update_attributes(:title => "Business", :modifier => @another_user)
|
335
|
-
@post.reload
|
336
|
-
# just workaround strange mongoid #last bug for now
|
337
|
-
@post.history_tracks.to_a[-1].modifier.should == @another_user
|
338
|
-
end
|
339
|
-
|
340
|
-
it "should update modifier" do
|
341
|
-
@section.update_attributes(:title => 'Technology 2', :modifier => @user)
|
342
|
-
@section.update_attributes(:title => "Business", :modifier => @another_user)
|
343
|
-
@post.reload
|
344
|
-
@post.history_tracks.last.modifier.should == @another_user
|
345
|
-
end
|
346
|
-
end
|
347
|
-
|
348
|
-
describe "on destroy embedded" do
|
349
|
-
it "should be possible to re-create destroyed embedded" do
|
350
|
-
@comment.destroy
|
351
|
-
@comment.history_tracks.last.undo!(@user)
|
352
|
-
@post.reload
|
353
|
-
@post.comments.first.title.should == "test"
|
354
|
-
end
|
355
|
-
|
356
|
-
it "should be possible to re-create destroyed embedded from parent" do
|
357
|
-
@comment.destroy
|
358
|
-
@post.history_tracks.last.undo!(@user)
|
359
|
-
@post.reload
|
360
|
-
@post.comments.first.title.should == "test"
|
361
|
-
end
|
362
|
-
|
363
|
-
it "should be possible to destroy after re-create embedded from parent" do
|
364
|
-
@comment.destroy
|
365
|
-
@post.history_tracks.last.undo!(@user)
|
366
|
-
@post.history_tracks.last.undo!(@user)
|
367
|
-
@post.reload
|
368
|
-
@post.comments.count.should == 0
|
369
|
-
end
|
370
|
-
|
371
|
-
it "should be possible to create with redo after undo create embedded from parent" do
|
372
|
-
@post.comments.create!(:title => "The second one")
|
373
|
-
@track = @post.history_tracks.last
|
374
|
-
@track.undo!(@user)
|
375
|
-
@track.redo!(@user)
|
376
|
-
@post.reload
|
377
|
-
@post.comments.count.should == 2
|
378
|
-
end
|
379
|
-
end
|
380
|
-
|
381
|
-
describe "embedded with cascading callbacks" do
|
382
|
-
before(:each) do
|
383
|
-
Thread.current[:mongoid_history_sweeper_controller] = self
|
384
|
-
# self.stub!(:current_user).and_return @user
|
385
|
-
allow(self).to receive(:current_user).and_return(@user)
|
386
|
-
@tag_foo = @post.tags.create(:title => "foo", :updated_by => @user)
|
387
|
-
@tag_bar = @post.tags.create(:title => "bar")
|
388
|
-
end
|
389
|
-
|
390
|
-
after(:each) do
|
391
|
-
Thread.current[:mongoid_history_sweeper_controller] = nil
|
392
|
-
end
|
393
|
-
|
394
|
-
it "should have cascaded the creation callbacks and set timestamps" do
|
395
|
-
@tag_foo.created_at.should_not be_nil
|
396
|
-
@tag_foo.updated_at.should_not be_nil
|
397
|
-
end
|
398
|
-
|
399
|
-
it "should allow an update through the parent model" do
|
400
|
-
update_hash = { "post" => { "tags_attributes" => { "1234" => { "id" => @tag_bar.id, "title" => "baz" } } } }
|
401
|
-
@post.update_attributes(update_hash["post"])
|
402
|
-
@post.tags.last.title.should == "baz"
|
403
|
-
end
|
404
|
-
|
405
|
-
it "should be possible to destroy through parent model using canoncial _destroy macro" do
|
406
|
-
@post.tags.count.should == 2
|
407
|
-
update_hash = { "post" => { "tags_attributes" => { "1234" => { "id" => @tag_bar.id, "title" => "baz", "_destroy" => "true"} } } }
|
408
|
-
@post.update_attributes(update_hash["post"])
|
409
|
-
@post.tags.count.should == 1
|
410
|
-
@post.history_tracks.last.action.should == "destroy"
|
411
|
-
end
|
412
|
-
|
413
|
-
it "should write relationship name for association_chain hiearchy instead of class name when using _destroy macro" do
|
414
|
-
update_hash = {"tags_attributes" => { "1234" => { "id" => @tag_foo.id, "_destroy" => "1"} } }
|
415
|
-
@post.update_attributes(update_hash)
|
416
|
-
|
417
|
-
# historically this would have evaluated to 'Tags' and an error would be thrown
|
418
|
-
# on any call that walked up the association_chain, e.g. 'trackable'
|
419
|
-
@tag_foo.history_tracks.last.association_chain.last["name"].should == "tags"
|
420
|
-
lambda{ @tag_foo.history_tracks.last.trackable }.should_not raise_error
|
421
|
-
end
|
422
|
-
|
423
|
-
it "should save modifier" do
|
424
|
-
@tag_foo.history_tracks.last.modifier.should eq @user
|
425
|
-
@tag_bar.history_tracks.last.modifier.should eq @user
|
426
|
-
end
|
427
|
-
end
|
428
|
-
|
429
|
-
describe "non-embedded" do
|
430
|
-
it "should undo changes" do
|
431
|
-
@post.update_attributes(:title => "Test2")
|
432
|
-
@post.history_tracks.where(:version => 1).last.undo!(@user)
|
433
|
-
@post.reload
|
434
|
-
@post.title.should == "Test"
|
435
|
-
end
|
436
|
-
|
437
|
-
it "should undo destruction" do
|
438
|
-
@post.destroy
|
439
|
-
@post.history_tracks.where(:version => 1).last.undo!(@user)
|
440
|
-
Post.find(@post.id).title.should == "Test"
|
441
|
-
end
|
442
|
-
|
443
|
-
it "should create a new history track after undo" do
|
444
|
-
@post.update_attributes(:title => "Test2")
|
445
|
-
@post.history_tracks.last.undo!(@user)
|
446
|
-
@post.reload
|
447
|
-
@post.history_tracks.count.should == 3
|
448
|
-
end
|
449
|
-
|
450
|
-
it "should assign @user as the modifier of the newly created history track" do
|
451
|
-
@post.update_attributes(:title => "Test2")
|
452
|
-
@post.history_tracks.where(:version => 1).last.undo!(@user)
|
453
|
-
@post.reload
|
454
|
-
@post.history_tracks.where(:version => 2).last.modifier.should == @user
|
455
|
-
end
|
456
|
-
|
457
|
-
it "should stay the same after undo and redo" do
|
458
|
-
@post.update_attributes(:title => "Test2")
|
459
|
-
@track = @post.history_tracks.last
|
460
|
-
@track.undo!(@user)
|
461
|
-
@track.redo!(@user)
|
462
|
-
@post2 = Post.where(:_id => @post.id).first
|
463
|
-
|
464
|
-
@post.title.should == @post2.title
|
465
|
-
end
|
466
|
-
|
467
|
-
it "should be destroyed after undo and redo" do
|
468
|
-
@post.destroy
|
469
|
-
@track = @post.history_tracks.where(:version => 1).last
|
470
|
-
@track.undo!(@user)
|
471
|
-
@track.redo!(@user)
|
472
|
-
Post.where(:_id => @post.id).first.should == nil
|
473
|
-
end
|
474
|
-
end
|
475
|
-
|
476
|
-
describe "embedded" do
|
477
|
-
it "should undo changes" do
|
478
|
-
@comment.update_attributes(:title => "Test2")
|
479
|
-
@comment.history_tracks.where(:version => 2).first.undo!(@user)
|
480
|
-
# reloading an embedded document === KAMIKAZE
|
481
|
-
# at least for the current release of mongoid...
|
482
|
-
@post.reload
|
483
|
-
@comment = @post.comments.first
|
484
|
-
@comment.title.should == "test"
|
485
|
-
end
|
486
|
-
|
487
|
-
it "should create a new history track after undo" do
|
488
|
-
@comment.update_attributes(:title => "Test2")
|
489
|
-
@comment.history_tracks.where(:version => 2).first.undo!(@user)
|
490
|
-
@post.reload
|
491
|
-
@comment = @post.comments.first
|
492
|
-
@comment.history_tracks.count.should == 3
|
493
|
-
end
|
494
|
-
|
495
|
-
it "should assign @user as the modifier of the newly created history track" do
|
496
|
-
@comment.update_attributes(:title => "Test2")
|
497
|
-
@comment.history_tracks.where(:version => 2).first.undo!(@user)
|
498
|
-
@post.reload
|
499
|
-
@comment = @post.comments.first
|
500
|
-
@comment.history_tracks.where(:version => 3).first.modifier.should == @user
|
501
|
-
end
|
502
|
-
|
503
|
-
it "should stay the same after undo and redo" do
|
504
|
-
@comment.update_attributes(:title => "Test2")
|
505
|
-
@track = @comment.history_tracks.where(:version => 2).first
|
506
|
-
@track.undo!(@user)
|
507
|
-
@track.redo!(@user)
|
508
|
-
@post2 = Post.where(:_id => @post.id).first
|
509
|
-
@comment2 = @post2.comments.first
|
510
|
-
|
511
|
-
@comment.title.should == @comment2.title
|
512
|
-
end
|
513
|
-
end
|
514
|
-
|
515
|
-
describe "trackables" do
|
516
|
-
before :each do
|
517
|
-
@comment.update_attributes(:title => "Test2") # version == 2
|
518
|
-
@comment.update_attributes(:title => "Test3") # version == 3
|
519
|
-
@comment.update_attributes(:title => "Test4") # version == 4
|
520
|
-
end
|
521
|
-
|
522
|
-
describe "undo" do
|
523
|
-
it "should recognize :from, :to options" do
|
524
|
-
@comment.undo! @user, :from => 4, :to => 2
|
525
|
-
@comment.title.should == "test"
|
526
|
-
end
|
527
|
-
|
528
|
-
it "should recognize parameter as version number" do
|
529
|
-
@comment.undo! @user, 3
|
530
|
-
@comment.title.should == "Test2"
|
531
|
-
end
|
532
|
-
|
533
|
-
it "should undo last version when no parameter is specified" do
|
534
|
-
@comment.undo! @user
|
535
|
-
@comment.title.should == "Test3"
|
536
|
-
end
|
537
|
-
|
538
|
-
it "should recognize :last options" do
|
539
|
-
@comment.undo! @user, :last => 2
|
540
|
-
@comment.title.should == "Test2"
|
541
|
-
end
|
542
|
-
end
|
543
|
-
|
544
|
-
describe "redo" do
|
545
|
-
before :each do
|
546
|
-
@comment.update_attributes(:title => "Test5")
|
547
|
-
end
|
548
|
-
|
549
|
-
it "should recognize :from, :to options" do
|
550
|
-
@comment.redo! @user, :from => 2, :to => 4
|
551
|
-
@comment.title.should == "Test4"
|
552
|
-
end
|
553
|
-
|
554
|
-
it "should recognize parameter as version number" do
|
555
|
-
@comment.redo! @user, 2
|
556
|
-
@comment.title.should == "Test2"
|
557
|
-
end
|
558
|
-
|
559
|
-
it "should redo last version when no parameter is specified" do
|
560
|
-
@comment.redo! @user
|
561
|
-
@comment.title.should == "Test5"
|
562
|
-
end
|
563
|
-
|
564
|
-
it "should recognize :last options" do
|
565
|
-
@comment.redo! @user, :last => 1
|
566
|
-
@comment.title.should == "Test5"
|
567
|
-
end
|
568
|
-
end
|
569
|
-
end
|
570
|
-
|
571
|
-
describe "duplicate relations" do
|
572
|
-
it "should save correct relation" do
|
573
|
-
lambda{ Restaurant.create!( title: 'test', modifier_id: @user.id ) }.should_not raise_error
|
574
|
-
end
|
575
|
-
end
|
576
|
-
|
577
|
-
|
578
|
-
describe "rails admin history" do
|
579
|
-
before :each do
|
580
|
-
@restaurant = Restaurant.create!( title: 'test', modifier_id: @user.id )
|
581
|
-
@adapter = ::RailsAdmin::Extensions::MongoidAudit::AuditingAdapter.new( nil )
|
582
|
-
@model = Struct.new( :model_name ).new( 'Restaurant' )
|
583
|
-
end
|
584
|
-
|
585
|
-
it "should list all records from history table" do
|
586
|
-
query = ''
|
587
|
-
|
588
|
-
items = @adapter.listing_for_model(@model, query, false, 'false', true, 1, 10)
|
589
|
-
|
590
|
-
expect( items ).not_to be_empty
|
591
|
-
|
592
|
-
item = items.first
|
593
|
-
|
594
|
-
expect( item.message ).to eq 'create Restaurant [title = test]'
|
595
|
-
expect( item.table ).to eq 'Restaurant'
|
596
|
-
expect( item.username ).to eq @user.email
|
597
|
-
expect( item.item ).to eq @restaurant.id
|
598
|
-
|
599
|
-
query = nil
|
600
|
-
|
601
|
-
items = @adapter.listing_for_model(@model, query, false, 'false', true, 1, 10)
|
602
|
-
|
603
|
-
expect( items ).not_to be_empty
|
604
|
-
|
605
|
-
item = items.first
|
606
|
-
|
607
|
-
expect( item.message ).to eq 'create Restaurant [title = test]'
|
608
|
-
end
|
609
|
-
|
610
|
-
it "should list records from history table specified by query" do
|
611
|
-
query = 'create'
|
612
|
-
|
613
|
-
items = @adapter.listing_for_model(@model, query, false, 'false', true, 1, 10)
|
614
|
-
|
615
|
-
expect( items ).not_to be_empty
|
616
|
-
|
617
|
-
item = items.first
|
618
|
-
|
619
|
-
expect( item.message ).to eq 'create Restaurant [title = test]'
|
620
|
-
expect( item.table ).to eq 'Restaurant'
|
621
|
-
expect( item.username ).to eq @user.email
|
622
|
-
expect( item.item ).to eq @restaurant.id
|
623
|
-
end
|
624
|
-
|
625
|
-
it "should list records from history table specified by item" do
|
626
|
-
query = ''
|
627
|
-
|
628
|
-
items = @adapter.listing_for_object(@model, @restaurant, query, false, 'false', true, 1, 10)
|
629
|
-
|
630
|
-
expect( items ).not_to be_empty
|
631
|
-
|
632
|
-
item = items.first
|
633
|
-
|
634
|
-
expect( item.message ).to eq 'create Restaurant [title = test]'
|
635
|
-
expect( item.table ).to eq 'Restaurant'
|
636
|
-
expect( item.username ).to eq @user.email
|
637
|
-
expect( item.item ).to eq @restaurant.id
|
638
|
-
|
639
|
-
query = nil
|
640
|
-
|
641
|
-
items = @adapter.listing_for_object(@model, @restaurant, query, false, 'false', true, 1, 10)
|
642
|
-
|
643
|
-
expect( items ).not_to be_empty
|
644
|
-
|
645
|
-
item = items.first
|
646
|
-
|
647
|
-
expect( item.message ).to eq 'create Restaurant [title = test]'
|
648
|
-
end
|
649
|
-
|
650
|
-
it "should list records from history table specified by item and query" do
|
651
|
-
query = 'create'
|
652
|
-
|
653
|
-
items = @adapter.listing_for_object(@model, @restaurant, query, false, 'false', true, 1, 10)
|
654
|
-
|
655
|
-
expect( items ).not_to be_empty
|
656
|
-
|
657
|
-
item = items.first
|
658
|
-
|
659
|
-
expect( item.message ).to eq 'create Restaurant [title = test]'
|
660
|
-
expect( item.table ).to eq 'Restaurant'
|
661
|
-
expect( item.username ).to eq @user.email
|
662
|
-
expect( item.item ).to eq @restaurant.id
|
663
|
-
end
|
664
|
-
end
|
665
|
-
end
|
666
|
-
end
|