Reading Roundup: Less Code, Fewer Queries, Faster Rails
Five reads on trimming friction from your Rails stack, one layer at a time
Hey there,
This week’s reads all circle around a similar idea from different angles: Rails apps tend to accumulate complexity over time, and the most satisfying fixes are the ones that don’t just solve the problem but reduce the amount of moving parts. Whether that’s replacing a four-query tracking pattern with a single atomic upsert, letting Rails parallelise your test suite with one line, or picking the right language for your AI coding agent.
Rails Multi-Tenancy
37signals Lead Programmer Mike Dalessio walks through the Active Record Tenanted gem, built while trying to give every Fizzy customer their own SQLite database, with a live demo showing the conversion of an existing app takes a surprisingly small diff.
Read here
From 3 queries to 1 with Rails upsert
A daily statistics tracker using find_or_create_by, with_lock, and retry logic gets replaced with a single atomic upsert using Rails’ on_duplicate option, cutting four queries down to one with no application-level locking needed.
Read here
Group Repeated Options with with_options
ActiveSupport’s with_options groups validations, callbacks, or associations that share a condition into a single block, reducing the number of places you’d need to update if the condition changes.
Read here
Which Programming Language Is Best for Claude Code?
A 600-run benchmark across 15 languages found Ruby ($0.36, 73s), Python ($0.38, 75s), and JavaScript ($0.39, 81s) were fastest and cheapest, with statically typed languages running 1.4 to 2.6 times slower and more expensive.
Read here
From 40 Minutes to 4 with Tests Parallelization
A 10,000-test Rails suite running in 40 minutes dropped to around 4 after upgrading to Rails 8.1 and adding one line to test_helper.rb to enable Minitest’s built-in parallelization.
Read here
Quick notes and actionables
The Active Record Tenanted gem is worth knowing even if you don’t need it now: it handles the things Rails leaves up to you when working with per-tenant databases: fragment caching, Active Storage paths, Action Cable routing, and runtime safety checks that raise an error if you try to write a record to the wrong tenant’s database. The Writebook demo in the podcast shows the actual code diff is tiny. The hard part, as 37signals found, is global replication and failover, which is why Fizzy shipped with MySQL instead of SQLite.
upsert with on_duplicate is the right tool for increment-style tracking: if you’re using find_or_create_by + with_lock + update_columns for anything counter-like, the Arel.sql on_duplicate approach removes the race condition at the database level rather than the application level. PostgreSQL’s INSERT...ON CONFLICT...DO UPDATE is atomic by design. You need a unique index on the conflict columns, and callbacks won’t run, but for simple counters that’s usually fine and the trade-off is worth it.
with_options pays off at three or more shared conditions: Andy Croll’s rule of thumb is practical: two items and the block adds more lines than it saves. Three or more, especially validations with a shared if: condition, and it reduces the places you’d need to update if the condition changes. The “why not” section is worth reading too: nesting multiple with_options blocks gets hard to follow quickly, so if you find yourself doing that, reconsider the structure instead.
Ruby outperforms statically typed languages for AI coding agents, at least at prototype scale: the benchmark is quantitative and worth taking seriously. Ruby’s concise syntax and training data advantage mean Claude Code generates and iterates faster. The author is honest that larger-scale tasks, where static typing catches bugs earlier, might tell a different story, but nobody has run that experiment yet. If you’re choosing a language for a greenfield project where AI assistance is part of the workflow, this data is relevant.
Type checkers add more AI cost than you might expect: Python/mypy ran 1.6 to 1.7 times slower and more expensive than plain Python; Ruby/Steep ran 2.0 to 3.2 times slower than plain Ruby. If you’re running AI coding agents heavily and relying on type checkers for safety, the overhead compounds across every iteration.
Parallelising Rails tests is one line, but cleaning up side effects takes days: parallelize(workers: :number_of_processors) in test_helper.rb is all it takes to enable Minitest parallelisation. The actual work is fixing the tests that fail when they run concurrently, usually from shared temp files, mutable class state, or locale changes that don’t get reset. The FastRuby.io post recommends enabling randomised test order first, before parallelisation, because it surfaces most of these issues earlier and lets you reproduce them with --seed.
Plenty to chew on this week. If you end up building something off the back of any of these, I want to hear about it. P.S. You can see everything I’ve been reading at dcyoung.dev/bookmarks.
