Rails verification
Verification loop for Rails projects: migrations, routes, security, performance, tests with coverage, asset pipeline, and deployment readiness checks before release or PR.From its SKILL.md
npx -y skills add FaisalAlqarni/sp-ecc --skill rails-verificationAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 5 stars5 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
25.3 KB, as published. Nobody here has run it
Rails Verification Loop
Run before PRs, after major changes, and pre-deploy to ensure Rails application quality, security, and production readiness.
Phase 1: Environment Check
# Verify Ruby version
ruby --version # Should match .ruby-version
# Check bundler and gems
bundle --version
bundle outdated
# Verify Rails version
rails --version
# Check for environment variables
ruby -e "puts 'SECRET_KEY_BASE set' if ENV['SECRET_KEY_BASE']; puts 'DATABASE_URL set' if ENV['DATABASE_URL']"
# Verify Node.js (for asset pipeline)
node --version
yarn --version # or npm --version
If environment is misconfigured, stop and fix before proceeding.
Phase 2: Code Quality & Formatting
# RuboCop linting
bundle exec rubocop -a # Auto-correct safe issues
bundle exec rubocop --auto-gen-config # Generate config for existing violations
# Rails best practices check
bundle exec rails_best_practices .
# Reek for code smells
bundle exec reek
# Rails-specific checks
bundle exec rails zeitwerk:check # Verify autoloading
Common issues:
- Style violations (indentation, string quotes, trailing whitespace)
- N+1 query detection in controllers
- Long methods or classes
- Autoloading issues with class/module naming
Phase 3: Migration Verification
# Check for pending migrations
bundle exec rails db:migrate:status
# Validate migration reversibility
bundle exec rails db:migrate
bundle exec rails db:rollback STEP=1
bundle exec rails db:migrate # Re-apply
# Check for migration conflicts
git diff --name-only | grep db/migrate
# Verify migration data safety
# Review each migration for:
# - Data loss operations (remove_column, drop_table)
# - Missing default values for NOT NULL columns
# - Large table alterations without batching
Migration Checklist:
- All migrations are reversible (use
changeor implementdown) - No data loss without explicit backfill strategy
- New columns have sensible defaults or allow NULL
- Indexes added for foreign keys and frequently queried columns
- Zero-downtime compatible (no removed columns/tables still in use)
- Large data migrations use batching (find_each, in_batches)
- Constraints (NOT NULL, unique) added safely in separate migrations
- Migration timestamps don't conflict with other branches
Zero-Downtime Migration Pattern:
# Step 1: Add new column with default
add_column :users, :full_name, :string, default: ""
# Step 2: Backfill data in background job
# User.find_each { |u| u.update(full_name: "#{u.first_name} #{u.last_name}") }
# Step 3: Add validation in model (soft enforcement)
# validates :full_name, presence: true
# Step 4: Add NOT NULL constraint (next deploy)
# change_column_null :users, :full_name, false
# Step 5: Remove old columns (next deploy)
# remove_column :users, :first_name
# remove_column :users, :last_name
Phase 4: Route Verification
# Display all routes
bundle exec rails routes > routes.txt
# Check for route coverage (routes used in code)
bundle exec rails routes:coverage # If rake_routes_coverage gem installed
# Find unused routes
bundle exec rails routes | grep -v "rails/active_storage"
# Verify RESTful compliance
# Review routes.txt for:
# - Excessive custom routes (should use RESTful actions when possible)
# - Missing constraints on route parameters
# - Namespace consistency
Route Security Checklist:
- Admin routes protected behind authentication/authorization
- API routes use proper versioning (api/v1, api/v2)
- Sensitive routes use HTTPS in production
- Route constraints validate parameters (format, id ranges)
- No wildcard routes exposing internal actions
- CORS configured correctly for API endpoints
RESTful Route Review:
# Good: Standard RESTful routes
resources :posts do
resources :comments, only: [:index, :create, :destroy]
end
# Warning: Too many custom routes
resources :posts do
member do
post :publish
post :unpublish
post :archive
get :preview
post :restore
end
end
# Consider: Use state machine or nested resources instead
# Bad: Exposing dangerous routes
# DELETE /users/:id/destroy_with_data # Should require confirmation
Phase 5: Security Scan
# Brakeman security scanner
bundle exec brakeman -q -z
# Bundle-audit for CVE vulnerabilities
bundle exec bundle-audit check --update
# Secrets detection
bundle exec rails credentials:show # Verify encrypted credentials work
git diff | grep -iE "password|secret|api_key|token" | grep -v "# "
# Check for insecure configurations
grep -r "config.force_ssl = false" config/
grep -r "protect_from_forgery" app/controllers/
OWASP Rails Security Checklist:
-
SQL Injection: No string interpolation in queries
# Bad: User.where("name = '#{params[:name]}'") # Good: User.where(name: params[:name]) -
Mass Assignment: Strong parameters in all controllers
# params.permit(:name, :email) # Explicit allowlist -
CSRF:
protect_from_forgery with: :exceptionin ApplicationController -
XSS: No
html_safeorrawwithout sanitization# Bad: <%= raw @user.bio %> # Good: <%= sanitize @user.bio, tags: %w[p br strong em] %> -
Authentication: Devise/Sorcery configured with secure defaults
- Password complexity requirements
- Account lockout after failed attempts
- Session timeout configured
- Secure password reset flow
-
Authorization: Pundit/CanCanCan policies for all actions
# authorize @post # In every controller action -
Secrets Management:
config/master.keynot committed to gitRails.application.credentialsused for API keys- Environment variables for deployment-specific config
-
Security Headers: Configured in production
# config/application.rb config.force_ssl = true config.ssl_options = { hsts: { subdomains: true } } # Content Security Policy config.content_security_policy do |policy| policy.default_src :self policy.script_src :self, :unsafe_inline end -
Rate Limiting: API endpoints protected with rack-attack
# config/initializers/rack_attack.rb Rack::Attack.throttle('api/ip', limit: 300, period: 5.minutes) -
File Uploads: ActiveStorage with validation
# validates :avatar, content_type: ['image/png', 'image/jpg'] # validates :avatar, size: { less_than: 5.megabytes }
Phase 6: Performance Checks
# Bullet gem for N+1 query detection
# Add to Gemfile: gem 'bullet', group: :development
# Enable in config/environments/development.rb
bundle exec rails server # Then browse app and check logs
# Database query analysis
bundle exec rails db:analyze # If available
# Check for missing indexes
bundle exec rails db:schema:dump
# Review schema.rb for foreign keys without indexes
Performance Checklist:
-
N+1 Queries Eliminated:
# Bad: @posts.each { |p| p.user.name } # Good: @posts.includes(:user).each { |p| p.user.name } -
Database Indexes:
- All foreign keys indexed
- Frequently queried columns indexed
- Composite indexes for multi-column queries
- Unique indexes for unique constraints
add_index :posts, :user_id add_index :posts, [:user_id, :published_at] add_index :users, :email, unique: true -
Caching Strategy:
- Fragment caching for expensive views
- Russian Doll caching for nested resources
- HTTP caching headers (ETag, Last-Modified)
- Cache expiration strategy defined
# <%= cache @post do %> # <%= render @post %> # <% end %> -
Background Jobs:
- Sidekiq/DelayedJob configured for async work
- Email sending moved to background
- External API calls in jobs
- Heavy computations queued
# UserMailer.welcome_email(@user).deliver_later # ProcessReportJob.perform_later(@report.id) -
Database Query Optimization:
selectto limit columns retrievedpluckinstead ofmapfor simple attributesfind_each/find_in_batchesfor large datasets- Counter caches for associations
# belongs_to :user, counter_cache: true # add_column :users, :posts_count, :integer, default: 0 -
Pagination:
- Large result sets paginated (kaminari, pagy)
- API responses paginated
# @posts = Post.page(params[:page]).per(25) -
Eager Loading:
includesfor multiple associationspreloadwhen conditions not neededjoinsfor filtering by association
# Post.includes(:user, comments: :user)
Phase 7: Asset Pipeline Verification
# Precompile assets (production mode)
RAILS_ENV=production bundle exec rails assets:precompile
# Check for asset compilation errors
# Review public/assets/ for generated files
# Verify asset paths
bundle exec rails runner "puts ActionController::Base.helpers.asset_path('application.css')"
# Check for uncompiled assets in views
grep -r "javascript_include_tag\|stylesheet_link_tag" app/views/ | grep -v "application"
# Clean up old assets
bundle exec rails assets:clean
Asset Pipeline Checklist:
-
Precompilation:
- All assets compile without errors
- Source maps generated for debugging
- Assets versioned with digest hashes
-
CDN Configuration:
# config/environments/production.rb config.asset_host = ENV['CDN_HOST'] # 'https://cdn.example.com' -
Compression:
- Gzip compression enabled
- Images optimized (ImageOptim, imgproxy)
- Minification for JS/CSS
-
Webpacker/Shakapacker (if used):
bundle exec rails webpacker:compile yarn run webpack:build -
Asset Organization:
- Vendor assets in vendor/assets
- Lib assets in lib/assets
- Manifests correctly require dependencies
Phase 8: Environment Configuration
# Compare environment configs
diff config/environments/development.rb config/environments/production.rb
# Verify environment-specific settings
bundle exec rails runner "puts Rails.env; puts Rails.configuration.action_mailer.delivery_method"
# Check for missing ENV variables
bundle exec rails runner "puts ENV.keys.grep(/^APP_/).inspect"
Environment Configuration Checklist:
Development:
- Detailed error pages enabled
- Asset debugging enabled
- Caching disabled (or selectively enabled)
- Mailer delivery method: letter_opener/test
- Bullet gem active for N+1 detection
Test:
- Fixtures/factories loaded properly
- Test database isolated
- Transactional tests enabled
- Background jobs run inline (or with test adapter)
Staging:
- Production-like configuration
- Error tracking enabled (Sentry, Rollbar)
- Performance monitoring active (New Relic, Scout)
- Separate database from production
Production:
-
config.force_ssl = true -
config.log_level = :info -
config.consider_all_requests_local = false -
config.action_controller.perform_caching = true -
config.assets.compile = false(precompiled assets only) -
config.active_storage.service = :amazon(or appropriate service) -
config.action_mailer.delivery_method = :smtp - Error tracking configured
- Log aggregation configured (Papertrail, Loggly)
Phase 9: Tests + Coverage
# Run full test suite with SimpleCov
COVERAGE=true bundle exec rspec
# Or with Minitest
COVERAGE=true bundle exec rails test
# Run specific test types
bundle exec rspec spec/models/
bundle exec rspec spec/controllers/
bundle exec rspec spec/requests/
bundle exec rspec spec/system/
# Check coverage report
open coverage/index.html
Test Coverage Report:
- Total tests: X passed, Y failed, Z skipped
- Overall coverage: XX%
- Per-component breakdown
Coverage Targets:
| Component | Target |
|---|---|
| Models | 90%+ |
| Controllers | 80%+ |
| Serializers | 85%+ |
| Services/POROs | 90%+ |
| Jobs | 85%+ |
| Mailers | 80%+ |
| Overall | 80%+ |
Test Quality Checks:
- All models have unit tests
- Controllers have request/integration tests
- Happy path and error cases covered
- Edge cases tested (nil, empty, boundary values)
- Authentication/authorization tested
- Background jobs tested
- Mailers have tests
Phase 10: Deployment Readiness
# Health check endpoint
curl http://localhost:3000/health
# Should return 200 OK with system status
# Database connectivity
bundle exec rails runner "ActiveRecord::Base.connection.execute('SELECT 1')"
# Cache connectivity (Redis)
bundle exec rails runner "Rails.cache.write('test', 'value'); puts Rails.cache.read('test')"
# Background job processor
# Ensure Sidekiq/DelayedJob is running
bundle exec sidekiq -C config/sidekiq.yml &
Deployment Checklist:
-
Health Checks:
# config/routes.rb get '/health', to: 'health#check' # app/controllers/health_controller.rb class HealthController < ApplicationController def check render json: { status: 'ok', database: database_ok?, cache: cache_ok?, timestamp: Time.current } end end -
Monitoring:
- Application performance monitoring (APM) configured
- Error tracking with context (Sentry, Rollbar, Honeybadger)
- Uptime monitoring (Pingdom, UptimeRobot)
- Database query monitoring
-
Logging:
- Structured logging (lograge gem)
- Log rotation configured
- Sensitive data filtered (passwords, tokens)
- Request IDs tracked across services
# config/initializers/lograge.rb Rails.application.configure do config.lograge.enabled = true config.lograge.custom_options = lambda do |event| { params: event.payload[:params].except('controller', 'action') } end end -
Error Tracking:
# config/initializers/sentry.rb Sentry.init do |config| config.dsn = ENV['SENTRY_DSN'] config.breadcrumbs_logger = [:active_support_logger, :http_logger] config.traces_sample_rate = 0.1 end -
Database Backups:
- Automated daily backups configured
- Backup restoration tested
- Point-in-time recovery available
- Backup retention policy defined
-
Scaling Readiness:
- Stateless application servers
- Session store externalized (Redis, database)
- File uploads to cloud storage (S3, GCS)
- Database connection pooling configured
Phase 11: Database Verification
# Check database schema consistency
bundle exec rails db:schema:dump
git diff db/schema.rb # Should show expected changes only
# Validate seed data
bundle exec rails db:seed
bundle exec rails runner "puts User.count; puts Post.count"
# Test database backup/restore
pg_dump myapp_production > backup.sql
createdb myapp_test_restore
psql myapp_test_restore < backup.sql
# Check for database bloat (PostgreSQL)
bundle exec rails dbconsole << EOF
SELECT schemaname, tablename, pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
FROM pg_tables WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC LIMIT 10;
EOF
Database Checklist:
- Schema.rb matches actual database structure
- Seeds.rb creates necessary default data
- Foreign key constraints defined
- No orphaned records in production
- Database indexes optimized
- Slow query log reviewed
- Connection pool sized appropriately
- Database vacuuming scheduled (PostgreSQL)
Phase 12: Code Quality Audit
# Run static analysis tools
bundle exec rubocop --format offenses
# Code complexity analysis
bundle exec flog app/ # Lower scores are better
bundle exec flay app/ # Detects duplicate code
# Security-specific linting
bundle exec brakeman -A # All checks, including warnings
# Dependency analysis
bundle exec bundle-audit check
bundle exec license_finder # Check gem licenses
Code Quality Metrics:
- RuboCop Compliance: 0 offenses (or documented exceptions)
- Flog Score: < 40 for methods, < 200 for classes
- Duplicate Code: < 5% duplication (flay)
- Documentation: Public API methods documented
- Test Coverage: ≥ 80% overall
Phase 13: Diff Review
# Show diff statistics
git diff --stat origin/main
# Show actual changes
git diff origin/main
# Show changed files
git diff --name-only origin/main
# Check for common issues
git diff origin/main | grep -i "todo\|fixme\|hack\|xxx"
git diff origin/main | grep "binding.pry" # Debug statements
git diff origin/main | grep "puts \|pp \|p " # Print debugging
git diff origin/main | grep -i "password.*=\|api_key.*=" # Hardcoded secrets
Diff Review Checklist:
- No debugging statements (puts, pp, binding.pry, debugger)
- No TODO/FIXME in critical code paths
- No hardcoded secrets or credentials
- Database migrations included for model changes
- Routes added/modified are documented
- Configuration changes documented in README or deploy notes
- Error handling present for external calls
- Transaction management where needed
- Logging added for important operations
- Comments explain "why" not "what"
Output Template
RAILS VERIFICATION REPORT
=========================
Phase 1: Environment Check
✓ Ruby 3.2.2
✓ Rails 7.1.0
✓ Bundler 2.4.10
✓ Node.js 20.10.0
✓ All environment variables set
Phase 2: Code Quality
✓ RuboCop: 0 offenses
✓ Reek: No code smells
✓ Rails best practices: Clean
✓ Zeitwerk: All classes loadable
Phase 3: Migration Verification
✓ No pending migrations
✓ All migrations reversible
✓ No data loss operations
✓ Foreign keys indexed
✓ Zero-downtime compatible
Phase 4: Route Verification
✓ 247 routes defined
✓ All admin routes protected
✓ API versioning consistent
✓ RESTful compliance: 95%
⚠ 3 unused routes detected
Phase 5: Security Scan
✓ Brakeman: 0 security issues
⚠ Bundle-audit: 2 vulnerabilities (update nokogiri, loofah)
✓ No hardcoded secrets
✓ CSRF protection enabled
✓ Strong parameters enforced
✓ Force SSL enabled in production
Phase 6: Performance
✓ No N+1 queries detected (Bullet scan)
✓ All foreign keys indexed
✓ Counter caches configured
✓ Background jobs for async work
✓ Fragment caching implemented
⚠ Consider pagination for /posts (500+ records)
Phase 7: Asset Pipeline
✓ Assets precompile successfully
✓ CDN configured
✓ Gzip compression enabled
✓ Source maps generated
Phase 8: Environment Configuration
✓ Production config secure
✓ Staging mirrors production
✓ Environment variables documented
✓ Force SSL enabled
✓ Log level: info
Phase 9: Tests + Coverage
Tests: 1,247 examples, 0 failures, 12 pending
Coverage:
Overall: 87.3%
Models: 92.1%
Controllers: 84.5%
Services: 91.8%
Jobs: 88.3%
Mailers: 82.4%
Phase 10: Deployment Readiness
✓ Health check endpoint: 200 OK
✓ Database connectivity verified
✓ Redis cache reachable
✓ Sidekiq running
✓ Error tracking (Sentry) configured
✓ APM (New Relic) configured
✓ Log aggregation (Papertrail) active
Phase 11: Database
✓ Schema consistent
✓ Seeds runnable
✓ Backups configured
✓ Foreign keys enforced
✓ No orphaned records
Phase 12: Code Quality
✓ RuboCop: 0 offenses
✓ Flog: Average complexity 15.2
✓ Flay: 2.3% duplication
✓ Documentation complete
Phase 13: Diff Review
Files changed: 34
+1,247 lines, -523 lines
✓ No debug statements
✓ No hardcoded secrets
✓ Migrations included
✓ Error handling present
RECOMMENDATIONS:
1. ⚠ Update nokogiri and loofah (security vulnerabilities)
2. ⚠ Add pagination to /posts endpoint
3. ⚠ Remove 3 unused routes
OVERALL STATUS: ⚠️ FIX WARNINGS BEFORE DEPLOY
NEXT STEPS:
1. bundle update nokogiri loofah
2. Implement pagination for high-volume endpoints
3. Remove unused routes
4. Re-run verification
5. Deploy to staging for final testing
Pre-Deployment Checklist
Critical (Blockers):
- All tests passing (0 failures)
- Coverage ≥ 80%
- No critical security vulnerabilities (Brakeman, bundle-audit)
- No pending migrations (or plan to run them)
-
config.force_ssl = truein production -
SECRET_KEY_BASEproperly set - Database backups configured and tested
High Priority:
- Error tracking configured (Sentry, Rollbar, Honeybadger)
- APM configured (New Relic, Scout, Skylight)
- Logging configured and log rotation active
- Health check endpoint responding
- Assets precompiled and CDN configured
- Background job processor running
- Redis/cache backend configured
- Database connection pooling sized correctly
Medium Priority:
- Uptime monitoring configured
- Rate limiting configured (rack-attack)
- CORS configured correctly for APIs
- Session store externalized
- File uploads to cloud storage
- Email delivery configured (SMTP, SendGrid, Mailgun)
- Content Security Policy configured
- HSTS headers enabled
Nice to Have:
- Feature flags configured (Flipper, Rollout)
- A/B testing framework (if needed)
- Internationalization (I18n) configured
- Sitemap generation
- RSS feeds (if applicable)
- OpenAPI/Swagger documentation (for APIs)
Continuous Integration
GitHub Actions Example
# .github/workflows/rails-verification.yml
name: Rails Verification
on: [push, pull_request]
jobs:
verify:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2
bundler-cache: true
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'yarn'
- name: Install dependencies
run: |
bundle install
yarn install
- name: Setup database
env:
RAILS_ENV: test
DATABASE_URL: postgres://postgres:postgres@localhost:5432/test
run: |
bundle exec rails db:create
bundle exec rails db:schema:load
- name: Run RuboCop
run: bundle exec rubocop
- name: Run Brakeman
run: bundle exec brakeman -q -z
- name: Run bundle-audit
run: bundle exec bundle-audit check --update
- name: Compile assets
run: bundle exec rails assets:precompile
env:
RAILS_ENV: test
- name: Run tests
env:
RAILS_ENV: test
DATABASE_URL: postgres://postgres:postgres@localhost:5432/test
REDIS_URL: redis://localhost:6379/0
run: |
bundle exec rspec --format documentation
- name: Upload coverage
uses: codecov/codecov-action@v3
if: always()
Quick Reference
| Check | Command |
|---|---|
| Environment | ruby --version && rails --version |
| Linting | bundle exec rubocop -a |
| Security | bundle exec brakeman -q && bundle-audit check |
| Migrations | bundle exec rails db:migrate:status |
| Routes | bundle exec rails routes |
| Tests | bundle exec rspec |
| Coverage | COVERAGE=true bundle exec rspec |
| Assets | RAILS_ENV=production bundle exec rails assets:precompile |
| Code quality | bundle exec rails_best_practices . |
| Diff stats | git diff --stat |
Common Issues & Solutions
Issue: Asset precompilation fails in production
# Solution: Check for missing gems in production group
bundle exec rails assets:precompile RAILS_ENV=production
# Review error messages for missing gems or Node.js issues
Issue: Database migration fails in production
# Solution: Test migration on production copy
pg_dump production_db | psql staging_db
RAILS_ENV=staging bundle exec rails db:migrate
Issue: Tests pass locally but fail in CI
# Common causes:
# 1. Database not reset between runs
bundle exec rails db:test:prepare
# 2. Time zone differences
# Add to spec_helper.rb: Time.zone = 'UTC'
# 3. Flaky tests (race conditions)
# Run with --seed to reproduce: bundle exec rspec --seed 12345
Issue: N+1 queries in production
# Detection: Enable Bullet in production (carefully)
# Or use APM tools (New Relic, Scout)
# Fix: Use includes/preload
# Before: @posts.each { |p| p.user.name }
# After: @posts.includes(:user).each { |p| p.user.name }
Issue: Memory bloat in background jobs
# Solution: Process in batches
User.find_in_batches(batch_size: 100) do |batch|
batch.each { |user| process_user(user) }
end
Remember: Automated verification catches common issues but doesn't replace manual code review, staging environment testing, and production monitoring. Always have a rollback plan.