Skip to content

option to group STI column annotation#1051

Open
dfl wants to merge 4 commits intoctran:developfrom
dfl:group-sti-columns
Open

option to group STI column annotation#1051
dfl wants to merge 4 commits intoctran:developfrom
dfl:group-sti-columns

Conversation

@dfl
Copy link
Copy Markdown

@dfl dfl commented Apr 25, 2026

Motivation

STI models share a single table, so today every model in an STI hierarchy gets an identical annotation listing all columns — including ones that are only relevant to a specific subclass. On a large hierarchy this creates noise on every schema change and makes it hard to tell at a glance which columns a given model actually uses.

The existing --exclude-sti-subclasses option sidesteps this by skipping subclass annotation entirely, but throws away useful context. This option takes the opposite approach: keep annotating all models, but group columns by which class owns them.

Example output

Base class (app/models/vehicle.rb):

# == Schema Information
#
# Table name: vehicles
#
#
# -- Vehicle columns --
#
#  id         :bigint           not null, primary key
#  type       :string           not null
#  name       :string           not null
#  created_at :datetime         not null
#  updated_at :datetime         not null
#
# -- Car columns --
#
#  num_doors   :integer
#  engine_type :string
#
# -- Truck columns --
#
#  payload_capacity :decimal
#  num_axles        :integer
#

Subclass (app/models/car.rb) — Truck columns omitted entirely:

# == Schema Information
#
# Table name: vehicles
#
#
# -- Vehicle columns --
#
#  id         :bigint           not null, primary key
#  type       :string           not null
#  name       :string           not null
#  created_at :datetime         not null
#  updated_at :datetime         not null
#
# -- Car columns --
#
#  num_doors   :integer
#  engine_type :string
#

Summary

  • Add --group-sti-columns CLI option that groups annotation columns by STI class ownership, so base class and subclass annotations show which columns belong where rather than a flat identical list across all models
  • Column ownership is determined by introspecting each model's validators, belongs_to associations, enums, and stored attributes — no configuration required
  • On a subclass, the annotation shows shared (base) columns and subclass-specific columns under separate labeled sections; on the base class, each subclass gets its own section
  • Extract STI logic into Annotate::StiColumns module to keep it cleanly separated from annotation formatting; also reuse it to simplify the existing exclude_sti_subclasses check
  • Use descendants instead of subclasses to support multi-level STI hierarchies (e.g. ElectricCar < Car < Vehicle)
  • Call Rails.application.eager_load! before querying descendants to avoid loading-order issues where the base class is annotated before subclasses are loaded
  • Warn to stderr when the heuristic can't assign any columns to a subclass, pointing users toward adding validations/associations/enums

Notes

  • The grouping is descriptive, not enforced — the DB still allows any STI subclass to read/write any column. The annotation documents what's actually expected/used, which is only misleading if you're intentionally accessing columns across subclass boundaries (already a code smell)
  • The main known limitation: if a subclass uses a column without declaring a validation, association, or enum for it, that column silently lands in the base group. The stderr warning covers this case but can't point to the specific column.
    • Workaround: add a harmless validation like validates :my_column, absence: false to the subclass to explicitly claim it.
  • Columns not claimed by any subclass default to the base class group (first-subclass-wins for contested columns, sorted alphabetically)
  • Option is off by default; existing annotations are unaffected

dfl and others added 4 commits April 8, 2026 19:42
…ship

When STI models share a table, every model gets identical annotations.
This new option groups columns by which class "owns" them, determined
via Rails introspection (validators, associations, enums, stored_attributes).

Extracted STI logic into Annotate::StiColumns for clean separation of
concerns. Also refactored the existing exclude_sti_subclasses check to
use the new module.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tion

- Rename option from --classify-sti-columns to --group-sti-columns to
  avoid confusion with --classified-sort
- Use `descendants` instead of `subclasses` to support multi-level STI
  hierarchies (e.g. ElectricCar < Car < Vehicle)
- Eager-load models via Rails.application.eager_load! to ensure all STI
  siblings are visible before partitioning
- Warn to stderr when the heuristic can't determine column ownership,
  guiding users to add validations/associations/enums
- Add annotation removal test proving STI group headers are cleaned up
- Add multi-level STI test (descendants across 3 levels)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant