CtrlK
BlogDocsLog inGet started
Tessl Logo

igmarin/rails-agent-skills

Curated library of AI agent skills for Ruby on Rails development. Covers code review, architecture, security, testing (RSpec), engines, service objects, DDD patterns, and workflow automation.

95

2.21x
Quality

97%

Does it follow best practices?

Impact

91%

2.21x

Average score across 3 eval scenarios

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

SKILL.mdrails-code-conventions/

name:
rails-code-conventions
description:
A daily checklist for writing clean Rails code, covering design principles (DRY, YAGNI, PORO, CoC, KISS), per-path rules (models, services, workers, controllers), structured logging, and comment discipline. Defers style and formatting to the project's configured linter(s).

Rails Code Conventions

Purpose: Your daily checklist for writing clean Rails code — design principles, per-path rules, logging, and comment discipline. When you are writing or reviewing any Rails code and need to know "how should this be structured?", this skill applies.

Use this skill when writing or reviewing Rails application code where design principles and per-area rules matter more than stack-specific UI choices (see rails-stack-conventions for Hotwire + Tailwind specifics).

Style source of truth: Style and formatting follow whatever linter(s) the project configures (see Linter — initial analysis below). This skill adds non-style behavior and architecture guidance only.

Linter — initial analysis

Before recommending style fixes or contradicting formatting rules:

  1. Detect which linter(s) the repo uses — e.g. RuboCop (.rubocop.yml, rubocop in Gemfile), Standard Ruby (standardrb, standard gem), frontend linters (eslint.config.*, .eslintrc*, biome.json, etc.), or scripts in package.json / bin/ (lint, rubocop, standardrb).
  2. Run the command the project documents or that matches the config (e.g. bundle exec rubocop, bundle exec standardrb, npm run lint). Do not assume RuboCop if the project uses Standard or another stack.

Quick Reference

TopicRule
Style/formatProject linter(s) — detect and run as above; do not invent style rules here
PrinciplesDRY, YAGNI, PORO where it helps, CoC, KISS
CommentsExplain why, not what; use tagged notes with context
LoggingFirst arg string, second arg hash; no string interpolation; event: when useful for dashboards
Deep stacksChain rails-stack-conventions → domain skills (services, jobs, RSpec)

Design Principles

PrincipleApply as
DRYExtract when duplication carries real maintenance cost; avoid premature abstraction
YAGNIBuild for current requirements; defer generalization until a second real use case
POROUse plain Ruby objects when they clarify responsibility; do not wrap everything in a "pattern"
Convention over ConfigurationPrefer Rails defaults and file placement; document only intentional deviations
KISSSimplest design that meets acceptance criteria and tests gate

Comments

  • Comment the why, not the what (the code shows what).
  • Use tags with enough context that a future reader can act: TODO:, FIXME:, HACK:, NOTE:, OPTIMIZE:.
# BAD — restates the method name, adds zero value
# Finds the user by email
def find_by_email(email)
  User.find_by(email: email)
end

# GOOD — explains intent and tradeoff
# Uses find_by (not find_by!) so callers can handle nil explicitly;
# downstream auth layer is responsible for raising on missing user.
def find_by_email(email)
  User.find_by(email: email)
end

Structured Logging

  • First argument: static string (message key or human-readable template without interpolated values).
  • Second argument: hash with structured fields (user_id:, order_id:, etc.).
  • Do not build the primary message with string interpolation; put dynamic data in the hash.
  • Include event: (or equivalent) for error or ops dashboards when the team uses tagged events.
# BAD — interpolation loses structure; cannot filter by user_id in log aggregators
Rails.logger.info("Processing order #{order.id} for user #{user.id}")

# GOOD — static message, structured data, filterable fields
Rails.logger.info("order.processing_started", {
  event: "order.processing_started",
  order_id: order.id,
  user_id: user.id,
  amount_cents: order.total_cents
})

Apply by area (path patterns)

