Add polymorphic-parent-resources skill - #42
Conversation
Documents the pattern for serving a child resource that hangs off many different parents -- comments, reports, duplications, attachments -- from a single controller, rather than one namespaced controller per parent. Pairs a route concern passing the parent class name as a route default with `resource_for`, which ships in rolemodel_rails 2.4.0. The skill covers models, routing, controller, and views; the gem version gate with a runtime check and a temporary local fallback for pinned projects; the type allowlist for user-supplied types; and a gotchas table. references/retrofit.md holds the audit commands, consolidation order, and verification steps for adopting the pattern in an app that already has duplicated per-parent controllers. Adds cross-references from the two skills developers already reach for, since nobody searches for a method they have not heard of. routing-patterns previously taught the route concern and then stopped at "controllers can access these via params[:commentable_type]" -- that dead end now points at the controller half. controller-patterns gains a decision-tree branch, Common Mistakes rows, and review checklist items, because its Nested Resource example was the template being copied per parent. Also documents a routing trap the gem README does not mention: applying a concern with member actions to several parents under `shallow` draws the member path once per parent, and only the first keeps the route name. Every member request therefore resolves with the first-drawn parent's `*_type` default regardless of the record's real parent, so scoping the parent before_action to collection actions is load-bearing for correctness, not just tidiness. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds a new Rails skill documenting the “polymorphic parent resources” pattern (route concern + resource_for) and updates existing routing/controller skills and repository indexes to reference it.
Changes:
- Introduces
polymorphic-parent-resourcesskill docs plus a retrofit guide for consolidating per-parent controllers. - Updates
routing-patternsandcontroller-patternsto call out the pattern and the shallow-member-route*_typedefault trap. - Adds the new skill to
README.mdandAGENTS.md.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| skills/routing-patterns/SKILL.md | Adds guidance linking route *_type defaults to the controller-side resource_for pattern and documents a shallow routing caveat. |
| skills/polymorphic-parent-resources/references/retrofit.md | New retrofit guide with audit/consolidation steps and verification commands. |
| skills/polymorphic-parent-resources/SKILL.md | New skill documenting the end-to-end pattern, prerequisites, gotchas, and controller template. |
| skills/controller-patterns/SKILL.md | Updates decision tree/checklists to steer multi-parent child resources to the new skill. |
| README.md | Adds the new skill to the skills list and cross-skill recommendations. |
| AGENTS.md | Adds the new skill to the Rails Backend agent list. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (4)
skills/polymorphic-parent-resources/SKILL.md:118
:unprocessable_contentis not a valid Rails status symbol for 422 responses; Rails uses:unprocessable_entity. As written, this example will raise (or send an unintended status) in many Rails versions. Update the snippet to usestatus: :unprocessable_entity(orstatus: 422).
render :new, status: :unprocessable_content
skills/polymorphic-parent-resources/references/retrofit.md:42
- These
grepcommands rely on\\|alternation without enabling extended regex, which is not portable (notably on BSD/macOSgrep). Usegrep -E(or a single regex likeresources :(comments|generated_reports|duplications)/(_comments_path|_comment_path)) so the commands work consistently across environments.
grep -n 'resources :comments\|resources :generated_reports\|resources :duplications' config/routes.rb
skills/polymorphic-parent-resources/references/retrofit.md:112
- These
grepcommands rely on\\|alternation without enabling extended regex, which is not portable (notably on BSD/macOSgrep). Usegrep -E(or a single regex likeresources :(comments|generated_reports|duplications)/(_comments_path|_comment_path)) so the commands work consistently across environments.
grep -rn '_comments_path\|_comment_path' app spec | grep -v 'polymorphic'
skills/routing-patterns/SKILL.md:57
- The new lines change list structure/formatting: the prior controller note was nested under the custom-parameters bullet, but line 55 is now a top-level item and line 57 breaks out of the list entirely. If this section is intended to keep the controller guidance tied to the metadata bullet, consider nesting line 55 (and optionally the 'Read...' note) under line 54, or format the note as a consistent callout (e.g., blockquote) to avoid accidental outline breakage.
- Use `parent_resource.name.classify` to dynamically pass the parent context type to controllers
- Specify `only:` or `except:` to limit actions when appropriate
- Custom parameters (like `commentable_type`, `resource_type`) are passed as metadata to controllers
- Controllers turn that metadata back into the parent record with `resource_for(:commentable_type)`, provided by the `rolemodel_rails` gem (>= 2.4.0)
**Read the `polymorphic-parent-resources` skill before writing the controller.** Passing a `*_type` default is only half the pattern — that skill covers the controller side, the required `before_action` scoping under shallow nesting (member routes inherit the wrong `*_type`), and how to consolidate existing per-parent controllers.
Ships a skill for
Rolemodel::ResourceFor::ControllerExtension, added torolemodel_railsin 2.4.0 (#205), so the pattern gets found and adopted instead of sitting in a gem README.Why cross-links, not just a new skill
The discovery problem is that nobody searches for
resource_for— they have never heard of it. Two existing skills are where developers and agents already are when they hit this problem, and both currently point the wrong way:routing-patternsalready taught the route concern (commentable_type: parent_resource.name.classify) and then stopped at "Controllers can access these viaparams[:commentable_type]." It taught you to pass the parent type and never said what to do with it. That dead end now points at the controller half.controller-patternsactively taught the thing this replaces. Its decision tree routed "Nested under parent resource? → Nested RESTful Controller Pattern", and the example hard-codesOrder.find(params[:order_id])with no note about multiple parents. An agent following it faithfully generates exactly the per-parent duplication the pattern exists to remove.grep -r "resource_for\|rolemodel_rails" skills/returned zero hits across all 26 skills before this PR.What's here
New skill —
skills/polymorphic-parent-resources/(SKILL.md at 195 lines, under the repo's ~200 convention):only:/except:split under shallow nestingrolemodel_rails >= 2.4.0gate, withbundle infoplus a runtime check, and a temporary local concern for projects pinned to an older version*_typearrives from user input rather than a route defaultreferences/retrofit.md— audit commands, consolidation order (one parent at a time), and verification for apps that already have duplicated per-parent controllersCross-links —
routing-patterns(dead-end bullet replaced, shallow caveat, checklist item),controller-patterns(decision-tree branch, two Common Mistakes rows, warning on the Nested Resource example, Agent Instructions branch, review checkbox), plusREADME.mdandAGENTS.mdlistings and the "How skills work together" section.A routing trap worth a look
Verifying the gem README's own example turned up something it does not document. Applying a concern with member actions to several parents under
shallowdraws/comments/:idonce per parent, and Rails keeps only the first route's name:So on member actions
params[:commentable_type]is present but is always the first-drawn parent, whatever the record's real parent is. That makes scoping the parentbefore_actionto collection actions load-bearing for correctness — the failure mode is either a silent wrong-parent read or a confusing 404 fromfind(nil), not just an untidy callback. Both the new skill androuting-patternsdocument it.The gem README currently explains the split as "member actions carry no parent id," which is true but undersells it. Happy to open a follow-up there if you agree it's worth restating.
Review notes
:inside the plain-scalar description, which would have made the skill fail to load).markdownlint-cli2defaults are untouched baseline — the repo has no markdownlint config and existing skills violate it identically.testing-patterns, but the directory isskills/tdd/.🤖 Generated with Claude Code