Rails Standards

by Convext

Public

Ruby on Rails coding conventions and patterns

ruby rails

Rules (52)

Config

high Use environment variables for configuration

Config in env vars, not code. Never commit .env. Commit .env.example. Never hardcode secrets.

Database

critical Never store sensitive data in plain text

Passwords: bcrypt. API keys: encrypt. PII: field-level encryption. Never log sensitive data.

Dependencies

critical Always use latest dependency versions

Use latest stable versions. Fix breaking changes—don't avoid upgrades. Check official registries.

Formatting

medium Use StandardRB for Ruby formatting

StandardRB: no config, no debates. RuboCop with opinionated defaults. Just run `standardrb --fix`.

Git

medium Atomic commits with Conventional Commits

Small, focused commits. Use: feat/fix/refactor/test/docs/chore. Each commit independently deployable.

Linting

high Use RuboCop for Ruby linting

RuboCop with .rubocop.yml config. Inherit from rubocop-rails, rubocop-minitest. Auto-correct safe cops.

Llm Behavior

critical Own all code in the repository

You wrote every line. No 'pre-existing issues'—only issues you haven't fixed yet.

critical User defines success

User and tests define done. Don't redefine scope or declare partial progress as complete.

critical No rationalizations

No excuses: 'pre-existing', 'unrelated', 'tedious', 'for now'. Recognize and continue working.

Package Manager

high Use Bundler for Ruby gems

Gemfile + Gemfile.lock. `bundle install --deployment` in prod. Groups for dev/test. `bundle exec` for commands.

Rails

critical Use Strong Parameters correctly

Whitelist attributes explicitly. Never `.permit!`. Use `require(:model).permit(:field)`.

critical Use Rails built-in authentication, never Devise

Use `has_secure_password`, `authenticate_by`, `generates_token_for`. Never use Devise/Sorcery/Clearance.

critical Use Rails credentials for secrets

Rails.application.credentials for secrets. `rails credentials:edit`. Environment-specific credentials. Never commit master.key.

high Use database constraints

Add NOT NULL, UNIQUE indexes, foreign keys, check constraints. Don't rely only on ActiveRecord validations.

high Use background jobs for slow operations

Email, file processing, API calls, reports → background jobs (SolidQueue/Sidekiq). Keep requests <200ms.

high Use Pundit for authorization

Pundit policies for authorization. `authorize @record` in controllers. Policy classes match models.

high Use Minitest for Rails testing

Minitest only—no RSpec. Rails default, fast, simple, fixtures-integrated.

high Use service objects for complex operations

Service objects for multi-model operations. Single public method (call). Return Result/Response objects. Keep models focused on persistence.

high Use Sidekiq for background jobs

Sidekiq for heavy background work. Redis required. Use perform_later. Retries with exponential backoff.

high Use Hotwire for Rails interactivity

Turbo Drive for SPA-like nav. Turbo Frames for partials. Turbo Streams for real-time. Stimulus for JS sprinkles.

high Use SolidQueue for Rails 8 jobs

SolidQueue: Rails 8 default job backend. Database-backed. No Redis needed. Mission Control for monitoring.

high Use Hotwire for interactivity

Use Turbo (Drive/Frames/Streams) + Stimulus before React/Vue. JS frameworks only for complex client state.

high Fat models, skinny controllers

Controllers: auth, params, call service, render. Logic in models/services/form objects/query objects.

high Avoid N+1 queries

Use `includes`/`preload`/`eager_load` for associations. Use Bullet gem in development.

medium Use Propshaft for Rails assets

Propshaft for asset pipeline. Simpler than Sprockets. No compilation, just fingerprinting. Rails 8 default.

medium Use SolidCache for Rails caching

SolidCache: database-backed cache. No Redis needed. Good for most apps. Rails 8 default.

medium Use SolidCable for Action Cable

SolidCable: database-backed pub/sub for Action Cable. No Redis needed. Rails 8 default.

medium Use Action Cable for WebSockets

Action Cable for WebSockets. Channels for subscriptions. Redis adapter for multi-server. Use Turbo Streams when possible.

medium Use has_many :through over HABTM

Always `has_many :through` with explicit join model. Never `has_and_belongs_to_many`.

medium Use scopes for common queries

Define scopes for reusable queries. Enables chaining: `Order.recent.completed.for_user(user)`.

medium Use Importmap for Rails JavaScript

Importmap for JS without bundling. Pin packages from CDN or vendor. Works with Hotwire. No node_modules.

medium Use Active Storage for file uploads

Active Storage for uploads. Direct uploads for large files. Variants for images. S3/GCS for production.

Ruby

high Prefer composition over inheritance

Favor modules and dependency injection over deep inheritance. Max 2-3 inheritance levels. Duck typing over type checking.

low Use frozen string literals

Add `# frozen_string_literal: true` to files. Prevents mutation bugs, improves performance.

medium Use Hanami for clean Ruby architecture

Hanami for DDD-style Ruby apps. Explicit dependencies. Actions over controllers. Repositories for persistence.

medium Use Sinatra for simple Ruby APIs

Sinatra for microservices and simple APIs. Modular style for larger apps. Rack middleware compatible.

medium Explicit return values in methods

Return meaningful values or `self`. Predicates (`?`) return booleans. Bang methods (`!`) mutate or raise.

medium Use Ruby 3+ features

Use pattern matching, endless methods (`def x = ...`), hash shorthand `{x:}`, numbered params `_1`. Ruby 3.2+.

Security

critical Implement proper authentication and authorization

Auth: bcrypt/argon2 for passwords, rate limiting, secure sessions/tokens. Authz: check permissions on every request, use policy objects or middleware.

critical Use HTTPS everywhere

Force SSL, redirect HTTP→HTTPS, secure cookies (Secure/HttpOnly/SameSite), HSTS headers.

critical Validate and sanitize all user input

Allowlists, not denylists. Validate type/length/format. Sanitize HTML. Parameterized queries only.

Testing

critical Never ignore failing tests

Fix failures immediately. No skipping, no "pre-existing issues." Own the codebase state—a test suite with ignored tests can't be trusted.

critical No mocking the class under test

Test real instances. Mocking the class under test hides bugs.

high Test behavior, not implementation

Test public interfaces, inputs/outputs. Tests must survive refactoring. Don't test private methods.

high TDD: Red -> Green -> Refactor

1) Write failing test 2) Minimum code to pass 3) Refactor. Every line has a reason.

high Mock only at external boundaries

Mock only: external HTTP APIs, time, filesystem side effects, third-party services. Use real implementations for internal services, database, and business logic.

medium Use RSpec for Ruby BDD

RSpec describe/context/it. let/let! for setup. FactoryBot for data. Avoid excessive mocking.

medium One assertion per test (conceptually)

One logical concept per test. Multiple asserts OK if same concept. Clear test names describing behavior.

medium Use fixtures or factories for test data

Use consistent test data setup: fixtures for stable reference data, factories for dynamic scenarios. Avoid inline object creation scattered throughout tests.

medium Use Capybara for Rails integration

Capybara for system tests. Use semantic selectors. Wait for async. Headless Chrome in CI.

Workflow

critical Code must work locally before pushing

Verify changes locally: run app, run tests, check for errors. CI catches environment issues, not basic bugs.

high Run formatter, linter, and tests before commit

Format → Lint → Test before every commit. Never rely on CI for basic checks.

Language Standards (1)

ruby
Framework: rails Linter: rubocop Formatter: standardrb

Use this Ruleset

Sign in to adopt or fork this ruleset

Sign in with GitHub

Statistics

Rules
52
Standards
1
Projects using
1
Created
Jan 15, 2026