module Mongoid::History::Trackable::MyInstanceMethods

Public Instance Methods

_create_relation(name, value) click to toggle source
# File lib/mongoid/history/trackable.rb, line 136
def _create_relation(name, value)
  send("create_#{self.class.relation_alias(name)}!", value)
end
_get_relation(name) click to toggle source
# File lib/mongoid/history/trackable.rb, line 132
def _get_relation(name)
  send(self.class.relation_alias(name))
end
history_tracks() click to toggle source
# File lib/mongoid/history/trackable.rb, line 85
def history_tracks
  @history_tracks ||= self.class.tracker_class.where(
    scope: related_scope,
    association_chain: association_hash
  ).asc(:version)
end
redo!(modifier = nil, options_or_version = nil) click to toggle source
# File lib/mongoid/history/trackable.rb, line 117
def redo!(modifier = nil, options_or_version = nil)
  versions = get_versions_criteria(options_or_version).to_a
  versions.sort! { |v1, v2| v1.version <=> v2.version }

  versions.each do |v|
    redo_attr = v.redo_attr(modifier)
    if Mongoid::Compatibility::Version.mongoid3?
      assign_attributes(redo_attr, without_protection: true)
      save!
    else
      update_attributes!(redo_attr)
    end
  end
end
undo(modifier = nil, options_or_version = nil) click to toggle source

undo :from => 1, :to => 5 undo 4 undo :last => 10

# File lib/mongoid/history/trackable.rb, line 95
def undo(modifier = nil, options_or_version = nil)
  versions = get_versions_criteria(options_or_version).to_a
  versions.sort! { |v1, v2| v2.version <=> v1.version }

  versions.each do |v|
    undo_attr = v.undo_attr(modifier)
    if Mongoid::Compatibility::Version.mongoid3? # update_attributes! not bypassing rails 3 protected attributes
      assign_attributes(undo_attr, without_protection: true)
    else # assign_attributes with 'without_protection' option does not work with rails 4/mongoid 4
      self.attributes = undo_attr
    end
  end
end
undo!(modifier = nil, options_or_version = nil) click to toggle source

undo! :from => 1, :to => 5 undo! 4 undo! :last => 10

# File lib/mongoid/history/trackable.rb, line 112
def undo!(modifier = nil, options_or_version = nil)
  undo(modifier, options_or_version)
  save!
end

Protected Instance Methods

increment_current_version?(action) click to toggle source
# File lib/mongoid/history/trackable.rb, line 328
def increment_current_version?(action)
  action != :destroy && !ancestor_flagged_for_destroy?(_parent)
end
track_history_for_action(action) { || ... } click to toggle source
# File lib/mongoid/history/trackable.rb, line 332
def track_history_for_action(action)
  if track_history_for_action?(action)
    current_version = increment_current_version?(action) ? increment_current_version : next_version
    last_track = self.class.tracker_class.create!(
      history_tracker_attributes(action.to_sym)
      .merge(version: current_version, action: action.to_s, trackable: self)
    )
  end

  clear_trackable_memoization

  begin
    yield
  rescue => e
    if track_history_for_action?(action)
      send("#{history_trackable_options[:version_field]}=", current_version - 1)
      last_track.destroy
    end
    raise e
  end
end
track_history_for_action?(action) click to toggle source
# File lib/mongoid/history/trackable.rb, line 324
def track_history_for_action?(action)
  track_history? && !(action.to_sym == :update && modified_attributes_for_update.blank?)
end

Private Instance Methods

ancestor_flagged_for_destroy?(doc) click to toggle source
# File lib/mongoid/history/trackable.rb, line 142
def ancestor_flagged_for_destroy?(doc)
  doc && (doc.flagged_for_destroy? || ancestor_flagged_for_destroy?(doc._parent))
end
association_hash(node = self) click to toggle source
# File lib/mongoid/history/trackable.rb, line 193
def association_hash(node = self)
  # We prefer to look up associations through the parent record because
  # we're assured, through the object creation, it'll exist. Whereas we're not guaranteed
  # the child to parent (embedded_in, belongs_to) relation will be defined
  if node._parent
    meta = node._parent.relations.values.find do |relation|
      if Mongoid::Compatibility::Version.mongoid3?
        relation.class_name == node.metadata.class_name.to_s && relation.name == node.metadata.name
      elsif Mongoid::Compatibility::Version.mongoid6_or_older?
        relation.class_name == node.relation_metadata.class_name.to_s &&
          relation.name == node.relation_metadata.name
      elsif Mongoid::Compatibility::Version.mongoid7_or_newer?
        relation.class_name == node._association.class_name.to_s &&
          relation.name == node._association.name
      end
    end
  end

  # if root node has no meta, and should use class name instead
  name = meta ? meta.key.to_s : node.class.name

  ActiveSupport::OrderedHash['name', name, 'id', node.id]
end
clear_trackable_memoization() click to toggle source
# File lib/mongoid/history/trackable.rb, line 273
def clear_trackable_memoization
  @history_tracker_attributes = @modified_attributes_for_create = @modified_attributes_for_update = @history_tracks = nil
end
expand_nested_document_key_value(document_key, value) click to toggle source

Handle nested document tracking of changes

