Back to Repositories

Testing Single Table Inheritance Versioning in Paper Trail

This test suite validates the Single Table Inheritance (STI) functionality in the PaperTrail gem, focusing on versioning behavior with custom inheritance columns. The tests verify version tracking across different animal types and their inheritance relationships.

Test Coverage Overview

The test suite provides comprehensive coverage of STI functionality in PaperTrail:

  • Baseline versioning configuration validation
  • Custom inheritance column implementation
  • Version tracking across multiple model types
  • Reification behavior for inherited models

Implementation Analysis

The testing approach uses RSpec to validate PaperTrail’s STI support:

Tests implement a hierarchical structure with Animal as the base class and Dog/Cat as subclasses. The suite verifies version creation, model reification, and inheritance column updates using PaperTrail’s versioning system.

Technical Details

Key technical components include:

  • RSpec for test framework
  • Custom STI configuration using ‘species’ column
  • PaperTrail::Version model for version tracking
  • Model callbacks integration testing

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolated test contexts for different scenarios
  • Comprehensive version chain validation
  • Edge case handling for inheritance changes
  • Proper setup and teardown management

paper-trail-gem/paper_trail

spec/models/animal_spec.rb

            
# frozen_string_literal: true

require "spec_helper"

RSpec.describe Animal, type: :model, versioning: true do
  it "baseline test setup" do
    expect(described_class.new).to be_versioned
    expect(described_class.inheritance_column).to eq("species")
  end

  describe "#descends_from_active_record?" do
    it "returns true, meaning that Animal is not an STI subclass" do
      expect(described_class.descends_from_active_record?).to eq(true)
    end
  end

  it "works with custom STI inheritance column" do
    animal = described_class.create(name: "Animal")
    animal.update(name: "Animal from the Muppets")
    animal.update(name: "Animal Muppet")
    animal.destroy
    dog = Dog.create(name: "Snoopy")
    dog.update(name: "Scooby")
    dog.update(name: "Scooby Doo")
    dog.destroy
    cat = Cat.create(name: "Garfield")
    cat.update(name: "Garfield (I hate Mondays)")
    cat.update(name: "Garfield The Cat")
    cat.destroy
    expect(PaperTrail::Version.count).to(eq(12))
    expect(animal.versions.count).to(eq(4))
    expect(animal.versions.first.reify).to(be_nil)
    animal.versions[(1..-1)].each do |v|
      expect(v.reify.class.name).to(eq("Animal"))
    end
    dog_versions = PaperTrail::Version.where(item_id: dog.id).order(:created_at)
    expect(dog_versions.count).to(eq(4))
    expect(dog_versions.first.reify).to(be_nil)
    expect(dog_versions.map { |v| v.reify.class.name }).to eq(%w[NilClass Dog Dog Dog])
    cat_versions = PaperTrail::Version.where(item_id: cat.id).order(:created_at)
    expect(cat_versions.count).to(eq(4))
    expect(cat_versions.first.reify).to(be_nil)
    expect(cat_versions.map { |v| v.reify.class.name }).to eq(%w[NilClass Cat Cat Cat])
  end

  it "allows the inheritance_column (species) to be updated" do
    cat = Cat.create!(name: "Leo")
    cat.update(name: "Spike", species: "Dog")
    dog = described_class.find(cat.id)
    expect(dog).to be_instance_of(Dog)
  end

  context "with callback-methods" do
    context "when only has_paper_trail set in super class" do
      let(:callback_cat) { Cat.create(name: "Markus") }

      it "trails all events" do
        callback_cat.update(name: "Billie")
        callback_cat.destroy
        expect(callback_cat.versions.collect(&:event)).to eq %w[create update destroy]
      end

      it "does not break reify" do
        callback_cat.destroy
        expect { callback_cat.versions.last.reify }.not_to raise_error
      end
    end
  end
end