mongoid-audit 0.3.2 → 1.0.0.alpha.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|