Writing & video
Ruby craft, one idea at a time.
Latest
ActionController::Live gives you SSE in six lines, and one open stream blocked every other request in that Puma process for as long as it stayed open. Measured on Rails 8.1.4: 3.0 seconds of blocking from a 4 second stream, while a plain sleeping action blocked nothing at all.
Read moreWhat ruby_llm actually puts in your Rails app: six tables behind one acts_as_chat line, an error hierarchy worth failing over on, and seventeen providers reached through one code path that stops being uniform at the credentials.
One line gives you a parent and its children from a single form, in four queries. Measured on Rails 8.1.4: reject_if all_blank discarded two of three submitted children with no error anywhere, and passing another parent's child id raises rather than letting you edit it.
after_save fires inside the transaction and after_commit fires outside it, which decides whether your background worker can see the record it was told about. Measured on Rails 8.1.4: the real callback order, what a nested transaction does, and why raising in after_commit leaves the row behind.
A template handler returns Ruby source code, not HTML. Measured on Rails 8.1.4: the five extensions registered out of the box, the one that silently catches your typos, and a working custom engine in a single lambda.
STI stores your class name in a column, which is fine until it isn't. Measured on Rails 8.1.4: loading a row whose class no longer exists raises SubclassNotFound while count keeps working, and becomes changes the Ruby object without touching the type.
Three ways to touch a lot of rows, measured on Rails 8.1.4 over 2,500 records. find_each walks an id cursor rather than paging with OFFSET, in_batches hands you a relation so update_all is one query a batch, and insert_all did in 1 query what 500 create! calls did in 1,000.
A role column, a roles table, or a gem. Measured on Rails 8.1.4: user.admin? costs zero queries on a column and nine on a join table across eight users, what includes gets that back, and the enum renumbering that silently promotes everybody.
A deleted_at column and default_scope look like a twenty minute feature. Measured on Rails 8.1.4: the uniqueness validator ignores your scope on purpose, belongs_to quietly returns nil for every child of a discarded parent, and a plain unique index locks the name forever.
to_param plus parameterize gives you /posts/12-hello-world in two lines. Measured on Rails 8.1.4: a Japanese title parameterizes to an empty string, find accepts any text you append to the id, and nothing stops two rows sharing a slug.
A sitemap is one controller action and a builder template. Measured against this site's own live sitemap: 190 bytes per URL, a 14.8x gzip ratio, and arithmetic showing the 50,000 URL cap binds roughly five times sooner than the 50 MB one.
generates_token_for builds a magic link in four lines, and the token it hands you is reusable and byte-identical every time you generate it. Measured on Rails 8.1.4: three sign-ins from one link, what actually invalidates it, and the one-line block that makes it burn.
A cross origin POST reaches your action and runs it whether or not rack-cors is installed. Measured on Rails 8.1.4: the request executed and returned 200 for a disallowed origin, rack-cors withheld the headers and nothing else, and the browser is the only thing doing any blocking.
Action Mailbox routes inbound mail to a class the way the router routes requests. Measured on Rails 8.1.4: the five statuses an email moves through, why a bounce is a status and not an exception, and the thirty day incineration nobody reads about until the storage bill arrives.
About 800 million for the rails gem, and the number is softer than it looks. Measured against the RubyGems API on 2026-10-12: activesupport is 1.81x higher, one 2014 version holds 5.1 percent of the total, and asking from Ruby instead of curl returns a different answer because the CDN varies on Accept-Encoding.
No 8.2 gem exists on RubyGems, not even a prerelease, checked on 2026-10-12. What the release history does say: seven minors since 6.0 with gaps from 90 to 659 days, 355 days elapsed since 8.1.0 against a median of 371, and a beta that landed 48 days before the last one and has no counterpart yet.
Rails ships three threads and one process, and whether that is right depends on something you can measure in an afternoon. Benchmarked on puma 8.0.2: threads take an IO bound endpoint from 9.4 to 103.3 req/s and take a CPU bound one from 57.6 to 57.7, which is the GVL to the decimal place.
The error prints three different messages and two of them never mention a token. Traced through actionpack 8.1.4 with every message captured verbatim: the four term predicate that decides, why a correct 86 character token still fails when the session cookie is gone, why two forms on one page carry two different tokens, and why ActionController::API never raises it at all.
Rails guards signed and encrypted cookies at 4096 bytes and does not guard a plain one at all. Measured on actionpack 8.1.4: an 8000 byte plain cookie ships without a word, the largest encrypted payload Rails accepts lands at 4310 to 4394 bytes on the wire depending on the run, and cookies.delete is a no-op when the cookie was not in the request.
The rails 8.1.3.1 gem ships two files and neither is Ruby. What the boundary between the language and the framework actually is, how to find which side any method in your code is on, what Active Support does to String, and what loading the whole thing costs in files and megabytes.
The two loaders a Rails 8.1 app runs, why a misnamed file serves HTTP 200 in development and refuses to boot in production, the error message you only get once, and the constant captured at boot that keeps answering the code the file used to hold.
Rails escapes five characters in every <%= %> and that is enough for a text node and not enough for a URL attribute, an unquoted attribute, a <script> body or a translation key ending in _html. Measured on Rails 8.1.3.1, with the Brakeman 8.0.6 output and the 22 tests.
Rails ships four Active Record adapters and rails new picks SQLite. What that choice costs, measured on one machine against SQLite, PostgreSQL 17.7 and MySQL 26.7.0: throughput that does not separate them, one failed migration that only rolls back on two of the three, and the lock that ends the SQLite story.
Rails already mounts a WebSocket server at /cable and a fresh 8.1 app has no app/channels directory at all. The real frame trace, the 404 that is an Origin check, what the async adapter silently drops, measured delivery latency on Solid Cable, and what 500 open connections cost a 3-thread Puma.
Three ways to put Vue 3 in front of Rails, all three built and measured on one laptop: importmap with no build step, Vite with real .vue files, and Inertia with no API at all. What each one puts on the wire, the Turbo lifecycle that breaks all of them, and the two failures that leave a green test suite.
What the VS Code marketplace serves for "ruby on rails" against what Ruby LSP 0.10.6 actually answers over the wire: the schema in the hover, the route code lens, the 4.8 second index, the 493 MB, and the two places it silently returns nothing.
Both stacks installed on one laptop with the same 5000 products in the same MySQL: where a custom field lives, the meta_query that answers 4460 instead of 500, the transaction API wpdb does not have, the back office Rails answers 404 to, and 12 queries against 1 for the same ten-row list.
The same filterable 600-row table built twice on one laptop, once with Rails and Hotwire and once with Rails serving JSON to a React 19.3 app: what arrives in the first response, what the browser downloads, the CORS 200 that renders zero rows, and where the milliseconds actually go when you filter.
Both frameworks installed on one M2 Max, the same scaffold, the same PostgreSQL, the same three endpoints under ApacheBench. Where Puma's 3 threads put the ceiling, what the GVL costs on a CPU endpoint, why 300 requests to a Rails counter never get past 35, and what Ecto refuses to let you write.
Both frameworks installed on one laptop and pointed at the same PostgreSQL: the per-request boot PHP pays and Rails does not, why Eloquent writes a blank title that Active Record refuses, the lazy-loading guard that skips single-row results, what php artisan optimize buys and what it breaks, and the same JSON endpoint benchmarked on both.
Both Odin Project paths read off the site and counted course by course, then the same CRUD feature built twice on one laptop: a Rails 8.1.4 scaffold against 112 lines of Express 5.2.1 and pg. The bytes of JavaScript a Rails page ships, the escaping test that fails on one side, and ApacheBench on both.
Both frameworks installed on one laptop and driven at the same PostgreSQL: where each one keeps the schema, what makemigrations refuses that Rails lets you run, the admin Rails does not have, Django 6.1's background tasks with no worker, and the same JSON endpoint benchmarked on both.
A Rails 8.1 process measured under a hard memory cap: 180 MB resident before it serves anything, 55 MB for each extra Puma worker, OOM-killed at 160 MB and alive at 192. With the cgroup numbers, the ApacheBench output, and a jemalloc result that went the wrong way three times.
The one request a browser makes for a video, why Active Storage answers it with 416 above 100 MiB, what the disk route does differently, and what three slow viewers do to a three-thread Puma. With the Chrome error string, the ab runs and the RSS numbers.
Why bumping Ruby first is the wrong move, measured: the gemspec declares a Ruby floor and no ceiling, Bundler resolves rails 7.0.8.7 against ruby 4.0.5 and exits 0, and then the app dies on a missing Logger. With the one rung that runs on both Rubies.
The eight test classes Rails 8.1 ships, what bin/ci actually runs, the per-example cost of each layer measured on a 1665-example suite, the authorisation hole Brakeman scores clean, and the two ignore files that give the same gem audit two different answers.
What rails new --css=tailwind actually installs, which files the v4 scanner reads and which it silently skips, why a class built with string interpolation never gets a rule, and the vendor/bundle directory that took one build from 39ms to 985ms. With the probe table and the passing test.
What one rails new gives a startup in 2026, counted on a fresh app: 16 gem lines, 114 gems, no Redis, no Node. Then the three things the generated app does not do, including the password reset that answers 302 and lands in solid_queue_failed_executions with nobody watching.
What is actually in the first response from a Rails 8.1.3.1 app against a client-rendered page of the same data, what ERB costs per row, the one view helper that was 94 percent of the render, and why the default ETag on every page with a CSRF token is worthless. ApacheBench output and a passing Minitest file.
What Rails 8 actually configures on a SQLite connection, the one line that made it survivable, how many writes per second a generated app really does through the full stack, and the two failures that cost real requests: a long write transaction from a second process, and a schema cache that is loaded lazily.
Building the five REST actions with rails new --api on Rails 8.1.3.1: the routes resources actually draws, why params.expect silently swallows unknown keys, the 400 that came back before the 201 did, and the empty text/html body every error returns in production.
What db:reset, db:migrate:reset, db:truncate_all and db:prepare each drop, rebuild and forget on rails 8.1.3.1, the unrun migration that turns a reset into a plain wipe, and the failed db:migrate:reset that overwrote a 519 line schema.rb with 17. Every transcript pasted from the run.
The four call shapes of render, why a bare partial name resolves against the controller prefixes and not the directory of the calling template, what the collection form actually buys in milliseconds, and the two errors strict locals turn into exceptions. Measured on Rails 8.1.3.1.
The five lines of actionpack that turn render json: into a response body, why only: works on a record and is ignored on a String, the two Rails 8.1 changes that alter the bytes without touching your controller, and where the time in a JSON response really goes.
render html:, render plain:, render inline: and render template: are four different objects inside Action View, and they disagree about escaping, layouts and Content-Type. Measured on Rails 8.1.3.1, with the injection that html_safe opens and the cost of an inline template.
Rails 8 needs no Redis, so if you are adding one anyway, this is what it is actually wired to: the cache store config line, the pool of five, the failsafe that turns every Redis error into a cache miss, the counter that read returns nil for, and the maxmemory policy that is wrong out of the box.
A 60 ms Rails page taken apart by ActiveSupport::Notifications, StackProf, Vernier and rack-mini-profiler, with what each one could name, what each one cost, and the two that gave a wrong answer on this machine. Every number from one laptop, with the conditions.
One real page profiled from 42.2 ms down to 11.3 ms, a missing index measured at 36.390 ms against 0.053 ms, and the Puma thread count that changed throughput by less than 3 percent. Every number from this laptop, with the conditions.
Rails measured against itself on one laptop: a bare Rack app, an empty Rails endpoint and a real page, with ApacheBench output and the conditions. Where the 34 milliseconds of a real request actually go, what four Puma workers bought, and why the framework benchmark numbers everybody quotes answer a question your application does not ask.
Rails ships no PDF renderer, so the choice is Prawn drawing primitives or headless Chrome printing your ERB. Measured side by side on the same invoice: 102.8 ms against 716.8 ms, 40114 bytes against 111038, a total that prints on all three pages, and an ETag that changes 15 times out of 20.
What rails new -d mysql actually writes, why host: localhost under mysql2 opens a unix socket and ignores your port, why caching_sha2_password refuses the connection after the password is right, and the four Rails behaviours that change the day the adapter is not pg. Run against MySQL 26.7.0.
A generated Rails 8.1.4 app with authentication and one scaffold has a red test suite before you write a line, and no way for anyone to sign up. The seven failures and one error, both one-line fixes, the twenty-eight lines that close the registration hole, and the duplicate address that 500s if you forget one of them.
Why default_scope is not a tenant boundary, what a PostgreSQL row level security policy stops that it cannot, the two reasons the policy silently did nothing on the first run, and the pg_class and lock table numbers where schema per tenant stops. With a passing Minitest file.
What one process actually buys: a transaction that rolls back both domains, measured against the same write over HTTP. Plus the directory under app/ that silently defines the wrong constants, why isolate_namespace let this site's own engine reach into Setting nine times, and the two things packwerk cannot see.
The same page, the same machine: Rails reported a 63 ms median while the client waited 787 ms. What process_action.action_controller actually carries, the queue time middleware Puma's own docs tell you to write, and the instrumentation subscriber that 500s the request it was measuring.
The measurement that tells a leak apart from a process that merely grew, three ObjectSpace dumps that name the line, what allocation tracing costs in RSS and throughput, and why clearing the leak left RSS at exactly the same 115.7 MB. With every GC.stat line pasted from the run.
Ruby comes from a version manager on every distribution, so the distro decides something else: which OpenSSL your Ruby is compiled against, whether headless Chrome exists for your system tests, and how old libpq and libvips are. Built Ruby 4.0.5 from source on Ubuntu 24.04 and Fedora 43 and got two different crypto libraries, with a 15-example Minitest suite over every claim.
Hotwire is not a gem you add, it is two gems rails new already wrote. What a default Rails 8.1 app actually ships, the one HTTP rule Turbo imposes that Rails did not have before, why the same 200 response is fine inside a turbo-frame and discarded outside one, and what each rung of the ladder costs in bytes. Seven browser tests, all of them run.
Where a Rails 8.1 app can actually live, measured: the container replacement that took Post.count from 1 to 0 with no error anywhere, the three minutes of sleep that skipped three cron runs and delayed a job by 2 minutes 23 seconds, and what a quarter of a vCPU does to a cold start.
What the macro actually declares on activerecord 8.1.3.1: a private join model, a hidden has_many, and three options it accepts and throws away. With the SQL, the orphan rows nobody deletes, and the two-line move to has_many :through that needs no migration.
What rails g graphql:install actually writes, the 61 queries it takes to render 60 posts and their authors, the 145-character query that returns 1.3 MB while passing every default limit, and the measured cost of the endpoint against a plain Rails JSON action.
Four lines from older Rails tutorials that raise NoMethodError on Rails 8.1.4, three that look just as old and still work, the three paths that date a tutorial without reading a word of it, and the three errors of a first hour quoted in full. Run on a freshly generated application, with the passing test that records it.
A zero padded SKU leaves a caxlsx export as the number 37, and the reason is one regex in cell.rb. What the two xlsx writers put in the file, what 200000 rows cost a Puma worker, what comes back through roo, and the Zlib::BufError that only appears when something else in the process shells out.
There is no Rails image on Docker Hub and there has not been one since 2017. Rails writes you a Dockerfile instead: 77 lines, three stages, 565 MB. Built, measured layer by layer, run until /up answered 200, pushed multi-arch to a registry, with an 11-example Minitest suite against the built image and the secret that ships inside it.
What a bind-mounted Rails app actually spends its time on, the one configuration line that gives most of it back, why postgres:18 refuses the volume every tutorial writes, and the DATABASE_URL in compose that lets bin/rails test drop your development database. Measured on a scratch app, with a 6-example Minitest suite that runs in the container.
Why the Dockerfile rails new writes cannot run your development environment, the one flag in compose.yaml that silently turns off db:prepare, what depends_on does and does not wait for, and the measured cost of a bind mount on macOS. Built from a scratch app, with a 12-example Minitest suite that runs inside the container.
The two files Render needs from a Rails 8 app, the build failure that happens before the database is touched, and why the four connections in a generated config/database.yml turn one Render Postgres into a green deploy with no Solid tables in it.
What the heroku/ruby buildpack actually does to a Rails 8.1 app, why it ignored the .ruby-version in the repo, and the release phase line that has to exist because db:prepare silently creates none of the Solid tables when Heroku gives you one database.
destroy and delete printed side by side on activerecord 8.1.3.1: the SQL each one sends, why destroy_all is 3001 statements and not one transaction, what every dependent: option emits, the two orders of magnitude a foreign key buys, and the delete button that quietly does a GET.
What config/database.yml actually becomes at boot on Rails 8.1: the shared: block, the DATABASE_URL merge that keeps your file's password, why pool: is now max_connections:, which of the 50 libpq keys reach PostgreSQL, and the misspelling that reports as a credentials error. Printed from a scratch app with a 12-example Minitest suite behind it.
A Ruby on Rails database query does nothing until something asks for rows, and which method you call decides whether that is one COUNT or ten thousand objects. With the per-request query cache serving a stale row, the development default that turns prepared statements off, and the select string that runs a DROP TABLE.
A Rails 8.1.3.1 app booting inside the official ruby4.0 Lambda base image: the nine-line handler, the Bundler::ReadOnlyFileSystemError raised at config/boot.rb:3 before Rails loads, init duration measured at seven CPU allocations against the documented 10-second Init cap, and what the billed milliseconds actually cost.
Basecamp's rails-active_search 0.1.0 run against PostgreSQL 17.7 and SQLite FTS5 on the same 50001 rows. The SQL it generates, why the two GIN indexes in its own migration never get used, the one expression index that takes the endpoint from 18.79 to 281.51 requests a second, and where two backends return different first pages for the same query.
What the Rails 8.2 edge release notes actually list, checked against rails/rails main: the version is 8.2.0.alpha, there is no tag and no branch, and config.load_defaults 8.2 flips eleven behaviours of which the notes page names four. With Herb, has_json, DROP DATABASE WITH (FORCE) and Rails.app.creds measured in a generated app.
The header-based CSRF check landing in Rails 8.2: the two strategies, the exact branch in request_forgery_protection.rb, the three deprecations that fire before you change anything, which clients never send the header, and why a local HTTP reproduction shows you nothing.
What happens when the Rails 8.2 default ERB engine meets a real view layer: 7 of 207 templates rejected by bin/rails herb:check, all 7 of which still render, an omitted </li> that does not, and the attribute escaping Herb's README promises that Action View switches back off.
The Rails 8.2 edge notes say has_secure_password gains Argon2 via algorithm: :argon2. The digest it writes is argon2id at RFC 9106's second recommended parameters, it is an opt-in and not a default, the parameters cannot be configured, and switching a table of bcrypt digests to it raises rather than returning false. Measured on an M2 Max.
The Omarchy announcement that ThePrimeagen joins Omarchy Core to lead Agentic QA says nothing about money, and readers assumed a salary anyway. Why the words joins, lead and Core do that, plus a count of how much of one Rails Gemfile is held by a single RubyGems owner.
Both editors on the same has_rich_text column, on Rails 8.1.3.1: the HTML each one writes for the same input, what Lexxy does to rows Trix authored, what it adds to the sanitizer allowlist app-wide, the 999,642 bytes it pins, and the two lines of system test it breaks.
The Lex Fridman episode where DHH describes the agent failure nobody quotes: PRs that were individually defensible and collectively wrecked the system. Then the review boundary he draws instead, priced against a real Rails codebase with bin/rails stats, bin/rubocop and a simulated merge.
Contexts, conditions, custom validator classes and errors.add, with the uniqueness race reproduced against a real PostgreSQL. Including the two places the validation and the unique index disagree, and the method that stops working the moment you add uniqueness.
What id: :uuid gives you in Rails 8.1, why v7 keys index differently from v4, and the four ways to get a uuidv7() when your PostgreSQL has one and when it does not. With the leaf densities, the WAL, and the benchmark where v7 came out worse than v4.
What a transaction block guarantees, the four ways work commits when you meant it to roll back, what requires_new buys you, when after_commit fires and in what order, and the three lock errors worth retrying. With the SQL and the error text from a real PostgreSQL 17.7.
The three clocks a Rails process runs on, what Time.now is actually wrong about, why Time.zone.parse returns nil where Time.parse raises, and the daylight saving gap that makes a daily Solid Queue task skip a day. With the runner output and the cron walk.
What driven_by starts, the three ways a browser test goes flaky, and what sixty of them cost against sixty request tests on the same page. With the real Selenium error text and the worker count that made the suite slower.
What the default logger actually formats, the log_tags setting that silently does nothing, and the Event Reporter Rails 8.1 added next to the log rather than in place of it. With the JSON a real request emits and the health check that keeps emitting it.
An enum plus a transition table gets you most of a state machine in twenty lines. What AASM, state_machines and statesman add on top, measured on Rails 8.1, including the race every one of them loses by default.
Why db:prepare seeds exactly once and never again, what find_or_create_by does to eight concurrent processes, the upsert_all statement that replaces it, and the import task this site actually runs. With the SQL, the savepoints and the transaction ids.
A scope is a class method that returns a relation, except when the body returns nil and Active Record hands back the receiver instead. With the merge rule that deletes half your WHERE clause, and why default_scope inverts an ORDER BY without raising.
What Rails already ships, what rspec-rails 8.0.4 adds on top, the failure messages that are not a matter of taste, and four measured timings that show the speed gap is the parallel runner and nothing else. With the verdict and what would reverse it.
How Solid Queue 1.7.0 runs recurring jobs: the config/recurring.yml format, the four Fugit schedules that parse clean and run wrong, what a deploy does to the task table, and the unique index that makes two schedulers safe until it is cascaded away.
Verifying an HMAC signature before the body is parsed, the three ways Rails takes the raw bytes away from you, idempotency as a unique index rather than an exists? check, and the replay window that does not dedupe anything. With the error text from stripe 19.6.2, actionpack 8.1.3.1 and PostgreSQL 17.7.
ILIKE, a trigram index, a generated tsvector column with GIN, pg_search, and a separate engine. What each rung finds, what index serves it, and where the plan stops being cheap. Measured on PostgreSQL 17.7 against 200000 rows.
What lock_version does to the UPDATE, when StaleObjectError fires and the three methods that interact with it badly, and what SELECT FOR UPDATE buys instead. Measured against activerecord 8.1.3.1 and PostgreSQL 17.7 with four concurrent writers.
What includes, preload and eager_load each emit, the runtime branch that makes includes two different queries, and the detection Rails ships without a gem. With the two cases strict_loading watches go straight past.
Why a float loses a penny, what a Postgres numeric column gives you instead, and the case for storing cents in an integer. With the casts Active Record 8.1 performs on the way in, the two rounding rules that disagree, and the currency that has no cents.
The four things a change method cannot undo, what adding a defaulted column really costs on two million rows, why a concurrent index needs its transaction turned off, and the deploy window where the new schema meets the old code. Measured on PostgreSQL 17.7 and activerecord 8.1.3.1.
What a leading dot resolves to, why the controller's version of it differs from the view's, the three different answers a default Rails 8.1 app gives to a missing key, and the fallback setting that renders an English date on a French page without a warning.
The 26 lines behind rails/health#show, what a green /up proves and what it cannot, and the arithmetic that turns a database check on that route into every server in the fleet failing at the same second.
What the fixture loader emits, what factory_bot 6.6.0 costs per object, and the measured gap between them on a real Postgres. With the fixture that loads a row your model would reject, and the verdict.
has_one_attached, direct uploads, the S3 service, and the content type check that most applications get backwards. With the Marcel results, the attach source, and the two ways a variant fails.
Sequences, traits and associations in factory_bot 6.6.0, the four-row graph one create call produces, and the read that build_stubbed does not stub. With the SQL counts and the per-object timings from PostgreSQL 17.7.
Where rescue_from belongs, what Rails.error.handle and .record actually do in Rails 8.1, the 400s your error service sees in development and not in production, and what a 500 page can say when the application that would render it has already failed.
ENV.fetch against ENV[], the mixed-type default nobody notices, Rails.env.local? and the environments it quietly excludes, credentials against env vars, and the .env file this site promises and never reads because dotenv was never installed.
One enum declaration read out of a shipping Rails 8.1 app: what it generates, why prefix exists, and the integer against string decision measured on PostgreSQL. Including the query that silently becomes IS NULL.
Streaming 200000 rows out of a controller instead of building the file in memory, CSV.foreach on the way back in, the BOM rule that reverses depending on how you open the file, and why keeping 20000 invalid records keeps 20000 records. With measured peak RSS for every version.
The encrypted file, the 32-character key, and the resolution order Rails uses to find both. Why a per-environment file replaces the shared one instead of merging with it, and why a missing key in production reports a problem with secret_key_base.
What ActiveSupport::Concern actually adds over a plain Ruby module, read out of activesupport 8.1.3.1, plus the three comparisons with a verdict each: against a module, against a service object, against a helper.
The real firing order for save, update and destroy on activerecord 8.1.3.1, what after_commit guarantees that after_save cannot, why a callback holding an HTTP call also holds a row lock, and every way throw :abort behaves that the guides do not print.
The four levels a Rails 8 request can be answered from, what cache_key and cache_version actually return, why touch: true is the only invalidation that holds, and the template dependency the digester guesses wrong.
What Rails turns your cache key into before the store sees it, the stable key with a version beside it, why no store will list its keys for you, which four can delete by pattern, and the namespace your app already set. With the normalized keys printed and the rows counted.
The four columns an audit trail needs, what audited and paper_trail actually install, the six write methods that produce no audit row at all, and the Postgres trigger that catches them and cannot say who. Measured on activerecord 8.1.3.1 and PostgreSQL 17.7.
What encrypts writes into the column, where each of the three keys is used, why a WHERE on a non-deterministic column returns zero rows without raising, and what happens to the index. With the ciphertexts, the EXPLAIN plans and the byte counts.
The two columns, the index the migration writes, and the foreign key the database will not give you. Then the constraint that bites hardest: Active Record raises EagerLoadPolymorphicError rather than join a belongs_to polymorphic association, and the hand written join that is the only way past it.
Declaring one, the default that is not a Rails default, the containment query that needs a GIN index, what that index costs on write, and the typo nothing rejects. Measured on PostgreSQL 17.7 against 500000 rows, with the seventeen jsonb columns of a working Rails 8 app as the sample.
What Rails' counter_cache hooks, the three questions it answers wrong (a condition, a scope, has_many through), and the one line of SQL two counters in this site use instead. With the UPDATE statements Active Record 8.1 actually builds.
ViewComponent 4.15.0 and Phlex 2.4.1 against plain ERB partials, written from a codebase that has 54 partials and no app/components directory, with the line that would flip the choice.
The eight actions Turbo actually ships, read out of turbo.js: what replace does that update does not, why append silently deduplicates, where morph lives, and the broadcast that never arrives because the transaction had not committed yet.
What fires turbo:frame-missing, the incident where one frame killed every article link on /yield, the difference between target on the frame and data-turbo-frame on the link, and the order to check things in.
Targets, values, actions and the lifecycle, read out of the twenty-five controllers in a working Rails 8 app. Including the disconnect that Turbo's snapshot cache makes load-bearing, and what testing Stimulus controllers actually costs.
limit and offset in plain Active Record, the two ways offset pagination fails, and the keyset query that fixes both. With the EXPLAIN plans, including the cursor form that silently stops using the index.
default_url_options and why a mailer needs one, SMTP settings read from encrypted credentials, previews against letter_opener, what deliver_later really enqueues, and the email that renders perfectly and links to example.com.
Solid Queue 1.7.0 against Sidekiq 8.1.7 on the axes that decide it: what you run, polling versus BRPOP, the dashboard, retries after a SIGKILL, and what the paid tiers cost.
Rails 8 Solid Cache keeps cache entries as SQL rows instead of RAM. The argument is hit rate, not speed, and it holds only if you read how Solid Cache trims, what it encrypts, and where it is genuinely worse than Redis.
What `rails app:update` edits and what it leaves alone, the load_defaults ladder, and the four changes that actually bite going from 7.x to 8: Propshaft, the Solid trio, the authentication generator and Kamal.
What Kamal 2 replaced, what Thruster does inside the container, where the four Solid databases come from on a first deploy, and why kamal-proxy's health check can pass while production is already broken.
CVSS 9.5, unauthenticated, and the exposed configuration is the one load_defaults 7.0 gives you. How to decide in a minute, what to upgrade to, and what the patch breaks.
Ruby 4.0 can run two incompatible monkey patches on the same method in one process. Here's the API, what's actually isolated, and the one call shape that silently breaks inside a box.
JSON.create_id, a json_create hook and create_additions turn JSON.parse into an object factory. One iteration instead of two, and it has been in the standard library all along.
One model, one controller, one route. Slugs that generate themselves with SecureRandom, a click counter that does not restamp the row, and why allow_other_host exists at all.
One method_missing hook generates every HTML tag. The Ruby object model, the ancestor chain, the method lookup path, and the two tag names Kernel already owns.
Seven lines of Ruby add a palindrome? method to String. What the POSIX regex, Module#include and Unicode accents actually do to it.
A Ruby interview question: evaluate 2+3*4 as 14 without eval. Precedence as an array of operator levels, leftmost-first reduction, and where it breaks.
Solid Queue, Solid Cache and Solid Cable put jobs, caching and WebSockets in PostgreSQL. What that buys, and the one place Rails 8 rate limiting and Solid Cache do not compose.
A practical, no-fluff plan to go from idea to a paying-ready Rails app in two days - using the boilerplate's built-in auth, billing and onboarding.