mongoid-history-patched 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - 1.9.3
6
+ - ruby-head
7
+ - jruby
8
+ - rbx
9
+ - ree
10
+
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "easy_diff"
4
+ gem "mongoid", ">= 2.0.0"
5
+
6
+ group :test do
7
+ gem "bson_ext"
8
+ gem "rspec"
9
+ gem "yard"
10
+ gem "bundler", ">= 1.0.0"
11
+ gem "jeweler"
12
+ gem "database_cleaner"
13
+ end
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Aaron Qian
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,206 @@
1
+ mongoid-history
2
+ ===============
3
+
4
+ [![Build Status](https://secure.travis-ci.org/aq1018/mongoid-history.png?branch=master)](http://travis-ci.org/aq1018/mongoid-history) [![Dependency Status](https://gemnasium.com/aq1018/mongoid-history.png?travis)](https://gemnasium.com/aq1018/mongoid-history)
5
+
6
+
7
+ In frustration of Mongoid::Versioning, I created this plugin for tracking historical changes for any document, including embedded ones. It achieves this by storing all history tracks in a single collection that you define. Embedded documents are referenced by storing an association path, which is an array of `document_name` and `document_id` fields starting from the top most parent document and down to the embedded document that should track history.
8
+
9
+ This plugin also implements multi-user undo, which allows users to undo any history change in any order. Undoing a document also creates a new history track. This is great for auditing and preventing vandalism, but it is probably not suitable for use cases such as a wiki.
10
+
11
+ Note
12
+ ----
13
+
14
+ **Please don't use 0.1.8 and 0.2.0.**
15
+
16
+ These versions won't work in Rails because there was an error in the sweeper that causes history tracker creation to fail. Upgrade to version 0.2.1 instead as it's able to track history on `embeds_one` documents correctly.
17
+
18
+ **Refactor in progress**
19
+
20
+ If you feel brave, you can look at the `refactor` branch and get a feel of what's coming. As I stated many times before, this gem was originally hacked up in one evening, and got patched many times by various fellow users. Thus the code has become pretty unmanagable over time. The `refactor` branch tries repay this technical debt by breaking things down into smaller class and implement better tests. Stay tuned! :D
21
+
22
+ Upgrading from mongoid-history-0.1.x to >= 0.2
23
+ ------------------------------------------------
24
+
25
+ If you are upgrading from 0.1.x to version 0.2.x and have existing data, run the following code **before** you start using 0.2.x. This is due to changes in `Mongoid::History::Tracker`'s `association_chain` field.
26
+
27
+ ```ruby
28
+ Mongoid::History.tracker_class.all.each do |tracker|
29
+ tracker.association_chain[1..-1].each do |node|
30
+ node['name'] = node['name'].tableize
31
+ end
32
+ tracker.save!
33
+ end
34
+ ```
35
+
36
+ Install
37
+ -------
38
+
39
+ This gem supports Ruby 1.8.7, 1.9.2, 1.9.3, JRuby, Rubinius and REE. Add it to your `Gemfile` or run `gem install mongoid-history`.
40
+
41
+ ```ruby
42
+ gem 'mongoid-history'
43
+ ```
44
+
45
+ Usage
46
+ -----
47
+
48
+ Here is a quick example on how to use this plugin.
49
+
50
+ **Create a history tracker**
51
+
52
+ Create a new class to track histories. All histories are stored in this tracker. The name of the class can be anything you like. The only requirement is that it includes `Mongoid::History::Tracker`
53
+
54
+ ```ruby
55
+ # app/models/history_tracker.rb
56
+ class HistoryTracker
57
+ include Mongoid::History::Tracker
58
+ end
59
+ ```
60
+
61
+ **Set tracker class name**
62
+
63
+ You should manually set the tracker class name to make sure your tracker can be found and loaded properly. You can skip this step if you manually require your tracker before using any trackables.
64
+
65
+ The following example sets the tracker class name using a Rails initializer.
66
+
67
+ ```ruby
68
+ # config/initializers/mongoid-history.rb
69
+ # initializer for mongoid-history
70
+ # assuming HistoryTracker is your tracker class
71
+ Mongoid::History.tracker_class_name = :history_tracker
72
+ ```
73
+
74
+ **Set `#current_user` method name**
75
+
76
+ You can set the name of the method that returns currently logged in user if you don't want to set `modifier` explicitly on every update.
77
+
78
+ The following example sets the `current_user_method` using a Rails initializer
79
+
80
+ ```ruby
81
+ # config/initializers/mongoid-history.rb
82
+ # initializer for mongoid-history
83
+ # assuming you're using devise/authlogic
84
+ Mongoid::History.current_user_method = :current_user
85
+ ```
86
+
87
+ When `current_user_method` is set, mongoid-history will invoke this method on each update and set its result as the instance modifier.
88
+
89
+ ```ruby
90
+ # assume that current_user return #<User _id: 1>
91
+ post = Post.first
92
+ post.update_attributes(:title => 'New title')
93
+
94
+ post.history_tracks.last.modifier #=> #<User _id: 1>
95
+ ```
96
+
97
+ **Create trackable classes and objects**
98
+
99
+ ```ruby
100
+ class Post
101
+ include Mongoid::Document
102
+ include Mongoid::Timestamps
103
+
104
+ # history tracking all Post documents
105
+ # note: tracking will not work until #track_history is invoked
106
+ include Mongoid::History::Trackable
107
+
108
+ field :title
109
+ field :body
110
+ field :rating
111
+ embeds_many :comments
112
+
113
+ # telling Mongoid::History how you want to track changes
114
+ track_history :on => [:title, :body], # track title and body fields only, default is :all
115
+ :modifier_field => :modifier, # adds "referenced_in :modifier" to track who made the change, default is :modifier
116
+ :version_field => :version, # adds "field :version, :type => Integer" to track current version, default is :version
117
+ :track_create => false, # track document creation, default is false
118
+ :track_update => true, # track document updates, default is true
119
+ :track_destroy => false, # track document destruction, default is false
120
+ end
121
+
122
+ class Comment
123
+ include Mongoid::Document
124
+ include Mongoid::Timestamps
125
+
126
+ # declare that we want to track comments
127
+ include Mongoid::History::Trackable
128
+
129
+ field :title
130
+ field :body
131
+ embedded_in :post, :inverse_of => :comments
132
+
133
+ # track title and body for all comments, scope it to post (the parent)
134
+ # also track creation and destruction
135
+ track_history :on => [:title, :body], :scope => :post, :track_create => true, :track_destroy => true
136
+ end
137
+
138
+ # the modifier class
139
+ class User
140
+ include Mongoid::Document
141
+ include Mongoid::Timestamps
142
+
143
+ field :name
144
+ end
145
+
146
+ user = User.create(:name => "Aaron")
147
+ post = Post.create(:title => "Test", :body => "Post", :modifier => user)
148
+ comment = post.comments.create(:title => "test", :body => "comment", :modifier => user)
149
+ comment.history_tracks.count # should be 1
150
+
151
+ comment.update_attributes(:title => "Test 2")
152
+ comment.history_tracks.count # should be 2
153
+
154
+ track = comment.history_tracks.last
155
+
156
+ track.undo! user # comment title should be "Test"
157
+
158
+ track.redo! user # comment title should be "Test 2"
159
+
160
+ # undo last change
161
+ comment.undo! user
162
+
163
+ # undo versions 1 - 4
164
+ comment.undo! user, :from => 4, :to => 1
165
+
166
+ # undo last 3 versions
167
+ comment.undo! user, :last => 3
168
+
169
+ # redo versions 1 - 4
170
+ comment.redo! user, :from => 1, :to => 4
171
+
172
+ # redo last 3 versions
173
+ comment.redo! user, :last => 3
174
+
175
+ # delete post
176
+ post.destroy
177
+
178
+ # undelete post
179
+ post.undo! user
180
+
181
+ # disable tracking for comments within a block
182
+ Comment.disable_tracking do
183
+ comment.update_attributes(:title => "Test 3")
184
+ end
185
+ ```
186
+ For more examples, check out [spec/integration/integration_spec.rb](https://github.com/aq1018/mongoid-history/blob/master/spec/integration/integration_spec.rb).
187
+
188
+ Contributing to mongoid-history
189
+ -------------------------------
190
+
191
+ * Check out the latest code to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
192
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
193
+ * Fork the project.
194
+ * Create a feature/bugfix branch.
195
+ * Commit and push until you are happy with your changes.
196
+ * Make sure to add tests.
197
+ * Update the CHANGELOG for the next release.
198
+ * Try not to mess with the Rakefile or version.
199
+ * Make a pull request.
200
+
201
+ Copyright
202
+ ---------
203
+
204
+ Copyright (c) 2011-2012 Aaron Qian. MIT License.
205
+ See [LICENSE.txt](https://github.com/aq1018/mongoid-history/blob/master/LICENSE.txt) for further details.
206
+
@@ -0,0 +1,37 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "mongoid-history"
16
+ gem.homepage = "http://github.com/aq1018/mongoid-history"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{ history tracking, auditing, undo, redo for mongoid}
19
+ gem.description = %Q{In frustration of Mongoid::Versioning, I created this plugin for tracking historical changes for any document, including embedded ones. It achieves this by storing all history tracks in a single collection that you define. (See Usage for more details) Embedded documents are referenced by storing an association path, which is an array of document_name and document_id fields starting from the top most parent document and down to the embedded document that should track history.
20
+
21
+ This plugin implements multi-user undo, which allows users to undo any history change in any order. Undoing a document also creates a new history track. This is great for auditing and preventing vandalism, but it is probably not suitable for use cases such as a wiki.}
22
+ gem.email = ["aq1018@gmail.com", "justin.mgrimes@gmail.com"]
23
+ gem.authors = ["Aaron Qian", "Justin Grimes"]
24
+ end
25
+ Jeweler::RubygemsDotOrgTasks.new
26
+
27
+ require 'rspec/core'
28
+ require 'rspec/core/rake_task'
29
+ RSpec::Core::RakeTask.new(:spec) do |spec|
30
+ spec.pattern = FileList['spec/**/*_spec.rb']
31
+ spec.rspec_opts = "--color --format progress"
32
+ end
33
+
34
+ task :default => :spec
35
+
36
+ require 'yard'
37
+ YARD::Rake::YardocTask.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.3
@@ -0,0 +1,10 @@
1
+ require 'easy_diff'
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/mongoid/history')
4
+ require File.expand_path(File.dirname(__FILE__) + '/mongoid/history/tracker')
5
+ require File.expand_path(File.dirname(__FILE__) + '/mongoid/history/trackable')
6
+ require File.expand_path(File.dirname(__FILE__) + '/mongoid/history/sweeper')
7
+
8
+ Mongoid::History.modifier_class_name = "User"
9
+ Mongoid::History.trackable_class_options = {}
10
+ Mongoid::History.current_user_method ||= :current_user
@@ -0,0 +1,13 @@
1
+ module Mongoid
2
+ module History
3
+ mattr_accessor :tracker_class_name
4
+ mattr_accessor :trackable_class_options
5
+ mattr_accessor :modifier_class_name
6
+ mattr_accessor :current_user_method
7
+
8
+ def self.tracker_class
9
+ @tracker_class ||= tracker_class_name.to_s.classify.constantize
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,44 @@
1
+ module Mongoid::History
2
+ class Sweeper < Mongoid::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::History.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
+ modifier_field = track.trackable.history_trackable_options[:modifier_field]
34
+ modifier = track.trackable.send modifier_field
35
+ track.modifier = current_user unless modifier
36
+ end
37
+
38
+ def current_user
39
+ if controller.respond_to?(Mongoid::History.current_user_method, true)
40
+ controller.send Mongoid::History.current_user_method
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,381 @@
1
+ module Mongoid::History
2
+ module Trackable
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def track_history(options={})
7
+ scope_name = self.collection_name.to_s.singularize.to_sym
8
+ default_options = {
9
+ :on => :all,
10
+ :except => [:created_at, :updated_at],
11
+ :except_but_remember => [],
12
+ :modifier_field => :modifier,
13
+ :version_field => :version,
14
+ :scope => scope_name,
15
+ :remember_attachment => true,
16
+ :track_create => false,
17
+ :track_update => true,
18
+ :track_destroy => false,
19
+ }
20
+
21
+ options = default_options.merge(options)
22
+
23
+ # normalize except fields
24
+ # manually ensure _id, id, version will not be tracked in history
25
+ options[:except] = [options[:except]] unless options[:except].is_a? Array
26
+ options[:except] << options[:version_field]
27
+ options[:except] << "#{options[:modifier_field]}_id".to_sym
28
+ options[:except] += [:_id, :id, :updated_at]
29
+ options[:except] = options[:except].map(&:to_s).flatten.compact.uniq
30
+ options[:except].map(&:to_s)
31
+
32
+ # normalize except_but_remember fields
33
+ options[:except_but_remember] = [options[:except_but_remember]] unless options[:except_but_remember].is_a? Array
34
+ options[:except_but_remember] = options[:except_but_remember].map(&:to_s).flatten.compact.uniq
35
+ options[:except_but_remember].map(&:to_s)
36
+
37
+ # normalize fields to track to either :all or an array of strings
38
+ if options[:on] != :all
39
+ options[:on] = [options[:on]] unless options[:on].is_a? Array
40
+ options[:on] = options[:on].map(&:to_s).flatten.uniq
41
+ end
42
+
43
+ field options[:version_field].to_sym, :type => Integer
44
+ belongs_to options[:modifier_field].to_sym, :class_name => Mongoid::History.modifier_class_name
45
+
46
+ include MyInstanceMethods
47
+ extend SingletonMethods
48
+
49
+ delegate :history_trackable_options, :to => 'self.class'
50
+ delegate :track_history?, :to => 'self.class'
51
+
52
+ before_update :track_update if options[:track_update]
53
+ before_create :track_create if options[:track_create]
54
+ before_destroy :track_destroy if options[:track_destroy]
55
+
56
+ Mongoid::History.trackable_class_options ||= {}
57
+ Mongoid::History.trackable_class_options[scope_name] = options
58
+ end
59
+
60
+ def track_history?
61
+ enabled = Thread.current[track_history_flag]
62
+ enabled.nil? ? true : enabled
63
+ end
64
+
65
+ def disable_tracking(&block)
66
+ begin
67
+ Thread.current[track_history_flag] = false
68
+ yield
69
+ ensure
70
+ Thread.current[track_history_flag] = true
71
+ end
72
+ end
73
+
74
+ def track_history_flag
75
+ "mongoid_history_#{self.name.underscore}_trackable_enabled".to_sym
76
+ end
77
+ end
78
+
79
+ module MyInstanceMethods
80
+ def history_tracks
81
+ @history_tracks ||= Mongoid::History.tracker_class.where(:scope => history_trackable_options[:scope], :association_chain => association_hash)
82
+ end
83
+
84
+ # undo :from => 1, :to => 5
85
+ # undo 4
86
+ # undo :last => 10
87
+ def undo!(modifier, options_or_version=nil)
88
+ if (!options_or_version.is_a?(Hash))
89
+ version = get_version(options_or_version.to_i)
90
+ attrs = {}
91
+ fields = history_tracks.first.modified.keys
92
+
93
+ if history_trackable_options[:on] == :all
94
+
95
+ fields.each do |k|
96
+ if !history_trackable_options[:except].include?(k)
97
+ if (!version.attributes[:modified].nil? && version.attributes[:modified].include?(k))
98
+ attrs[k] = version.attributes[:modified][k]
99
+ else
100
+ attrs[k] = version.attributes[k]
101
+ end
102
+ end
103
+ end
104
+
105
+ else
106
+
107
+ fields.each do |k|
108
+ if history_trackable_options[:on].include?(k)
109
+
110
+ if (!version.attributes[:modified].nil? && version.attributes[:modified].include?(k))
111
+ attrs[k] = version.attributes[:modified][k]
112
+ else
113
+ attrs[k] = version.attributes[k]
114
+ end
115
+
116
+ end
117
+ end
118
+
119
+ end
120
+
121
+ if history_trackable_options[:remember_attachment]
122
+
123
+ addr = [Rails.root, '/public/', 'system/', 'store/', attachment_definitions.first.first.to_s, '/', id.to_s, '/', version.data_updated_at.to_i.to_s, '/'].join()
124
+ Dir.chdir(addr)
125
+ file_name = Dir.glob('*.*').first
126
+
127
+ attach = File.new(addr + file_name)
128
+ data = attach
129
+
130
+ attrs["data_file_name"] = file_name
131
+
132
+ end
133
+
134
+ self.update_attributes(attrs)
135
+
136
+ else
137
+
138
+ versions = get_versions_criteria(options_or_version).to_a
139
+ versions.sort!{|v1, v2| v2.version <=> v1.version}
140
+
141
+ versions.each do |v|
142
+ undo_attr = v.undo_attr(modifier)
143
+ self.attributes = v.undo_attr(modifier)
144
+ end
145
+ end
146
+
147
+ save!
148
+ end
149
+
150
+ def redo!(modifier, options_or_version=nil)
151
+ versions = get_versions_criteria(options_or_version).to_a
152
+ versions.sort!{|v1, v2| v1.version <=> v2.version}
153
+
154
+ versions.each do |v|
155
+ redo_attr = v.redo_attr(modifier)
156
+ self.attributes = redo_attr
157
+ end
158
+ save!
159
+ end
160
+
161
+ def get_version(version)
162
+ track = history_tracks.first
163
+
164
+ track.modified.keys.each do |key|
165
+ track[key] = track.modified[key]
166
+ end
167
+
168
+ version -= 1
169
+
170
+ if (version < 0)
171
+ return nil
172
+ end
173
+
174
+ if (version >= history_tracks.count)
175
+ version = history_tracks.count-1
176
+ end
177
+
178
+ for i in 1..version
179
+
180
+ history_tracks[i].modified.keys.each do |key|
181
+ track[key] = history_tracks[i].modified[key]
182
+ end
183
+
184
+ track.modified = history_tracks[i].modified
185
+ track.original = history_tracks[i].original
186
+
187
+ end
188
+
189
+ track.version = version
190
+ track
191
+ end
192
+
193
+ private
194
+
195
+ def get_versions_criteria(options_or_version)
196
+ if options_or_version.is_a? Hash
197
+ options = options_or_version
198
+ if options[:from] && options[:to]
199
+ lower = options[:from] >= options[:to] ? options[:to] : options[:from]
200
+ upper = options[:from] < options[:to] ? options[:to] : options[:from]
201
+ versions = history_tracks.where( :version.in => (lower .. upper).to_a )
202
+ elsif options[:last]
203
+ versions = history_tracks.limit( options[:last] )
204
+ else
205
+ raise "Invalid options, please specify (:from / :to) keys or :last key."
206
+ end
207
+ else
208
+ options_or_version = options_or_version.to_a if options_or_version.is_a?(Range)
209
+ version_field_name = history_trackable_options[:version_field]
210
+ version = options_or_version || self.attributes[version_field_name] || self.attributes[version_field_name.to_s]
211
+ version = [ version ].flatten
212
+ versions = history_tracks.where(:version.in => version)
213
+ end
214
+ versions.desc(:version)
215
+ end
216
+
217
+ def remember_only?(attributes)
218
+
219
+ return false if history_trackable_options[:except_but_remember].nil? || history_trackable_options[:except_but_remember].blank?
220
+
221
+ attributes.each do |k, v|
222
+ return false if !history_trackable_options[:except_but_remember].include?(k.to_s)
223
+ end
224
+
225
+ true
226
+ end
227
+
228
+ def fill_remember_only_attributes(for_update)
229
+
230
+ history_trackable_options[:except_but_remember].each do |key|
231
+
232
+ if (!for_update.include?(key))
233
+ for_update[key] = self[key]
234
+ end
235
+
236
+ end
237
+
238
+ end
239
+
240
+ def should_track_update?
241
+ for_update = modified_attributes_for_update
242
+
243
+ return false if (remember_only?(for_update))
244
+
245
+ track_history? && !for_update.blank?
246
+ end
247
+
248
+ def traverse_association_chain(node=self)
249
+ list = node._parent ? traverse_association_chain(node._parent) : []
250
+ list << association_hash(node)
251
+ list
252
+ end
253
+
254
+ def association_hash(node=self)
255
+
256
+ # We prefer to look up associations through the parent record because
257
+ # we're assured, through the object creation, it'll exist. Whereas we're not guarenteed
258
+ # the child to parent (embedded_in, belongs_to) relation will be defined
259
+ if node._parent
260
+ meta = _parent.relations.values.select do |relation|
261
+ relation.class_name == node.class.to_s
262
+ end.first
263
+ end
264
+
265
+ # if root node has no meta, and should use class name instead
266
+ name = meta ? meta.key.to_s : node.class.name
267
+
268
+ { 'name' => name, 'id' => node.id}
269
+ end
270
+
271
+ def modified_attributes_for_update
272
+ @modified_attributes_for_update = changes
273
+
274
+ if history_trackable_options[:on] == :all
275
+
276
+ changes.each do |k, v|
277
+ @modified_attributes_for_update.delete(k) if history_trackable_options[:except].include?(k)
278
+ end
279
+
280
+ else
281
+
282
+ changes.each do |k, v|
283
+ @modified_attributes_for_update.delete(k) if !history_trackable_options[:on].include?(k)
284
+ end
285
+
286
+ end
287
+
288
+ @modified_attributes_for_update
289
+ end
290
+
291
+ def modified_attributes_for_create
292
+ @modified_attributes_for_create ||= attributes.inject({}) do |h, pair|
293
+ k,v = pair
294
+ h[k] = [nil, v]
295
+ h
296
+ end.reject do |k, v|
297
+ history_trackable_options[:except].include?(k)
298
+ end
299
+ end
300
+
301
+ def modified_attributes_for_destroy
302
+ @modified_attributes_for_destroy ||= attributes.inject({}) do |h, pair|
303
+ k,v = pair
304
+ h[k] = [nil, v]
305
+ h
306
+ end
307
+ end
308
+
309
+ def history_tracker_attributes(method)
310
+ return @history_tracker_attributes if @history_tracker_attributes
311
+
312
+ @history_tracker_attributes = {
313
+ :association_chain => traverse_association_chain,
314
+ :scope => history_trackable_options[:scope],
315
+ :modifier => send(history_trackable_options[:modifier_field])
316
+ }
317
+
318
+ original, modified = transform_changes(case method
319
+ when :destroy then modified_attributes_for_destroy
320
+ when :create then modified_attributes_for_create
321
+ else modified_attributes_for_update
322
+ end)
323
+
324
+ fill_remember_only_attributes(modified)
325
+
326
+ @history_tracker_attributes[:original] = original
327
+ @history_tracker_attributes[:modified] = modified
328
+ @history_tracker_attributes
329
+ end
330
+
331
+ def track_update
332
+ return unless should_track_update?
333
+ current_version = (self.send(history_trackable_options[:version_field]) || 0 ) + 1
334
+ self.send("#{history_trackable_options[:version_field]}=", current_version)
335
+ Mongoid::History.tracker_class.create!(history_tracker_attributes(:update).merge(:version => current_version, :action => "update", :trackable => self))
336
+ clear_memoization
337
+ end
338
+
339
+ def track_create
340
+ return unless track_history?
341
+ current_version = (self.send(history_trackable_options[:version_field]) || 0 ) + 1
342
+ self.send("#{history_trackable_options[:version_field]}=", current_version)
343
+ Mongoid::History.tracker_class.create!(history_tracker_attributes(:create).merge(:version => current_version, :action => "create", :trackable => self))
344
+ clear_memoization
345
+ end
346
+
347
+ def track_destroy
348
+ return unless track_history?
349
+ current_version = (self.send(history_trackable_options[:version_field]) || 0 ) + 1
350
+ Mongoid::History.tracker_class.create!(history_tracker_attributes(:destroy).merge(:version => current_version, :action => "destroy", :trackable => self))
351
+ clear_memoization
352
+ end
353
+
354
+ def clear_memoization
355
+ @history_tracker_attributes = nil
356
+ @modified_attributes_for_create = nil
357
+ @modified_attributes_for_update = nil
358
+ @history_tracks = nil
359
+ end
360
+
361
+ def transform_changes(changes)
362
+ original = {}
363
+ modified = {}
364
+ changes.each_pair do |k, v|
365
+ o, m = v
366
+ original[k] = o unless o.nil?
367
+ modified[k] = m #unless m.nil?
368
+ end
369
+
370
+ return original.easy_diff modified
371
+ end
372
+
373
+ end
374
+
375
+ module SingletonMethods
376
+ def history_trackable_options
377
+ @history_trackable_options ||= Mongoid::History.trackable_class_options[self.collection_name.to_s.singularize.to_sym]
378
+ end
379
+ end
380
+ end
381
+ end