module Mongoid::History::Tracker
Public Instance Methods
Similar to tracked_changes
, but contains only a single value for each affected field:
- :create and :update return the modified values - :destroy returns original values
Included for legacy compatibility.
@deprecated
@return [ HashWithIndifferentAccess ] a change set in the format:
{ field_1: value, field_2: value }
# File lib/mongoid/history/tracker.rb, line 148 def affected target = action.to_sym == :destroy ? :from : :to @affected ||= tracked_changes.inject(HashWithIndifferentAccess.new) do |h, (k, v)| h[k] = v[target] h end end
# File lib/mongoid/history/tracker.rb, line 41 def redo!(modifier = nil) if action.to_sym == :destroy re_destroy elsif action.to_sym == :create re_create elsif Mongoid::Compatibility::Version.mongoid3? trackable.update_attributes!(redo_attr(modifier), without_protection: true) else trackable.update_attributes!(redo_attr(modifier)) end end
# File lib/mongoid/history/tracker.rb, line 64 def redo_attr(modifier) redo_hash = affected.easy_unmerge(original) redo_hash.easy_merge!(modified) modifier_field = trackable.history_trackable_options[:modifier_field] redo_hash[modifier_field] = modifier if modifier_field localize_keys(redo_hash) end
# File lib/mongoid/history/tracker.rb, line 76 def trackable @trackable ||= trackable_parents_and_trackable.last end
# File lib/mongoid/history/tracker.rb, line 84 def trackable_parent @trackable_parent ||= trackable_parents_and_trackable[-2] end
Returns the class of the trackable, irrespective of whether the trackable object has been destroyed.
@return [ Class ] the class of the trackable
# File lib/mongoid/history/tracker.rb, line 160 def trackable_parent_class association_chain.first['name'].constantize end
# File lib/mongoid/history/tracker.rb, line 80 def trackable_parents @trackable_parents ||= trackable_parents_and_trackable[0, -1] end
# File lib/mongoid/history/tracker.rb, line 72 def trackable_root @trackable_root ||= trackable_parents_and_trackable.first end
Outputs a :from, :to hash for each affected field. Intentionally excludes fields which are not tracked, even if there are tracked values for such fields present in the database.
@return [ HashWithIndifferentAccess ] a change set in the format:
{ field_1: {to: new_val}, field_2: {from: old_val, to: new_val} }
# File lib/mongoid/history/tracker.rb, line 94 def tracked_changes @tracked_changes ||= (modified.keys | original.keys).inject(HashWithIndifferentAccess.new) do |h, k| h[k] = { from: original[k], to: modified[k] }.delete_if { |_, vv| vv.nil? } h end.delete_if { |k, v| v.blank? || !trackable_parent_class.tracked?(k) } end
Outputs summary of edit actions performed: :add, :modify, :remove, or :array. Does deep comparison of arrays. Useful for creating human-readable representations of the history tracker. Considers changing a value to 'blank' to be a removal.
@return [ HashWithIndifferentAccess ] a change set in the format:
{ add: { field_1: new_val, ... }, modify: { field_2: {from: old_val, to: new_val}, ... }, remove: { field_3: old_val }, array: { field_4: {add: ['foo', 'bar'], remove: ['baz']} } }
# File lib/mongoid/history/tracker.rb, line 110 def tracked_edits return @tracked_edits if @tracked_edits @tracked_edits = HashWithIndifferentAccess.new tracked_changes.each do |k, v| next if v[:from].blank? && v[:to].blank? if trackable_parent_class.tracked_embeds_many?(k) prepare_tracked_edits_for_embeds_many(k, v) elsif v[:from].blank? @tracked_edits[:add] ||= {} @tracked_edits[:add][k] = v[:to] elsif v[:to].blank? @tracked_edits[:remove] ||= {} @tracked_edits[:remove][k] = v[:from] elsif v[:from].is_a?(Array) && v[:to].is_a?(Array) @tracked_edits[:array] ||= {} old_values = v[:from] - v[:to] new_values = v[:to] - v[:from] @tracked_edits[:array][k] = { add: new_values, remove: old_values }.delete_if { |_, vv| vv.blank? } else @tracked_edits[:modify] ||= {} @tracked_edits[:modify][k] = v end end @tracked_edits end
# File lib/mongoid/history/tracker.rb, line 29 def undo!(modifier = nil) if action.to_sym == :destroy re_create elsif action.to_sym == :create re_destroy elsif Mongoid::Compatibility::Version.mongoid3? trackable.update_attributes!(undo_attr(modifier), without_protection: true) else trackable.update_attributes!(undo_attr(modifier)) end end
# File lib/mongoid/history/tracker.rb, line 53 def undo_attr(modifier) undo_hash = affected.easy_unmerge(modified) undo_hash.easy_merge!(original) modifier_field = trackable.history_trackable_options[:modifier_field] undo_hash[modifier_field] = modifier if modifier_field (modified.keys - undo_hash.keys).each do |k| undo_hash[k] = nil end localize_keys(undo_hash) end
Private Instance Methods
# File lib/mongoid/history/tracker.rb, line 180 def create_on_parent name = association_chain.last['name'] if trackable_parent.class.embeds_one?(name) trackable_parent._create_relation(name, localize_keys(original)) elsif trackable_parent.class.embeds_many?(name) trackable_parent._get_relation(name).create!(localize_keys(original)) else raise 'This should never happen. Please report bug!' end end
# File lib/mongoid/history/tracker.rb, line 174 def create_standalone restored = trackable_parent_class.new(localize_keys(original)) restored.id = original['_id'] restored.save! end
# File lib/mongoid/history/tracker.rb, line 223 def localize_keys(hash) klass = association_chain.first['name'].constantize if klass.respond_to?(:localized_fields) klass.localized_fields.keys.each do |name| hash["#{name}_translations"] = hash.delete(name) if hash[name].present? end end hash end
# File lib/mongoid/history/tracker.rb, line 233 def prepare_tracked_edits_for_embeds_many(key, value) @tracked_edits[:embeds_many] ||= {} value[:from] ||= [] value[:to] ||= [] modify_ids = value[:from].map { |vv| vv['_id'] }.compact & value[:to].map { |vv| vv['_id'] }.compact modify_values = modify_ids.map { |id| { from: value[:from].detect { |vv| vv['_id'] == id }, to: value[:to].detect { |vv| vv['_id'] == id } } } modify_values.delete_if { |vv| vv[:from] == vv[:to] } ignore_values = modify_values.map { |vv| [vv[:from], vv[:to]] }.flatten old_values = value[:from] - value[:to] - ignore_values new_values = value[:to] - value[:from] - ignore_values @tracked_edits[:embeds_many][key] = { add: new_values, remove: old_values, modify: modify_values }.delete_if { |_, vv| vv.blank? } end
# File lib/mongoid/history/tracker.rb, line 166 def re_create association_chain.length > 1 ? create_on_parent : create_standalone end
# File lib/mongoid/history/tracker.rb, line 170 def re_destroy trackable.destroy end
# File lib/mongoid/history/tracker.rb, line 192 def trackable_parents_and_trackable @trackable_parents_and_trackable ||= traverse_association_chain end
# File lib/mongoid/history/tracker.rb, line 196 def traverse_association_chain chain = association_chain.dup doc = nil documents = [] loop do node = chain.shift name = node['name'] doc = if doc.nil? # root association. First element of the association chain # unscoped is added to remove any default_scope defined in model klass = name.classify.constantize klass.unscoped.where(_id: node['id']).first elsif doc.class.embeds_one?(name) doc._get_relation(name) elsif doc.class.embeds_many?(name) doc._get_relation(name).unscoped.where(_id: node['id']).first else relation_klass = doc.class.relation_class_of(name) if doc relation_klass ||= 'nil' raise "Unexpected relation for field '#{name}': #{relation_klass}. This should never happen. Please report bug." end documents << doc break if chain.empty? end documents end