Back to Repositories

Validating Model Configuration and Version Associations in Paper Trail

This test suite validates the configuration behavior of PaperTrail’s model integration, focusing on the has_paper_trail method and its versioning capabilities. It ensures proper handling of version associations, class naming, and configuration options while maintaining backward compatibility.

Test Coverage Overview

The test suite provides comprehensive coverage of PaperTrail’s ModelConfig functionality:

  • Validates single-call restriction for has_paper_trail
  • Tests version association naming and configuration
  • Verifies custom class name implementations
  • Examines has_many relationship options
  • Checks error handling for invalid configurations

Implementation Analysis

The testing approach utilizes RSpec’s behavior-driven development patterns with dynamic class creation:

Tests employ anonymous class definitions using Class.new(ApplicationRecord) to isolate each test case. The implementation leverages RSpec’s expect syntax and nested describe blocks to organize related test scenarios systematically.

Technical Details

Key technical components include:

  • RSpec test framework
  • ActiveRecord reflection APIs
  • Dynamic class generation
  • Deprecation warning verification
  • Error handling validation

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolated test cases using anonymous classes
  • Comprehensive error scenario coverage
  • Clear test organization using nested contexts
  • Explicit expectation matching
  • Deprecation handling verification

paper-trail-gem/paper_trail

spec/paper_trail/model_config_spec.rb

            
# frozen_string_literal: true

require "spec_helper"

module PaperTrail
  ::RSpec.describe ModelConfig do
    describe "has_paper_trail" do
      it "raises when called multiple times" do
        klass = Class.new(ApplicationRecord) do
          has_paper_trail
        end

        expect do
          klass.has_paper_trail
        end.to raise_error(Error, "has_paper_trail must be called only once")
      end

      describe "versions:" do
        it "name can be passed instead of an options hash", :deprecated do
          allow(PaperTrail.deprecator).to receive(:warn)
          klass = Class.new(ApplicationRecord) do
            has_paper_trail versions: :drafts
          end
          expect(klass.reflect_on_association(:drafts)).to be_a(
            ActiveRecord::Reflection::HasManyReflection
          )
          expect(PaperTrail.deprecator).to have_received(:warn).once.with(
            a_string_starting_with("Passing versions association name"),
            array_including(/#{__FILE__}:/)
          )
        end

        it "name can be passed in the options hash" do
          klass = Class.new(ApplicationRecord) do
            has_paper_trail versions: { name: :drafts }
          end
          expect(klass.reflect_on_association(:drafts)).to be_a(
            ActiveRecord::Reflection::HasManyReflection
          )
        end

        it "class_name can be passed in the options hash" do
          klass = Class.new(ApplicationRecord) do
            has_paper_trail versions: { class_name: "NoObjectVersion" }
          end
          expect(klass.reflect_on_association(:versions).options[:class_name]).to eq(
            "NoObjectVersion"
          )
        end

        it "allows any option that has_many supports" do
          klass = Class.new(ApplicationRecord) do
            has_paper_trail versions: { autosave: true, validate: true }
          end
          expect(klass.reflect_on_association(:versions).options[:autosave]).to eq true
          expect(klass.reflect_on_association(:versions).options[:validate]).to eq true
        end

        it "can even override options that PaperTrail adds to has_many" do
          klass = Class.new(ApplicationRecord) do
            has_paper_trail versions: { as: :foo }
          end
          expect(klass.reflect_on_association(:versions).options[:as]).to eq :foo
        end

        it "raises an error on unknown has_many options" do
          expect {
            Class.new(ApplicationRecord) do
              has_paper_trail versions: { read_my_mind: true, validate: true }
            end
          }.to raise_error(
            /Unknown key: :read_my_mind. Valid keys are: .*:class_name,/
          )
        end

        describe "passing an abstract class to class_name" do
          it "raises an error" do
            expect {
              Class.new(ApplicationRecord) do
                has_paper_trail versions: { class_name: "AbstractVersion" }
              end
            }.to raise_error(
              /use concrete \(not abstract\) version models/
            )
          end
        end
      end

      describe "class_name:" do
        it "can be used instead of versions: {class_name: ...}", :deprecated do
          allow(PaperTrail.deprecator).to receive(:warn)
          klass = Class.new(ApplicationRecord) do
            has_paper_trail class_name: "NoObjectVersion"
          end
          expect(klass.reflect_on_association(:versions).options[:class_name]).to eq(
            "NoObjectVersion"
          )
          expect(PaperTrail.deprecator).to have_received(:warn).once.with(
            a_string_starting_with("Passing Version class name"),
            array_including(/#{__FILE__}:/)
          )
        end
      end
    end
  end
end