module PaperTrail::Reifier

Given a version record and some options, builds a new model object. @api private

Public Class Methods

reify(version, options) click to toggle source

See `VersionConcern#reify` for documentation. @api private

# File lib/mongo_trails/reifier.rb, line 12
def reify(version, options)
  options = apply_defaults_to(options, version)
  attrs = version.object_deserialized
  model = init_model(attrs, options, version)
  reify_attributes(model, version, attrs)
  model.send "#{model.class.version_association_name}=", version
  model
end

Private Class Methods

apply_defaults_to(options, version) click to toggle source

Given a hash of `options` for `.reify`, return a new hash with default values applied. @api private

# File lib/mongo_trails/reifier.rb, line 26
def apply_defaults_to(options, version)
  {
    version_at: version.created_at,
    mark_for_destruction: false,
    has_one: false,
    has_many: false,
    belongs_to: false,
    has_and_belongs_to_many: false,
    unversioned_attributes: :nil
  }.merge(options)
end
init_model(attrs, options, version) click to toggle source

Initialize a model object suitable for reifying `version` into. Does not perform reification, merely instantiates the appropriate model class and, if specified by `options`, sets unversioned attributes to `nil`.

Normally a polymorphic belongs_to relationship allows us to get the object we belong to by calling, in this case, `item`. However this returns nil if `item` has been destroyed, and we need to be able to retrieve destroyed objects.

In this situation we constantize the `item_type` to get hold of the class…except when the stored object's attributes include a `type` key. If this is the case, the object we belong to is using single table inheritance (STI) and the `item_type` will be the base class, not the actual subclass. If `type` is present but empty, the class is the base class.

# File lib/mongo_trails/reifier.rb, line 54
def init_model(attrs, options, version)
  klass = version_reification_class(version, attrs)

  # The `dup` option and destroyed version always returns a new object,
  # otherwise we should attempt to load item or to look for the item
  # outside of default scope(s).
  model = if options[:dup] == true || version.event == "destroy"
            klass.new
          else
            find_cond = { klass.primary_key => version.item_id }

            version.item || klass.unscoped.where(find_cond).first || klass.new
          end

  if options[:unversioned_attributes] == :nil && !model.new_record?
    init_unversioned_attrs(attrs, model)
  end

  model
end
init_unversioned_attrs(attrs, model) click to toggle source

Look for attributes that exist in `model` and not in this version. These attributes should be set to nil. Modifies `attrs`. @api private

# File lib/mongo_trails/reifier.rb, line 78
def init_unversioned_attrs(attrs, model)
  (model.attribute_names - attrs.keys).each { |k| attrs[k] = nil }
end
reify_attribute(k, v, model, version) click to toggle source

Reify onto `model` an attribute named `k` with value `v` from `version`.

`ObjectAttribute#deserialize` will return the mapped enum value and in Rails < 5, the []= uses the integer type caster from the column definition (in general) and thus will turn a (usually) string to 0 instead of the correct value.

@api private

# File lib/mongo_trails/reifier.rb, line 90
def reify_attribute(k, v, model, version)
  if model.has_attribute?(k)
    model[k.to_sym] = v
  elsif model.respond_to?("#{k}=")
    model.send("#{k}=", v)
  elsif version.logger
    version.logger.warn(
      "Attribute #{k} does not exist on #{version.item_type} (Version id: #{version.id})."
    )
  end
end
reify_attributes(model, version, attrs) click to toggle source

Reify onto `model` all the attributes of `version`. @api private

# File lib/mongo_trails/reifier.rb, line 104
def reify_attributes(model, version, attrs)
  AttributeSerializers::ObjectAttribute.new(model.class).deserialize(attrs)
  attrs.each do |k, v|
    reify_attribute(k, v, model, version)
  end
end
version_reification_class(version, attrs) click to toggle source

Given a `version`, return the class to reify. This method supports Single Table Inheritance (STI) with custom inheritance columns.

For example, imagine a `version` whose `item_type` is “Animal”. The `animals` table is an STI table (it has cats and dogs) and it has a custom inheritance column, `species`. If `attrs` is “Dog”, this method returns the constant `Dog`. If `attrs` is blank, this method returns the constant `Animal`. You can see this particular example in action in `spec/models/animal_spec.rb`.

TODO: Duplication: similar `constantize` in VersionConcern#version_limit

# File lib/mongo_trails/reifier.rb, line 122
def version_reification_class(version, attrs)
  inheritance_column_name = version.item_type.constantize.inheritance_column
  inher_col_value = attrs[inheritance_column_name]
  class_name = inher_col_value.blank? ? version.item_type : inher_col_value
  class_name.constantize
end