Review, generate, and update Rails routes following professional patterns and best practices. Covers RESTful resource routing, route concerns for code reusability, shallow nesting strategies, and advanced route configurations.
63
55%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
Optimize this skill with Tessl
npx tessl skill review --optimize ./skills/routing-patterns/SKILL.mdThis 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.
This skill covers:
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
endExample: Duplicatable Resources
concern :duplicatable do
resources :duplications, only: %i[create], resource_type: parent_resource.name.classify
endExample: Dynamic Form Updates (Turbo/AJAX)
concern :turbo_fetch do
patch :turbo_fetch, on: :collection
endKey Points:
parent_resource.name.classify to dynamically pass the parent context type to controllersonly: or except: to limit actions when appropriatecommentable_type, resource_type) are passed as metadata to controllersparams[:commentable_type] or routing metadataApply 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:
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
endBenefits:
shallow: false when full nesting is neededGenerated 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#destroyOverride Example:
concern :assembly do
# Keep full nesting when parent context is always needed
resources :assembly_items, only: %i[show], param: :kind, shallow: false
endAlways 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 viewsWhy This Matters:
rails routes outputFor 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]
endCommon Pattern:
index (displayed on parent's show page)show (edited inline or from parent view)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
endKey Points:
index action:id parameter (e.g., /users/1/profile not /users/1/profiles/1)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
endKey Points:
:id parameter):id parameter)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
endResults:
/products/:slug instead of /products/:idparams[:slug] instead of params[:id]/products/vintage-leather-jacket instead of /products/123Set default options for a resource:
resources :categories, defaults: { subcategory: false }These defaults are available in params[:subcategory].
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]
endUsage: These allow url_for(@form_object) or link_to(@assembly_item) to work correctly.
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
endMount 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? }# 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_checkHere'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'
endWhen reviewing or generating routes, verify:
commentable_type) use parent_resource.name.classifyscope shallow: trueonly: or except: to limit actionsresource not resourceson: parameterparam: when needed❌ 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
endConcerns 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 functionalityYou 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
endThese custom parameters are available in the controller as routing metadata:
# In controller
def index
@versionable_type = request.path_parameters[:versionable_type] # => "Article"
endAdd 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
endMount 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'
endThese patterns appear frequently in Rails applications and can be implemented using concerns:
Commentable Resources:
Duplicatable Resources:
Archivable Resources:
Searchable Resources:
Versioned Resources:
Taggable Resources:
When asked to review routes:
only:/except: appropriatelyWhen asked to generate new routes:
only: or except: based on needed actionsresource (singular) if there's only one per parentWhen asked to update 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 POSTLast Updated: February 2026
097ad6b
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.