A Rails 8 template built for working with AI coding agents.
rails new gives you a framework. Rocket Sheep gives you a codebase an agent can be productive in on the first prompt β because the patterns are already there, already documented, and already written down in a CLAUDE.md the agent reads before it touches anything.
rails new myapp \
--database=postgresql \
--template=https://raw.githubusercontent.com/PositiveControl/rails-rocket-sheep/main/template.rbRoughly three minutes later you have a running, deployable, linted, tested Rails 8 app with authentication, background jobs, SEO, and a Kamal deploy config β plus the conventions doc that keeps an agent from inventing its own.
Point an AI agent at a fresh rails new app and it will make reasonable choices. The trouble is it makes different reasonable choices every session. One feature gets a service object, the next gets a fat controller. One model uses soft deletes, the next uses destroy. Business logic lands wherever the context window happened to be pointing.
You end up as a full-time code reviewer for an codebase with no opinions.
Rocket Sheep front-loads the opinions:
- Patterns exist before the agent arrives.
ApplicationServicewith a Result struct,ApplicationFormfor multi-model forms,ApplicationComponentfor UI units,Data-based registries for fixed variant sets, PaperTrail for audit trails, Discard for the tables that genuinely need soft deletes. The agent extends existing patterns instead of inventing new ones. - The conventions are written down. A generated
CLAUDE.mdstates the rules β Slim not ERB, service objects for business logic, scopes over class methods, UUIDs everywhere β with worked examples of both the right and wrong version. - Anti-patterns are named explicitly.
CLAUDE.mdand thedocs/rules/files name what not to do β N+1 iteration, premature.to_a, hardcoded entity knowledge,accepts_nested_attributes_for, model broadcasts for single-user updates β with the correct form beside each one. Agents follow negative examples well when you actually give them some. - The pattern budget is fixed. Six sanctioned directories under
app/:services,forms,queries,policies,lib,components. A seventh requires an ADR. Sprawl is the failure mode of a pattern catalogue, so the catalogue names its own limit. - Docs have a home.
docs/system/architecture.mdfor ADRs,docs/sop/for procedures,docs/plans/for feature plans. The agent has somewhere to put what it learns, so the next session starts informed.
The result is that the tenth feature looks like the first one.
Solid Queue, Solid Cache, and Solid Cable, each on its own database, with queue.yml / cache.yml / cable.yml and the multi-database database.yml already written. No Redis anywhere in the stack.
Devise, pre-configured for Turbo (navigational_formats set correctly β the fix everyone hits on day one). Petergate for role-based access.
- UUID primary keys on every table, wired through the generators so
rails g modeldoes the right thing automatically - Discard available for soft deletes, opt-in per table β
destroyis the default, and PaperTrail can reify a destroyed record - PaperTrail with
--with-changesfor a full audit trail - Pagy for pagination, wired into
ApplicationControllersopagy(scope)works out of the box
class CreateOrderService < ApplicationService
def initialize(user:, items:)
@user, @items = user, items
end
def call
order = Order.new(user: @user, items: @items)
order.save ? success(order) : failure(order.errors.full_messages)
end
end
result = CreateOrderService.call(user: current_user, items: cart_items)
result.success? ? redirect_to(result.value) : render(:new)Result is a Struct with success?, failure?, value, errors, and a record alias. Services get log_error and log_info helpers that tag output with the class name.
ApplicationForm β ActiveModel::Model plus attributes β for the submit that writes two models, or carries fields that aren't columns:
class SignupForm < ApplicationForm
attribute :email, :string
attribute :company_name, :string
validates :email, :company_name, presence: true
def save
return false if invalid?
ApplicationRecord.transaction { ... }
true
end
endThe controller treats it exactly like a model. No accepts_nested_attributes_for, no error keys shaped like items.attributes.0.quantity.
ViewComponent, configured for Slim sidecar directories, with ApplicationComponent and four working components β AlertComponent, FlashComponent (already wired into the layout), ErrorSummaryComponent, EmptyStateComponent β each with a unit test that runs without a request.
bin/rails generate component Badge label
# app/components/badge_component.rb
# app/components/badge_component/badge_component.html.slim
# test/components/badge_component_test.rbPlans, tiers, product types β anything with a fixed set of variants and per-variant attributes:
module PlanRegistry
Plan = Data.define(:key, :name, :price_cents, :features) do
def free? = price_cents.zero?
def has_feature?(feature) = features.include?(feature.to_sym)
end
ITEMS = {
free: Plan.new(key: :free, name: "Free", price_cents: 0, features: %i[basic_access]),
pro: Plan.new(key: :pro, name: "Pro", price_cents: 2_900, features: %i[basic_access api_access])
}.freeze
class << self
def [](key) = ITEMS.fetch(key.to_sym) # unknown key raises
def all = ITEMS.values
def paid = all.reject(&:free?)
end
end
PlanRegistry[user.plan].has_feature?(:api_access)No base class to learn. A mistyped attribute raises NoMethodError instead of returning nil; an unknown key raises KeyError; and Data.define requires every member, so an attribute can't be added to one variant and forgotten on the others. app/lib/plan_registry.rb ships as the canonical shape β copy it.
Not a checkbox β an actual working setup:
public/robots.txtwith sane crawl rules- Dynamic
/sitemap.xmlfromHomeController#sitemap <meta name="description">and<link rel="canonical">in the layout, with per-pagecontent_foroverridesStructuredDataHelper#jsonld_tagplus aWebSiteJSON-LD block on every page- Integration tests in
test/integration/seo_test.rbthat assert the tags are actually there - Lighthouse CI workflow with a performance budget
Kamal 2 with a PostgreSQL accessory, a tuned multi-stage Dockerfile, a docker-entrypoint that runs migrations, and a .kamal/secrets scaffold. kamal setup && kamal deploy from a clean server.
An agent writes tests on every task, so testing is where drift compounds fastest: one session writes fixtures, the next adds FactoryBot; one session stubs an HTTP call, the next lets it hit the network in CI. Rocket Sheep pins the answers and then makes them impossible to miss.
The conventions are a routed rule, not advice. docs/rules/testing.md states one framework (Minitest and fixtures β no RSpec, no factories), which layer tests what, the fixture rules, and cassette naming. It carries applies_to: ["test/**"], and docs/rules/INDEX.md routes test/** to it β an agent about to touch a test file lands on it before writing a line, the same way editing a migration lands it on safe-migrations.
The suite runs before anything is claimed to work. "Tests first" is the first non-negotiable in the generated CLAUDE.md. /implement writes tests per logical unit and won't move on red. /pr_submit runs the full unit suite plus the system tests it selects from the branch diff. /rails_code_review reviews the diff against the rule and flags missing coverage at the severity of the code it would have covered. /test_fix exists for the run that comes back red.
Three guardrails you meet without reading anything. They are the ones that fire on their own:
- Fixtures that don't collide. Stock Rails writes two identical placeholder records into every new fixture file, and the second one violates the first unique index it meets β usually Devise's
email, on your firstbin/test. A generator override ships an empty fixture file with the reason and a worked example in the comment, sorails g modelproduces something that passes. - No live network in tests. WebMock blocks it; VCR records it.
vcr_cassette("stripe/create_customer") { ... }is available in every test case, andtest/support/vcr.rbalready filtersAPI_KEYand the Resend credential out of recordings before they reach the repo. - Slow tests are reported, every run. Slowpoke prints any test over 500ms and nothing at all when the suite is clean, so it never becomes noise you learn to scroll past.
SLOWPOKE_THRESHOLD,SLOWPOKE_MAX_RESULTS,SLOWPOKE_HISTORY, andSLOWPOKE_CItune it per run;SLOWPOKE_CI=truefails the build on a slow test once the suite is under the line. The usual cause β records built insetupthat no assertion reads β is a rule, anddocs/sop/find-slow-tests.mdis the procedure.
Working examples ship with the app rather than being left as an exercise: four ViewComponent tests that run without a request or a route, and SEO integration tests that assert the meta tags are really in the response.
Around them: bin/test wraps rails test and forces a single worker on macOS, where forked workers crash for reasons that have nothing to do with your code. Bullet flags N+1s in development, RuboCop (Rails Omakase) and Brakeman run in the same CI workflow as the suite, and letter_opener_web previews mail at /letter_opener.
Tailwind CSS, Slim templates, ViewComponent, and generic toggle_controller.js / modal_controller.js Stimulus controllers that cover most of what you'd otherwise write twice. The Turbo status contract, strict locals for partials, and the Stimulus target/value/class rules each get their own rule file in docs/rules/ β the failures they prevent are silent ones.
| Guide | What's in it |
|---|---|
| Getting Started | Prerequisites, first run, what to do in the first ten minutes |
| What's Included | Every gem and file the template adds, and why |
| Working With AI Agents | How the conventions are structured, and how to extend them |
| The Agent Workflow | The 19 slash commands, the four gates, sizing rules, setup |
| Agent Guardrails | Permissions and hooks β enforcement, not just conventions |
| Inventory & Gaps | What's included, what isn't, and what's next |
| Deployment | Kamal from zero to a deployed app on a fresh VPS |
| Comparison | Honest comparison against plain rails new, Jumpstart Pro, and Bullet Train |
| FAQ | Ruby/Rails versions, removing pieces, upgrades, licensing |
The patterns themselves β service objects, registries, form objects, components, soft deletes, audit trails, with worked examples of the right and wrong version β are documented inside every generated app as docs/rules/, one convention per file.
Each generated app also ships: docs/rules/ (37 rules + a routing index), docs/system/architecture.md, docs/system/models.md, and how-to guides for SEO, Kamal hardening, and extracting the database to a separate host.
- Ruby 3.3+ (Pagy 43 sets the floor; developed and tested on 4.0)
- Rails 8.0+
- PostgreSQL 13+ (uses built-in
gen_random_uuid(), no pgcrypto extension needed) - Node.js β only if you swap Tailwind for a bundler-based frontend
cd myapp
rails g devise User # create your User model
bin/dev # http://localhost:3000
bin/test # run the suiteThen edit config/deploy.yml, fill in .kamal/secrets, and update the sitemap URL in public/robots.txt.
See Getting Started for the full walkthrough.
Honest scope, so nobody buys the wrong thing:
- Not a SaaS starter kit. No billing, no subscriptions, no teams, no admin panel. If you want Stripe and multi-tenancy pre-built, Jumpstart Pro is the better purchase. Rocket Sheep is a foundation, not an application.
- Not a component library. ViewComponent is set up and four utility components ship (alert, flash, error summary, empty state). Buttons, tables, navs, and everything else are yours to write.
- Not a framework. Everything it adds is a plain Rails file you own and can delete. There is no gem to depend on, no upgrade treadmill, and nothing that breaks when Rails 8.1 ships.
Commercial license β see LICENSE for the full terms.
Two tiers: Single Application and Unlimited Applications. Both are perpetual one-time purchases, not subscriptions.
Apps you generate are entirely yours β no royalties, no attribution, and you may open-source them. The template is applied once and leaves behind ordinary Rails files with no runtime dependency on it. What the license restricts is redistributing the template, not anything you build with it.