Rails Standards
by Convext
Ruby on Rails coding conventions and patterns
Rules (52)
Config
Config in env vars, not code. Never commit .env. Commit .env.example. Never hardcode secrets.
Database
Passwords: bcrypt. API keys: encrypt. PII: field-level encryption. Never log sensitive data.
Dependencies
Use latest stable versions. Fix breaking changes—don't avoid upgrades. Check official registries.
Formatting
StandardRB: no config, no debates. RuboCop with opinionated defaults. Just run `standardrb --fix`.
Git
Small, focused commits. Use: feat/fix/refactor/test/docs/chore. Each commit independently deployable.
Linting
RuboCop with .rubocop.yml config. Inherit from rubocop-rails, rubocop-minitest. Auto-correct safe cops.
Llm Behavior
You wrote every line. No 'pre-existing issues'—only issues you haven't fixed yet.
User and tests define done. Don't redefine scope or declare partial progress as complete.
No excuses: 'pre-existing', 'unrelated', 'tedious', 'for now'. Recognize and continue working.
Package Manager
Gemfile + Gemfile.lock. `bundle install --deployment` in prod. Groups for dev/test. `bundle exec` for commands.
Rails
Whitelist attributes explicitly. Never `.permit!`. Use `require(:model).permit(:field)`.
Use `has_secure_password`, `authenticate_by`, `generates_token_for`. Never use Devise/Sorcery/Clearance.
Rails.application.credentials for secrets. `rails credentials:edit`. Environment-specific credentials. Never commit master.key.
Add NOT NULL, UNIQUE indexes, foreign keys, check constraints. Don't rely only on ActiveRecord validations.
Email, file processing, API calls, reports → background jobs (SolidQueue/Sidekiq). Keep requests <200ms.
Pundit policies for authorization. `authorize @record` in controllers. Policy classes match models.
Minitest only—no RSpec. Rails default, fast, simple, fixtures-integrated.
Service objects for multi-model operations. Single public method (call). Return Result/Response objects. Keep models focused on persistence.
Sidekiq for heavy background work. Redis required. Use perform_later. Retries with exponential backoff.
Turbo Drive for SPA-like nav. Turbo Frames for partials. Turbo Streams for real-time. Stimulus for JS sprinkles.
SolidQueue: Rails 8 default job backend. Database-backed. No Redis needed. Mission Control for monitoring.
Use Turbo (Drive/Frames/Streams) + Stimulus before React/Vue. JS frameworks only for complex client state.
Controllers: auth, params, call service, render. Logic in models/services/form objects/query objects.
Use `includes`/`preload`/`eager_load` for associations. Use Bullet gem in development.
Propshaft for asset pipeline. Simpler than Sprockets. No compilation, just fingerprinting. Rails 8 default.
SolidCache: database-backed cache. No Redis needed. Good for most apps. Rails 8 default.
SolidCable: database-backed pub/sub for Action Cable. No Redis needed. Rails 8 default.
Action Cable for WebSockets. Channels for subscriptions. Redis adapter for multi-server. Use Turbo Streams when possible.
Always `has_many :through` with explicit join model. Never `has_and_belongs_to_many`.
Define scopes for reusable queries. Enables chaining: `Order.recent.completed.for_user(user)`.
Importmap for JS without bundling. Pin packages from CDN or vendor. Works with Hotwire. No node_modules.
Active Storage for uploads. Direct uploads for large files. Variants for images. S3/GCS for production.
Ruby
Favor modules and dependency injection over deep inheritance. Max 2-3 inheritance levels. Duck typing over type checking.
Add `# frozen_string_literal: true` to files. Prevents mutation bugs, improves performance.
Hanami for DDD-style Ruby apps. Explicit dependencies. Actions over controllers. Repositories for persistence.
Sinatra for microservices and simple APIs. Modular style for larger apps. Rack middleware compatible.
Return meaningful values or `self`. Predicates (`?`) return booleans. Bang methods (`!`) mutate or raise.
Use pattern matching, endless methods (`def x = ...`), hash shorthand `{x:}`, numbered params `_1`. Ruby 3.2+.
Security
Auth: bcrypt/argon2 for passwords, rate limiting, secure sessions/tokens. Authz: check permissions on every request, use policy objects or middleware.
Force SSL, redirect HTTP→HTTPS, secure cookies (Secure/HttpOnly/SameSite), HSTS headers.
Allowlists, not denylists. Validate type/length/format. Sanitize HTML. Parameterized queries only.
Testing
Fix failures immediately. No skipping, no "pre-existing issues." Own the codebase state—a test suite with ignored tests can't be trusted.
Test real instances. Mocking the class under test hides bugs.
Test public interfaces, inputs/outputs. Tests must survive refactoring. Don't test private methods.
1) Write failing test 2) Minimum code to pass 3) Refactor. Every line has a reason.
Mock only: external HTTP APIs, time, filesystem side effects, third-party services. Use real implementations for internal services, database, and business logic.
RSpec describe/context/it. let/let! for setup. FactoryBot for data. Avoid excessive mocking.
One logical concept per test. Multiple asserts OK if same concept. Clear test names describing behavior.
Use consistent test data setup: fixtures for stable reference data, factories for dynamic scenarios. Avoid inline object creation scattered throughout tests.
Capybara for system tests. Use semantic selectors. Wait for async. Headless Chrome in CI.
Workflow
Verify changes locally: run app, run tests, check for errors. CI catches environment issues, not basic bugs.
Format → Lint → Test before every commit. Never rely on CI for basic checks.
Language Standards (1)
Use this Ruleset
Sign in to adopt or fork this ruleset
Sign in with GitHubStatistics
- Rules
- 52
- Standards
- 1
- Projects using
- 1
- Created
- Jan 15, 2026