Is Rails token efficient code? Counting what an agent has to read
September 24, 2026
Keep reading
- Rails vs Rust in 2026, after HEY moved its backend
- Testing what an agent wrote
- Reviewing Rails an agent wrote
- Pencils down: what DHH actually said at Rails World 2026
- Cursor rules for a Rails codebase
- Convention over configuration is a context argument now
- Claude Code in a Rails codebase
- AGENTS.md for a Rails app
Most ai context rails advice is about what to put in the context. The harder question is what a codebase forces an agent to read before it can answer anything, and that number is measurable. The @rails account's positioning line makes the claim directly: "Ruby on Rails scales from PROMPT to IPO. Token-efficient code that's easy for agents to write, and still beautiful for humans to review."
Worth taking seriously, and worth counting rather than agreeing with. What follows is a rails token count of a real Rails 8.1.3.1 application, the SaaS boilerplate this site sells, measured file by file.
Counting a real application instead of arguing about it
Every number below came from
tiktoken'so200k_baseencoding, run over the working tree. That encoding is OpenAI's rather than Anthropic's or Google's, and the absolute counts would shift by a few percent on another tokenizer. The ratios between file types, which is what the rest of this page turns on, do not.app/views/**/*.erbspec/**/*.rbapp/models/**/*.rbconfig/**(rb and yml)Gemfile.lockapp/controllers/**/*.rbapp/services/**/*.rbdb/schema.rbapp/,config/andspec/together are 292,397 tokens. A frontier coding model now ships a million token window, so the old framing, that a Rails app does not fit, is dead. A rails context window is no longer one budget for the repository. It is one budget per turn, and a twenty turn session that re-reads the same trees pays for them twenty times.The ordering is the interesting part. Markup outweighs the models, controllers and services combined by better than two to one. The specs on their own cost more than all three. Whatever Rails is doing well or badly, it is doing it to the smallest rows in that table.
Characters lie and lines lie worse
Chars per token is not a constant, and treating it as one is how people end up surprised by a bill.
config/locales/en.ymlapp/services/**/*.rbapp/controllers/**/*.rbapp/models/**/*.rbspec/**/*.rbdb/schema.rbviews containing <svgGemfile.lockA byte of
Gemfile.lockcosts 2.2 times what a byte ofen.ymlcosts. The reason is that version strings, hashes and hyphenated gem names shatter into single character tokens, while English prose and Ruby identifiers do not. SoGemfile.lockis 7.5 times the size ofGemfileon disk and 14 times its token cost, and any agent that globs the repository root to "check the dependencies" has spent 19,492 tokens answering a question the 1,397 tokenGemfileanswers better.The practical consequence is that file size is not a proxy for context cost, and lines of code are worse than file size.
db/schema.rbis 345 lines.app/views/admin/_icon.html.erbis 50. The 50 line file costs more.The icon partial that outweighs the schema
app/views/admin/_icon.html.erbis 8,988 bytes and costs 5,638 tokens.db/schema.rbis 14,536 bytes and costs 3,972. A file 62 percent of the size costs 42 percent more.All of that partial is Heroicons path data behind a
casestatement. One path element:121 tokens. For comparison,
has_many :messages, dependent: :destroyis 10 tokens andclass User < ApplicationRecordis 5. One icon costs what twelve associations cost.Across the view tree, 18 of 131 files contain an
<svg, and those 18 hold 35,956 of the 124,934 view tokens. Fourteen percent of the files, 29 percent of the cost. Strip them out and the remaining 113 views run at 3.41 characters per token instead of 2.93.Nothing about this is a Rails problem, which is the point. A team that moves icon paths into a sprite, a helper backed by a hash, or anything else an agent can skip has cut 29 percent off its most expensive tree without touching a line of application logic. The same is true of a Tailwind utility string:
<div class="flex items-center justify-between gap-3 rounded-lg border border-gray-200 px-4 py-3">is 25 tokens of styling wrapped around content the agent came for.Where the token efficient code claim holds
Terseness is not the mechanism, and people who defend Ruby on line count are defending the wrong thing.
has_many :messages, dependent: :destroyis cheap in a context window for a reason that has nothing to do with its length: the agent already knows what it does. Every consequence of that line, the reader method, the collection proxy, the callback that deletes children, the foreign key convention, was in the training data thousands of times. Zero tokens of the repository are spent explaining it.An in-house DSL of the same shape gets none of that.
broadcasts_to :account, via: :fanoutis 11 tokens, one more than thehas_many, and an agent has to open its implementation to learn anything at all, which is frequently a file larger than the model that called it. Same terseness, opposite cost.So the honest version of the claim is not that Rails is terse. Rails is token efficient code because its vocabulary is pretrained and its file layout is predictable enough that an agent can find
app/models/user.rbwithout a directory listing. Why conventions are the context is the same argument from the framework's side.What would overturn this: a measurement showing that agents open framework source in a Rails codebase at a similar rate to a bespoke one. If an agent reads
activerecordto understandhas_many, the pretraining premise is wrong and the cost is just hidden a level down.Where the claim stops holding
The pretraining premise has an expiry date, and the enum syntax is the cleanest example in Rails. Defining an enum with keyword arguments was deprecated across Rails 7 and is gone in 8. The signature in ActiveRecord 8.1.3.1 is:
nameis positional and required. Run the old form against it and you getArgumentError: wrong number of arguments (given 0, expected 1..2), which is exactly what a model that learned Rails from a decade of pre-7.0 code will write. The current form,enum :role, { member: 0, admin: 1 }, default: :member, costs 20 tokens. The wrong form costs 20 tokens and a failing boot. A convention is only free when the weights hold the current one, and freshly changed conventions are the most expensive text in the repository because they cost tokens twice, once to write wrong and once to correct.The second failure has nothing to do with the framework.
app/models/setup.rbin this application is 222 lines and 2,623 tokens, and it holds a table of SMTP providers, a field definition, a step definition and the wizard object that walks them. An agent asked to change the SMTP list reads all four, because from outside the file there is no way to tell which 40 lines matter. Rails does not prevent that file, and the phrase "convention over configuration" does nothing about it. A fat model with three hundred lines of callbacks is not token efficient code in any framework, and the boilerplate in a repository is not what makes it expensive.The part of the claim DHH is not betting on
Reported out of the Rails World 2026 keynote in Austin, which nobody here watched, HEY 2.0 is being rebuilt as native apps on every platform over a Rust backend, written by agents under the direction of programmers and designers. DHH's line on the language, as it was quoted afterwards, was that Rust is amazing "if you never, ever, EVER have to look at it yourself."
Read that next to the @rails sentence and the two halves come apart. "Easy for agents to write" and "beautiful for humans to review" are not the same requirement, and the Rust backend is what the first half looks like when you drop the second. Rails is the bet that you still want to read it. The page you are on is only worth writing under that bet, because if nobody reviews the code then the cheapest representation wins and it is not Ruby. What DHH actually said covers the keynote itself.
Five changes that measurably shrink the read
All five were measured on the application above, and all five state what they cost.
annotaterbwrites a schema comment block into the model file. Average cost across the 21 tables here is 170 tokens per table. The question it answers, "what columns does this have", otherwise pulls in the 3,972 tokendb/schema.rbalongside the 1,201 token model. The cost is staleness: the annotation is regenerated by a rake task afterdb:migrate, and a branch that skips it ships a comment that lies, which is worse than no comment.config/locales/en.yml. A copy question then reads one 11,682 token file instead of grepping 131 views, and<%= t("admin.users.index.title") %>is 11 tokens in the view. The cost is that an agent changing a screen now reads two files instead of one, so this trades many shallow reads for two deep ones and only wins if copy questions outnumber layout questions.AGENTS.mdhere is 66 lines and 1,086 tokens, and it names where forms, services and copy live so the agent does not globapp/. AGENTS.md for a Rails app goes through what belongs in it. The cost is a second source of truth that no test checks.Gemfile.lockinto context. 19,492 tokens that answer a resolver's question rather than yours. There is no cost to this one.The rule underneath all five: cheapness comes from what the agent can skip, not from what it reads quickly.
What this page does not cover
Prompt caching, which genuinely changes the arithmetic. A prefix that does not change between turns is billed at roughly a tenth of a fresh read across the major providers, so the twenty turns paying twenty times above is a ceiling rather than a bill. The files an agent is actively editing are the ones that do change, and those are the ones that miss the cache.
Retrieval. Embedding a repository and fetching the relevant chunks is a real answer to the same problem, with its own failure mode, and it is a different page.
Test suites, which are the second largest number in the first table and were left alone here. 76,611 tokens of specs are a cost an agent pays and also the main thing that makes its output checkable, and treating that number as waste gets the trade backwards. Testing what an agent wrote takes up that side of it.
Whether any of this changes what a model produces. Everything measured here is input cost and nothing more. The claim that a smaller context yields better code is plausible, widely repeated, and not something this application has run an experiment on.