@example

expand_nested_document_key('embedded.document.changed_field', 'old'])
#=> { 'embedded' => {'document' => { 'changed_field' => 'old' }}}

@param [String] document_key key with dots @param [?] value

@return [Hash<String, ?>]

# File lib/mongoid/history/trackable.rb, line 306
def expand_nested_document_key_value(document_key, value)
  expanded_key = value
  document_key.to_s.split('.').reverse.each do |key|
    expanded_key = { key => expanded_key }
  end
  expanded_key
end
get_versions_criteria(options_or_version) click to toggle source
# File lib/mongoid/history/trackable.rb, line 146
def get_versions_criteria(options_or_version)
  if options_or_version.is_a? Hash
    options = options_or_version
    if options[:from] && options[:to]
      lower = options[:from] >= options[:to] ? options[:to] : options[:from]
      upper = options[:from] < options[:to] ? options[:to] : options[:from]
      versions = history_tracks.where(:version.in => (lower..upper).to_a)
    elsif options[:last]
      versions = history_tracks.limit(options[:last])
    else
      raise 'Invalid options, please specify (:from / :to) keys or :last key.'
    end
  else
    options_or_version = options_or_version.to_a if options_or_version.is_a?(Range)
    version_field_name = history_trackable_options[:version_field]
    version = options_or_version || attributes[version_field_name] || attributes[version_field_name.to_s]
    version = [version].flatten
    versions = history_tracks.where(:version.in => version)
  end
  versions.desc(:version)
end
history_tracker_attributes(action) click to toggle source
# File lib/mongoid/history/trackable.rb, line 244
def history_tracker_attributes(action)
  return @history_tracker_attributes if @history_tracker_attributes

  modifier_field = history_trackable_options[:modifier_field]
  @history_tracker_attributes = {
    association_chain: traverse_association_chain,
    scope: related_scope
  }
  @history_tracker_attributes[:modifier] = send(modifier_field) if modifier_field

  original, modified = transform_changes(modified_attributes_for_action(action))

  @history_tracker_attributes[:original] = original
  @history_tracker_attributes[:modified] = modified
  @history_tracker_attributes
end
increment_current_version() click to toggle source
# File lib/mongoid/history/trackable.rb, line 318
def increment_current_version
  next_version.tap { |version| send("#{history_trackable_options[:version_field]}=", version) }
end
modified_attributes_for_action(action) click to toggle source

Returns a Hash of field name to pairs of original and modified values for each tracked field for a given action.

@param [ String | Symbol ] action The modification action (:create, :update, :destroy)

@return [ Hash<String, Array<Object>> ] the pairs of original and modified

values for each field
# File lib/mongoid/history/trackable.rb, line 224
def modified_attributes_for_action(action)
  case action.to_sym
  when :destroy then modified_attributes_for_destroy
  when :create then modified_attributes_for_create
  else modified_attributes_for_update
  end
end
modified_attributes_for_create() click to toggle source
# File lib/mongoid/history/trackable.rb, line 236
def modified_attributes_for_create
  @modified_attributes_for_create ||= Mongoid::History::Attributes::Create.new(self).attributes
end
modified_attributes_for_destroy() click to toggle source
# File lib/mongoid/history/trackable.rb, line 240
def modified_attributes_for_destroy
  @modified_attributes_for_destroy ||= Mongoid::History::Attributes::Destroy.new(self).attributes
end
modified_attributes_for_update() click to toggle source
# File lib/mongoid/history/trackable.rb, line 232
def modified_attributes_for_update
  @modified_attributes_for_update ||= Mongoid::History::Attributes::Update.new(self).attributes
end
next_version() click to toggle source
# File lib/mongoid/history/trackable.rb, line 314
def next_version
  (send(history_trackable_options[:version_field]) || 0) + 1
end
track_create(&block) click to toggle source
# File lib/mongoid/history/trackable.rb, line 261
def track_create(&block)
  track_history_for_action(:create, &block)
end
track_destroy(&block) click to toggle source
# File lib/mongoid/history/trackable.rb, line 269
def track_destroy(&block)
  track_history_for_action(:destroy, &block) unless destroyed?
end
track_update(&block) click to toggle source
# File lib/mongoid/history/trackable.rb, line 265
def track_update(&block)
  track_history_for_action(:update, &block)
end
transform_changes(changes) click to toggle source

Transform hash of pair of changes into an `original` and `modified` hash Nested document keys (key name with dots) are expanded

@param [Hash<Array>] changes

@return [Array<Hash<?>,Hash<?>>] <description>

# File lib/mongoid/history/trackable.rb, line 283
def transform_changes(changes)
  original = {}
  modified = {}
  changes.each_pair do |k, modification_pair|
    o, m = modification_pair
    original.deep_merge!(expand_nested_document_key_value(k, o)) unless o.nil?
    modified.deep_merge!(expand_nested_document_key_value(k, m)) unless m.nil?
  end

  [original, modified]
end
traverse_association_chain(node = self) click to toggle source
# File lib/mongoid/history/trackable.rb, line 189
def traverse_association_chain(node = self)
  (node._parent ? traverse_association_chain(node._parent) : []).tap { |list| list << association_hash(node) }
end