Skip to content

Records index: replace granular Format filter with Record Type (with later format sub-facet) #370

Description

@holden

Problem

The Format facet on /records currently exposes the raw record_formats.name field. This produces ~27+ visible options on the sidebar — many of which are noisy near-duplicates of each other caused by data entry drift.

Examples from production data today:

  • Singles: 7-inch (13,242) — and separately Singles: 7-Inch (1), Singles: 7-inch. (1), Singles: 7-rpm (1) — all the same physical thing
  • LPs: 10/12-inch (4,324) and LPs 10/12-inch (1) — same thing, missing colon
  • Gold Standard Singles with "447" prefix (44) and Gold Standard Singles with '447' prefix (separate row, only the quote char differs)
  • 22 record_formats are not assigned to any record_type_id (covering 4 records)

This makes the facet effectively useless for filtering — users can't tell which entries are real and which are typos, and the long tail dilutes the signal of the top buckets.

Data model recap

We have two tables:

  • record_formats — 57 distinct values (granular, noisy). records.record_format_id points here.
  • record_types — 5 clean values (broad, parent). record_formats.record_type_id points here.

So record_type is already modeled as the parent of record_format. We just don't surface it on the index page.

Counts across the active collection (user 1, 20,380 records) when grouped by record_type:

Type Records
45s 14,710
LPs 4,536
Picture Sleeves 1,007
78s 123
Sound postcard 0

That's 99.98% coverage in 4 buckets vs 27+ confusing rows today.

Proposal

Two-phase rollout. Phase 1 is a quick UX win; Phase 2 unlocks deeper filtering once the data is cleaned.

Phase 1 — Replace Format facet with Type facet (this PR)

  • Add has_one :record_type, through: :record_format on Record
  • Replace the @formats query in RecordsController#load_filter_options with @record_types (join through record_formats, group by record_types.name, hide buckets with count = 0)
  • Update the sidebar partial to render Type instead of Format
  • Switch the Ransack predicate from record_format_id_in to filter by record_format_id_in derived from the selected Type (or via a new record_type_id ransacker)
  • Verify existing sort options keyed on record_format_* still work (the FK stays, only the facet changes)

Phase 2 — Add Format as cascading sub-facet (separate issue/PR)

After data normalization, when a Type is selected, expose its child Formats as a second filter group:

  • Backfill the 22 orphan formats with the correct record_type_id
  • Merge typo variants into canonical rows (Singles: 7-InchSingles: 7-inch, etc.)
  • Drop empty/zero-count formats
  • Render Format checkboxes only when a Type is active

Wireframes

Current — single noisy Format facet

+--------------------------------+
| Format                       ^ |
+--------------------------------+
| [ ] Singles: 7-inch     13,242 |
| [ ] LPs: 10/12-inch      4,324 |
| [ ] Promotional Singles  1,149 |
| [ ] Picture Sleeves      1,002 |
| [ ] EPs: 7-inch            274 |
| [ ] Singles: 10/12-inch    135 |
| [ ] Singles: 78 rpm        121 |
| [ ] Singles: 12-inch        64 |
| [ ] Gold Standard Sing...   44 |
| [ ] Picture Disc Singles    16 |
| [ ] EPs: 7-inch 45 rpm       7 |
| [ ] EPs: 10-inch             6 |
| [ ] Gold Standard Pict...    5 |
| [ ] EPs: 12-inch             3 |
| [ ] Promotional 12-inch...   3 |
| [ ] Singles: 12-inch 33/45   2 |
| [ ] Gold Standard Sing...    2 |
| [ ] Promotional Single...    2 |
| [ ] LPs 10/12-inch           1 |   <-- typo dup of row 2
| [ ] 78 rpm Album             1 |
| [ ] Picture Sleeve           1 |   <-- singular vs plural dup
| [ ] Plastic Soundsheets...   1 |
| [ ] EPs: 7-inch 33/45        1 |
| [ ] Promotional LPs          1 |
| [ ] Albums: 78 rpm           1 |
+--------------------------------+

Phase 1 — Type only (ship now)

+--------------------------------+
| Type                         ^ |
+--------------------------------+
| [ ] 45s                 14,710 |
| [ ] LPs                  4,536 |
| [ ] Picture Sleeves      1,007 |
| [ ] 78s                    123 |
+--------------------------------+

Phase 2 — Type + cascading Format sub-facet (after cleanup)

When a Type is checked, its child formats appear indented below it:

+--------------------------------+
| Type                         ^ |
+--------------------------------+
| [x] 45s                 14,710 |
|     [ ] Singles: 7-inch 13,242 |
|     [ ] Promotional Sgl. 1,149 |
|     [ ] EPs: 7-inch        274 |
|     [ ] Picture Disc Sgl    16 |
|     [ ] Gold Std "447"      44 |
|     [ ] Gold Std "GB"        2 |
| [ ] LPs                  4,536 |
| [ ] Picture Sleeves      1,007 |
| [ ] 78s                    123 |
+--------------------------------+

(Picture Sleeves remains a top-level Type because it's a category in its own right, not a sub-format of 45s.)

Recommendation

Do Phase 1 now, ship it as its own PR. It's a small, low-risk change that immediately makes the facet useful for the 99.98% of records that have a clean type assignment.

Treat Phase 2 as gated on data cleanup. Don't expose the cascading Format sub-facet until the typo dedup + orphan backfill is done, otherwise the sub-list will reproduce the same noise problem at a smaller scale. File a separate cleanup issue for the data work and a follow-up issue for the UI.

Out of scope

  • Changing the schema (we're keeping record_format_id on records)
  • Changing the record detail page (Format stays visible there)
  • Changing admin/edit forms

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions