Skip to content

Link stories and workshops as a many-to-many (both sides) - #2587

Draft
maebeale wants to merge 3 commits into
mainfrom
maebeale/stories-workshops-m2m
Draft

maebeale wants to merge 3 commits into
mainfrom
maebeale/stories-workshops-m2m

Conversation

@maebeale

@maebeale maebeale commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

🤖 suggested review level: 5 Inspect 🔬 new join table + model, both edit forms reworked, decorator/importer/sort changes

A story can now reference multiple workshops and vice versa. Each linked to the workshop curriculum or typed in for one that isn't listed — and the relationship is editable from both sides. Facilitators couldn't previously connect a story to more than one workshop, or see a workshop's stories from the curriculum.

We've left in that initial relationship in order to support a data migration to retain existing connections.

What you can do now

  • From a story: add any number of workshops — pick one from the curriculum, or type a free-text name, per row.
  • From a workshop: connect any number of stories (each with an optional "workshop name the story used"), and the workshop page lists the stories that use it.
  • "New story" from a workshop pre-selects that workshop in the form.

How it's modeled

  • Join model StoryWorkshop on table stories_workshops (has_many :through both directions). Each row carries an optional workshop_id and/or an optional external_workshop_title, plus position. A row with both renders "Name (from the Workshop)".
  • Migration is schema-only, no data backfill (per request). The legacy stories.workshop_id / external_workshop_title columns are kept as a safety net; display falls back to them when a story has no join rows yet, and the story edit form shows them read-only ("Previously linked") so a persisted story's pre-join workshop data never silently hides.
  • A unique index on stories_workshops [story_id, workshop_id] backs the model's uniqueness validation (a concurrent double-submit can't slip a duplicate past it). External-title-only rows have a NULL workshop_id, which MySQL exempts, so a story can still carry several unlisted-workshop rows.

Notes for the reviewer

  • The old single-workshop picker is replaced by cocoon nested rows on both forms. Each row's picker is a remote-select typeahead (TomSelect) that searches on demand — workshops and stories are both unbounded, so neither collection is loaded up front. This adds Story to the remote_searchable set + search/:model allowlist and a StoryPolicy#search? (authenticated) rule.
  • Index "workshop" sort orders by a story's alphabetically-first linked workshop (subquery).
  • Naming overlap on the workshop page: the existing "Related stories" tab shows facilitator-spotlight Resources; the new "Stories" tab shows real Story records. Kept both.

Testing

  • Model spec for StoryWorkshop (needs a workshop or a title; no duplicate workshop per story).
  • Request specs: workshop show lists linked stories (Turbo-Frame, incl. nil-association record); "New story" pre-selects; promote copies the idea's workshop + external title.

Follow-up

🤖 Generated with Claude Code

maebeale and others added 3 commits September 30, 2026 16:18
A story can reference multiple workshops (each linked to the curriculum or a
free-text name), and a workshop can connect multiple stories. The workshop page
lists the stories that use it, and 'New story' from a workshop pre-selects it.

The join is a StoryWorkshop model on a stories_workshops table (has_many
:through both ways). The legacy stories.workshop_id / external_workshop_title
columns are kept as a safety copy; display falls back to them when a story has
no join rows yet. Migration is schema-only, no backfill.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Workshops and stories are both unbounded, so loading the full collection
into a native <select> per nested row doesn't scale. Search each on demand
via the existing remote-select (TomSelect) control instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…p read-only

- Add a unique index on stories_workshops [story_id, workshop_id] (folded
  into the create migration) so a concurrent double-submit can't slip a
  duplicate link past the model validation. NULL workshop_id rows
  (external-title-only) stay unaffected — MySQL allows multiple NULLs.
- Surface the kept legacy stories.workshop_id / external_workshop_title
  columns read-only on the edit form so a persisted story's pre-join
  workshop data stays visible instead of silently hiding.

Co-Authored-By: Claude Opus 4.8 (1M context) <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