routing-patterns
npx skills add https://github.com/rolemodel/rolemodel-skills --skill routing-patterns
Agent 安装分布
Skill 文档
Routes Best Practices
Purpose
This skill helps AI agents review, generate, and update Rails routes following professional patterns and best practices. It covers RESTful resource routing, route concerns for code reusability, shallow nesting strategies, and advanced route configurations that can be applied to any Rails application.
Context
This skill covers:
- Rails routing with RESTful conventions
- Route concerns for DRY (Don’t Repeat Yourself) principle
- Shallow nesting to avoid overly long URLs
- Custom parameters and metadata for flexible routing
- Custom route resolvers for polymorphic paths
- Constraint-based routing for authorization
- Organization patterns for maintainable routes files
Best Practices
1. Route Concerns for Reusable Behavior
Define concerns at the top of routes.rb for behaviors shared across multiple resources. This keeps your routes DRY and maintainable.
Example: Commentable Resources
concern :commentable do
resources :comments, commentable_type: parent_resource.name.classify
end
Example: Duplicatable Resources
concern :duplicatable do
resources :duplications, only: %i[create], resource_type: parent_resource.name.classify
end
Example: Dynamic Form Updates (Turbo/AJAX)
concern :turbo_fetch do
patch :turbo_fetch, on: :collection
end
Key Points:
- Concerns extract common nested resource patterns into reusable modules
- Use
parent_resource.name.classifyto dynamically pass the parent context type to controllers - Specify
only:orexcept:to limit actions when appropriate - Custom parameters (like
commentable_type,resource_type) are passed as metadata to controllers - Controllers can access these via
params[:commentable_type]or routing metadata
2. Applying Concerns to Resources
Apply concerns using the concerns: option with an array of symbols:
resources :products, concerns: %i[duplicatable turbo_fetch]
resources :articles, concerns: %i[commentable duplicatable turbo_fetch]
Benefits:
- Eliminates repetitive nested resource definitions
- Changes to shared behavior only need to be updated in one place
- Makes it immediately clear which resources share common functionality
3. Shallow Nesting Strategy
Use scope shallow: true wrapper to enable shallow nesting for all nested resources. This prevents URLs from becoming unwieldy with deep nesting.
scope shallow: true do
resources :projects do
resources :tasks do
resources :comments
# comments routes are shallow - only :index and :create are nested
# show/edit/update/destroy use /comments/:id instead of /projects/:project_id/tasks/:task_id/comments/:id
end
end
end
end
Benefits:
- Shorter, cleaner URLs for member actions (show, edit, update, destroy)
- Only collection actions (index, create) remain nested under parent
- Easier to bookmark and share individual resource URLs
- Can override with
shallow: falsewhen full nesting is needed
Generated Routes:
# Nested (collection routes)
GET /projects/:project_id/tasks tasks#index
POST /projects/:project_id/tasks tasks#create
GET /projects/:project_id/tasks/new tasks#new
# Shallow (member routes)
GET /tasks/:id tasks#show
GET /tasks/:id/edit tasks#edit
PATCH /tasks/:id tasks#update
DELETE /tasks/:id tasks#destroy
Override Example:
concern :assembly do
# Keep full nesting when parent context is always needed
resources :assembly_items, only: %i[show], param: :kind, shallow: false
end
4. Limiting Actions with only: and except:
Always be explicit about which actions a resource provides. This improves security, performance, and code clarity.
# Only specific actions
resources :webhooks, only: [], concerns: %i[turbo_fetch] # No standard REST actions, only custom
resources :duplications, only: %i[create] # Only create action needed
resources :previews, only: %i[show update] # Only show and update
# All except specific actions
resources :automations, except: %i[show] # All standard actions except show
resources :notes, except: %i[show] # Create/edit/destroy but no individual view
resources :widgets, except: %i[index show] # No collection or individual views
Why This Matters:
- Prevents unused routes from cluttering
rails routesoutput - Blocks access to unimplemented controller actions
- Documents intent clearly for other developers
- Reduces attack surface by not exposing unnecessary endpoints
5. Nested Resources
For resources that belong to a parent, nest them appropriately:
resources :companies do
resources :employees, except: %i[index show]
end
resources :products do
resources :reviews, except: %i[index]
resources :variants, except: %i[index show]
end
Common Pattern:
- Parent resource gets full CRUD by default
- Nested resources often exclude
index(displayed on parent’s show page) - Nested resources often exclude
show(edited inline or from parent view) - Nested create/update/destroy actions work within parent context
6. Singular Resources
Use resource (singular) for resources where there’s only one per parent:
resources :users do
resource :profile, only: %i[show edit update] # Only one profile per user
resource :settings, only: %i[edit update] # Only one settings per user
resource :avatar, only: %i[show update] # Only one avatar per user
end
Key Points:
- Singular resources don’t have an
indexaction - URLs don’t require an
:idparameter (e.g.,/users/1/profilenot/users/1/profiles/1) - Perfect for one-to-one relationships or singleton resources
7. Collection and Member Routes
Add custom routes using on: :collection or on: :member:
resources :products do
# Collection routes (no :id needed)
get :search, on: :collection # /products/search
post :bulk_import, on: :collection # /products/bulk_import
# Member routes (requires :id)
post :duplicate, on: :member # /products/:id/duplicate
patch :archive, on: :member # /products/:id/archive
get :preview, on: :member # /products/:id/preview
end
# In a concern
concern :archivable do
patch :archive, on: :member
patch :unarchive, on: :member
end
Key Points:
- Collection routes act on the entire collection (no
:idparameter) - Member routes act on a single resource (requires
:idparameter) - Use appropriate HTTP verbs (GET for reads, POST for creates, PATCH/PUT for updates, DELETE for removes)
8. Custom Parameters
Override default parameter names using param::
resources :products, param: :slug # Uses :slug instead of :id
# In concerns
concern :categorizable do
resources :categories, only: %i[show], param: :slug
end
Results:
- URLs become
/products/:sluginstead of/products/:id - Controller receives
params[:slug]instead ofparams[:id] - Example:
/products/vintage-leather-jacketinstead of/products/123 - Useful for SEO-friendly URLs or when using non-numeric identifiers
9. Default Options
Set default options for a resource:
resources :categories, defaults: { subcategory: false }
These defaults are available in params[:subcategory].
10. Custom Route Resolvers
Define custom resolvers for polymorphic path helpers:
resolve 'Bulk::Accessories' do |form|
form.persisted? ? [form.accessory] : [form.tank, :accessories]
end
resolve 'AssemblyItem' do |item|
[item.host, item]
end
Usage: These allow url_for(@form_object) or link_to(@assembly_item) to work correctly.
11. Session Routes
Use controller block with scope for related authentication actions:
controller :sessions do
get :login, action: :new
delete :logout, action: :destroy
scope :auth do
get :failure
match 'ADFS/callback', action: :create, via: %i[get post], as: :adfs_callback
end
end
12. Mounting Engines with Constraints
Mount admin engines with authentication constraints:
# Allow access only if user is logged in and is admin
mount PgHero::Engine, at: :pghero,
constraints: -> env {
env.session[:user_id].present? &&
User.find_by(id: env.session[:user_id])&.admin?
}
# Redirect to login if not authenticated
get :pghero, to: redirect('/login'), anchor: false,
constraints: -> env { env.session[:user_id].blank? }
13. Health Check Routes
# Production health check
mount Health::Check::Engine, at: 'health-check' if Rails.env.production?
# Rails 7.1+ health check
get :up, to: 'rails/health#show', as: :rails_health_check
Complete Example Structure
Here’s a well-organized routes file following all best practices:
Rails.application.routes.draw do
# 1. Define concerns first (reusable route patterns)
concern :commentable do
resources :comments, commentable_type: parent_resource.name.classify
end
concern :archivable do
patch :archive, on: :member
patch :unarchive, on: :member
end
concern :searchable do
get :search, on: :collection
end
# 2. Main routes with shallow nesting
scope shallow: true do
# Top-level resources
resources :users, except: %i[show] do
resource :profile, only: %i[show edit update]
resource :settings, only: %i[edit update]
end
resources :products, concerns: %i[commentable archivable searchable] do
resources :reviews, except: %i[index]
resources :variants, except: %i[index show]
end
# Nested resources
resources :projects do
resources :tasks, concerns: %i[commentable] do
resource :assignment, only: %i[create destroy]
end
end
end
# 3. Session/authentication routes
controller :sessions do
get :login, action: :new
post :login, action: :create
delete :logout, action: :destroy
end
# 4. Admin routes with constraints
namespace :admin do
resources :users
resources :settings, only: %i[index update]
end
# 5. Mounted engines (with constraints if needed)
mount Sidekiq::Web, at: '/sidekiq', constraints: AdminConstraint.new
# 6. Custom route resolvers (for polymorphic routing)
resolve 'ProjectTask' do |task|
[task.project, task]
end
# 7. Health checks
get :up, to: 'rails/health#show', as: :rails_health_check
# 8. Root route
root 'dashboard#index'
end
Review Checklist
When reviewing or generating routes, verify:
- Concerns are defined at the top of the file
- Concerns are DRY and reusable across multiple resources
- Custom parameters (like
commentable_type) useparent_resource.name.classify - Shallow nesting is enabled with
scope shallow: true - Resources explicitly use
only:orexcept:to limit actions - Nested resources follow the pattern (often no index/show)
- Singular resources use
resourcenotresources - Collection/member routes use
on:parameter - Custom parameter names use
param:when needed - Route resolvers are defined for form objects and polymorphic models
- Admin engines have authentication constraints
- Root route is defined
- Routes are organized logically (concerns â resources â custom â engines â resolvers â root)
Anti-Patterns to Avoid
â Don’t repeat nested resource patterns:
resources :articles do
resources :comments, commentable_type: 'Article'
end
resources :posts do
resources :comments, commentable_type: 'Post'
end
â Use concerns instead:
concern :commentable do
resources :comments, commentable_type: parent_resource.name.classify
end
resources :articles, concerns: %i[commentable]
resources :posts, concerns: %i[commentable]
â Don’t use deep nesting without shallow:
resources :companies do
resources :projects do
resources :tasks do
# Results in /companies/:company_id/projects/:project_id/tasks/:id/edit
end
end
end
â Use shallow nesting:
scope shallow: true do
resources :companies do
resources :projects do
resources :tasks # edit becomes /tasks/:id/edit
end
end
end
â Don’t leave all actions when not needed:
resources :duplications # Provides 7 REST actions but only need create
â Be explicit:
resources :duplications, only: %i[create]
â Don’t use plural for singular resources:
resources :profiles, only: %i[show] # There's only one profile per user
â Use singular resource:
resource :profile, only: %i[show]
â Don’t hardcode context types:
concern :commentable do
resources :comments, commentable_type: 'Article' # Only works for articles
end
â Use parent_resource:
concern :commentable do
resources :comments, commentable_type: parent_resource.name.classify
end
Advanced Patterns
Concerns with Nested Concerns
Concerns can reference other concerns for highly reusable routing patterns:
concern :searchable do
get :search, on: :collection
get :autocomplete, on: :collection
end
concern :taggable do
resources :tags, only: %i[index create destroy],
concerns: %i[searchable],
taggable_type: parent_resource.name.classify
end
resources :articles, concerns: %i[taggable]
# Articles get tags with search and autocomplete functionality
Multiple Custom Params
You can pass multiple custom parameters to concerns:
concern :versioned do
resources :versions,
only: %i[index show],
param: :version_number,
shallow: false,
versionable_type: parent_resource.name.classify
end
These custom parameters are available in the controller as routing metadata:
# In controller
def index
@versionable_type = request.path_parameters[:versionable_type] # => "Article"
end
Context-Specific Route Additions
Add additional routes to specific resources after applying a concern:
resources :articles, concerns: %i[commentable] do
# Add article-specific routes beyond the concern
get :preview, on: :member
post :publish, on: :member
end
Conditional Engine Mounting
Mount engines conditionally based on environment:
if Rails.env.production?
mount HealthCheck::Engine, at: 'health-check'
end
unless Rails.env.production?
mount LetterOpenerWeb::Engine, at: '/letter_opener'
end
Common Routing Patterns
These patterns appear frequently in Rails applications and can be implemented using concerns:
Commentable Resources:
- Pattern: Resources that can have comments
- Implementation: Nested comments resource with polymorphic association
- Applied to: Articles, blog posts, products, tasks, etc.
Duplicatable Resources:
- Pattern: Resources that can be cloned/duplicated
- Implementation: Create-only nested resource for duplication action
- Applied to: Templates, documents, configurations, etc.
Archivable Resources:
- Pattern: Resources that can be archived/unarchived
- Implementation: Member routes for archive state changes
- Applied to: Projects, documents, records, etc.
Searchable Resources:
- Pattern: Resources with search/filter functionality
- Implementation: Collection routes for search operations
- Applied to: Products, users, articles, etc.
Versioned Resources:
- Pattern: Resources with version history
- Implementation: Nested versions resource, often read-only
- Applied to: Documents, API resources, configurations, etc.
Taggable Resources:
- Pattern: Resources that can be tagged/categorized
- Implementation: Nested tags with create/destroy actions
- Applied to: Articles, images, bookmarks, etc.
Usage Instructions for AI Agents
When asked to review routes:
- Check routes are organized: concerns â resources â custom â engines â resolvers â root
- Verify concerns are properly defined and reusable
- Check for deep nesting that should use shallow
- Ensure resources use
only:/except:appropriately - Verify singular vs plural resource usage
- Check for repeated patterns that should be concerns
- Validate custom resolvers match model relationships
When asked to generate new routes:
- Determine if the behavior is reusable â create/use a concern
- Check if resource should be nested under a parent
- Enable shallow nesting for nested resources (unless explicitly needed)
- Specify
only:orexcept:based on needed actions - Use
resource(singular) if there’s only one per parent - Add custom collection/member routes if needed
- Create route resolver if it’s a form object or polymorphic model
- Follow existing concern patterns for similar functionality
When asked to update routes:
- Maintain consistency with existing patterns
- If adding similar functionality to multiple resources, refactor to use concerns
- Update existing concerns rather than duplicating code
- Preserve shallow nesting strategy
- Keep routes organized in the established structure
- Update custom resolvers if model relationships change
Testing Routes
Always verify routes after changes:
# List all routes
rails routes
# Search for specific routes
rails routes | grep products
# Show routes for a specific controller
rails routes -c products
# Show routes with expanded format
rails routes --expanded
# Filter by HTTP verb
rails routes -g POST
Last Updated: February 2026