vanilla-rails-delegated-types

ZempTime's avatarfrom ZempTime

Use when designing content systems where 5+ distinct types need to comingle in feeds, timelines, or searches with shared metadata and operations - NOT for simple polymorphic associations or 1-3 similar types

⭐0stars🔀0forks📁View on GitHub🕐Updated Dec 26, 2025

When & Why to Use This Skill

This Claude skill provides expert guidance on implementing the 'Delegated Types' (Recording/Recordable) pattern in Ruby on Rails. It helps developers architect scalable content systems where five or more distinct models need to coexist in unified feeds or timelines. By centralizing shared metadata and operations into a container model, it eliminates the complexity of polymorphic associations and simplifies cross-type queries, access control, and pagination.

Use Cases

  • Designing complex activity streams or project timelines that aggregate diverse content types like messages, documents, and uploads into a single chronological view.
  • Implementing centralized access control and shared metadata (e.g., ownership, timestamps, project associations) across multiple distinct models to reduce database redundancy.
  • Refactoring legacy polymorphic associations that suffer from performance issues or complex Ruby-side pagination logic when merging multiple data types.
  • Streamlining bulk operations such as moving, archiving, or searching across a wide variety of content entities through a unified interface.
namevanilla-rails-delegated-types
descriptionUse when designing content systems where 5+ distinct types need to comingle in feeds, timelines, or searches with shared metadata and operations - NOT for simple polymorphic associations or 1-3 similar types

Delegated Types (Recording/Recordable Pattern)

A container model (Recording) unifies many content types (Recordables) that share metadata, access control, and operations.

Core insight: When many content types must comingle and share behavior, create a unified container rather than duplicating metadata or using complex polymorphic queries.

When to Use

digraph decision {
    "How many content types?" [shape=diamond];
    "Do they comingle in UI?" [shape=diamond];
    "Share metadata/operations?" [shape=diamond];
    "Direct associations" [shape=box];
    "Delegated types" [shape=box];
    "Consider merging types" [shape=box];

    "How many content types?" -> "Direct associations" [label="1-2"];
    "How many content types?" -> "Do they comingle in UI?" [label="3-4"];
    "How many content types?" -> "Share metadata/operations?" [label="5+"];
    "Do they comingle in UI?" -> "Consider merging types" [label="rarely"];
    "Do they comingle in UI?" -> "Share metadata/operations?" [label="often"];
    "Share metadata/operations?" -> "Direct associations" [label="no"];
    "Share metadata/operations?" -> "Delegated types" [label="yes"];
}

Use when:

  • 5+ content types (Messages, Documents, Uploads, Comments, etc.)
  • Types appear together in feeds, timelines, activity streams
  • Shared metadata: project, creator, timestamps, access control
  • Common operations: copy, move, archive, search across all types
  • Single query needed: "all content in project X sorted by date"

Don't use when:

  • Only 1-3 types (use direct associations)
  • Types are similar enough to merge (see flattening.md)
  • Types don't comingle in UI
  • No shared metadata or operations

Quick Reference

Decision Answer
1-2 content types Direct associations
3-4 types, rarely comingle Consider merging or direct
3-4 types, often comingle Maybe delegated types
5+ types, shared metadata Delegated types
Only 1 type left Flatten (see flattening.md)
Container adds no value Flatten

Difference from Simple Polymorphic

Aspect Simple Polymorphic Delegated Types
Query target Each concrete type Recording (container)
Shared metadata Duplicated in each table Once in Recording
Pagination Complex (merge in Ruby) Natural (query Recording)
Access control Per-type On Recording

Sub-Documents

  • implementing.md - Two-layer pattern (Bucket + Recording), schema, models, querying
  • flattening.md - When to remove delegated types, PR 445 example, signals

Real-World Usage

Basecamp uses this for: Messages, Documents, Uploads, Comments, Todos - unified project timeline with shared access control and operations.

Fizzy removed it: When only Comments remained after merging EventSummary (see flattening.md).