Rules below apply when those paths exist in the project. If a path is absent, skip that row.

AreaPath patternGuidance
ActiveRecord performanceapp/models/**/*.rbEager load in loops; push work into SQL where appropriate; prefer pluck, exists?, find_each over loading full records unnecessarily
Background jobsapp/workers/**/*.rb, app/jobs/**/*.rbClear worker/job structure, queue selection, idempotency, structured error logging (see rails-background-jobs for Active Job / Solid Queue / Sidekiq depth)
Error handlingapp/services/**/*.rb, app/lib/**/*.rb, app/exceptions/**/*.rbDomain exceptions with prefixed codes where the team uses them; rescue_from or base handlers for API layers as conventions dictate
Logging / tracingapp/services/**/*.rb, app/workers/**/*.rb, app/jobs/**/*.rb, app/controllers/**/*.rb, app/repositories/**/*.rbStructured logging; add APM trace spans and tags (e.g. Datadog) for key operations when the stack includes them
Controllersapp/controllers/**/*_controller.rbStrong params; thin actions delegating to services; watch IDOR and PII exposure (see rails-security-review)
Repositoriesapp/repositories/**/*.rbAvoid new repository objects unless raw SQL, caching, a clear domain boundary, or external service isolation justifies it; document why in code
RSpecspec/**/*_spec.rbFactoryBot; prefer request specs over controller specs; use env: metadata (or project equivalent) for ENV changes; prefer let over let! unless the example requires eager setup; avoid before for data when let or inline factories are clearer
Serializersapp/serializers/**/*.rbIf using ActiveModel::Serializer (or similar): explicit key: mapping; avoid N+1; pass preloaded associations via options when applicable
Service objectsapp/services/**/*.rbSingle responsibility; class methods for stateless entry points, instance API when dependencies are injected; public methods first; bang (!) / predicate (?) naming as appropriate (see ruby-service-objects)
SQL securityRaw SQL anywhereNo string interpolation of user input; use sanitize_sql_array / bound parameters; whitelist dynamic ORDER BY; document why raw SQL is needed

RSpec and let_it_be (test-prof)

  • Only use let_it_be if the project already depends on the test-prof gem (check Gemfile / Gemfile.lock). Search before recommending it.
  • If test-prof is not present, follow rspec-best-practices with let as the default; use let! only when lazy evaluation would break the example (e.g. callbacks, DB constraints that must exist before the action). Explicit setup is fine when clearer. Do not require adding test-prof / let_it_be unless the user asks to introduce it.

HARD-GATE: Tests Gate Implementation

When this skill guides new behavior, the tests gate still applies:

PRD → TASKS → TEST (write, run, fail) → IMPLEMENTATION → …

No implementation code before a failing test. See rspec-best-practices and rails-agent-skills.

Common Mistakes

MistakeReality
Duplicate linter rules in proseThe project's configured linter(s) are authoritative for style; this skill is for behavior and boundaries
Assuming RuboCop without checkingDetect and run the linter the repo actually uses
let_it_be in every projectUse only when test-prof is already a dependency
Defaulting to let! everywherePrefer lazy let; reserve let! for cases that need eager setup
New app/repositories for every queryActiveRecord is the default data boundary unless there's a documented reason
Interpolated log messagesLoses structure; use hash payload — see logging example above
Comments restating method namesAdds noise; comment intent and tradeoffs — see comment example above

Integration

SkillWhen to chain
rails-stack-conventionsStack-specific: PostgreSQL, Hotwire, Tailwind
ddd-rails-modelingWhen domain concepts and invariants need clearer Rails-first modeling choices
ruby-service-objectsImplementing or refining service objects
rails-background-jobsWorkers, queues, retries, idempotency
rspec-best-practicesSpec style, tests gate (red/green/refactor), request vs controller specs
rails-security-reviewControllers, params, IDOR, PII
rails-code-reviewFull PR pass before merge

rails-code-conventions

README.md

tile.json