module PaperTrailAssociationTracking::Reifiers::HasMany

Reify a single, direct (not `through`) `has_many` association of `model`. @api private

Public Class Methods

prepare_array(array, options, versions) click to toggle source

Replaces each record in `array` with its reified version, if present in `versions`.

@api private @param array - The collection to be modified. @param options @param versions - A `Hash` mapping IDs to `Version`s @return nil - Always returns `nil`

Once modified by this method, `array` will be assigned to the AR association currently being reified.

# File lib/paper_trail_association_tracking/reifiers/has_many.rb, line 35
def prepare_array(array, options, versions)
  # Iterate each child to replace it with the previous value if there is
  # a version after the timestamp.
  array.map! do |record|
    if (version = versions.delete(record.id)).nil?
      record
    elsif version.event == "create"
      options[:mark_for_destruction] ? record.tap(&:mark_for_destruction) : nil
    else
      version.reify(
        options.merge(
          has_many: false,
          has_one: false,
          belongs_to: false,
          has_and_belongs_to_many: false
        )
      )
    end
  end

  # Reify the rest of the versions and add them to the collection, these
  # versions are for those that have been removed from the live
  # associations.
  array.concat(
    versions.values.map { |v|
      v.reify(
        options.merge(
          has_many: false,
          has_one: false,
          belongs_to: false,
          has_and_belongs_to_many: false
        )
      )
    }
  )

  array.compact!

  nil
end
reify(assoc, model, options, transaction_id, version_table_name) click to toggle source

@api private

# File lib/paper_trail_association_tracking/reifiers/has_many.rb, line 10
def reify(assoc, model, options, transaction_id, version_table_name)
  versions = load_versions_for_hm_association(
    assoc,
    model,
    version_table_name,
    transaction_id,
    options[:version_at]
  )
  collection = Array.new model.send(assoc.name).reload # to avoid cache
  prepare_array(collection, options, versions)
  model.send(assoc.name).proxy_association.target = collection
end
versions_by_id(klass, version_ids) click to toggle source

Given a SQL fragment that identifies the IDs of version records, returns a `Hash` mapping those IDs to `Version`s.

@api private @param klass - An ActiveRecord class. @param version_ids - Array. The IDs of version records. @return A `Hash` mapping IDs to `Version`s

# File lib/paper_trail_association_tracking/reifiers/has_many.rb, line 84
def versions_by_id(klass, version_ids)
  klass.
    paper_trail.version_class.
    where(id: version_ids).
    inject({}) { |a, e| a.merge!(e.item_id => e) }
end

Private Class Methods

load_versions_for_hm_association(assoc, model, version_table, tx_id, version_at) click to toggle source

Given a `has_many` association on `model`, return the version records from the point in time identified by `tx_id` or `version_at`. @api private

# File lib/paper_trail_association_tracking/reifiers/has_many.rb, line 96
def load_versions_for_hm_association(assoc, model, version_table, tx_id, version_at)
  # For STI models, associations may be defined to reference superclasses, so looking up
  # based on only the child-most class is not appropriate.
  sti_model_names =  model.class.ancestors
                          .select { |x| x <= model.class.base_class && x.method_defined?(assoc.name) }
                          .map(&:name)

  version_ids = ::PaperTrail::VersionAssociation.
    joins(model.class.version_association_name).
    select("MIN(version_id) as version_id").
    where("foreign_key_name = ?", assoc.foreign_key).
    where("foreign_key_id = ?", model.id).
    where(foreign_type: sti_model_names + [nil]).
    where("#{version_table}.item_type = ?", assoc.klass.base_class.name).
    where("created_at >= ? OR transaction_id = ?", version_at, tx_id).
    group("item_id").
    map{|e| e.version_id}
  versions_by_id(model.class, version_ids)
end