<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sebastian Jimenez</title>
    <description>The latest articles on DEV Community by Sebastian Jimenez (@sebasjimenezvel).</description>
    <link>https://dev.to/sebasjimenezvel</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F165562%2Fa801ce5f-70e6-4052-9df5-caabace7dfd0.png</url>
      <title>DEV Community: Sebastian Jimenez</title>
      <link>https://dev.to/sebasjimenezvel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sebasjimenezvel"/>
    <language>en</language>
    <item>
      <title>Deploying Rails 8's Solid Trio to Production with a Single Database</title>
      <dc:creator>Sebastian Jimenez</dc:creator>
      <pubDate>Fri, 06 Mar 2026 07:01:00 +0000</pubDate>
      <link>https://dev.to/sebasjimenezvel/deploying-rails-8s-solid-trio-to-production-with-a-single-database-55be</link>
      <guid>https://dev.to/sebasjimenezvel/deploying-rails-8s-solid-trio-to-production-with-a-single-database-55be</guid>
      <description>&lt;h1&gt;
  
  
  Deploying Rails 8's Solid Trio to Production with a Single Database
&lt;/h1&gt;

&lt;p&gt;Rails 8 ships with the Solid stack — Solid Cache, Solid Cable, and Solid Queue — replacing Redis for caching, WebSockets, and background jobs. Database-backed infrastructure with zero external dependencies.&lt;/p&gt;

&lt;p&gt;Development was smooth. Production wasn't. This post documents the issues we ran into deploying the Solid trio to a Hatchbox-hosted app with a single PostgreSQL database, the mistakes we made debugging them, and what ultimately fixed things.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rails 8&lt;/strong&gt; with Hotwire (Turbo + Stimulus)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL 18&lt;/strong&gt; in production, 17 locally&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hatchbox&lt;/strong&gt; for deployment (single provisioned database)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solid Queue&lt;/strong&gt;, &lt;strong&gt;Solid Cache&lt;/strong&gt;, and &lt;strong&gt;Solid Cable&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The app is a booking system. Admins manage bookings, and Turbo broadcasts push real-time status updates when bookings are confirmed or cancelled.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 1: Missing Tables
&lt;/h2&gt;

&lt;p&gt;The first error appeared on the first request that touched caching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PG::UndefinedTable: ERROR: relation "solid_cache_entries" does not exist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What happened
&lt;/h3&gt;

&lt;p&gt;When you run &lt;code&gt;rails solid_cache:install&lt;/code&gt;, it generates:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;config/cache.yml&lt;/code&gt; — the configuration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;db/cache_schema.rb&lt;/code&gt; — the table definition&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There's no migration file. The official approach is to configure a separate &lt;code&gt;cache&lt;/code&gt; database in &lt;code&gt;database.yml&lt;/code&gt; and run &lt;code&gt;db:prepare&lt;/code&gt; in production, which loads schema files for secondary databases.&lt;/p&gt;

&lt;p&gt;Our deploy platform, &lt;a href="https://hatchbox.relationkit.io/articles/42-how-does-hatchbox-run-rails-migrations" rel="noopener noreferrer"&gt;Hatchbox&lt;/a&gt;, runs &lt;code&gt;db:migrate&lt;/code&gt; — not &lt;code&gt;db:prepare&lt;/code&gt;. And &lt;code&gt;db:migrate&lt;/code&gt; only runs migration files from &lt;code&gt;db/migrate/&lt;/code&gt;. It doesn't load schema files.&lt;/p&gt;

&lt;p&gt;The same was true for Solid Cable and its &lt;code&gt;db/cable_schema.rb&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it worked locally
&lt;/h3&gt;

&lt;p&gt;In development, the databases had been set up with &lt;code&gt;db:prepare&lt;/code&gt; when the project was first created. The tables existed. We never noticed they weren't created through migrations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 2: One Database, Four Configs
&lt;/h2&gt;

&lt;p&gt;Our &lt;code&gt;database.yml&lt;/code&gt; in production had separate entries for each Solid gem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;production&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV["DATABASE_URL"] %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV["DATABASE_URL"] %&amp;gt;&lt;/span&gt;
    &lt;span class="na"&gt;migrations_paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db/cache_migrate&lt;/span&gt;
  &lt;span class="na"&gt;cable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV["DATABASE_URL"] %&amp;gt;&lt;/span&gt;
    &lt;span class="na"&gt;migrations_paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db/cable_migrate&lt;/span&gt;
  &lt;span class="na"&gt;queue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV["DATABASE_URL"] %&amp;gt;&lt;/span&gt;
    &lt;span class="na"&gt;migrations_paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db/queue_migrate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks like four databases, but they all point to the same &lt;code&gt;DATABASE_URL&lt;/code&gt;. Hatchbox provisions one database. We weren't running separate databases — we were running separate configurations pointing to the same database.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;migrations_paths&lt;/code&gt; entries pointed to folders like &lt;code&gt;db/cache_migrate/&lt;/code&gt; and &lt;code&gt;db/cable_migrate/&lt;/code&gt;. These folders were empty. The actual table definitions lived in schema files, which &lt;code&gt;db:migrate&lt;/code&gt; ignores.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We Did
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The official separate-database approach
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/rails/solid_cache" rel="noopener noreferrer"&gt;Solid Cache&lt;/a&gt; and &lt;a href="https://github.com/rails/solid_cable" rel="noopener noreferrer"&gt;Solid Cable&lt;/a&gt; READMEs recommend separate databases with &lt;code&gt;db:prepare&lt;/code&gt;. That's the standard approach and it works well when your hosting allows provisioning multiple databases.&lt;/p&gt;

&lt;p&gt;We couldn't do that. Hatchbox gives us one database and runs &lt;code&gt;db:migrate&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The single-database workaround
&lt;/h3&gt;

&lt;p&gt;Both READMEs document what to do in this case. From the Solid Cache README:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"When you omit &lt;code&gt;database&lt;/code&gt;, &lt;code&gt;databases&lt;/code&gt;, or &lt;code&gt;connects_to&lt;/code&gt; settings, Solid Cache automatically uses the &lt;code&gt;ActiveRecord::Base&lt;/code&gt; connection pool."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Solid Cable's README is more explicit:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Copy the contents of &lt;code&gt;db/cable_schema.rb&lt;/code&gt; into a normal migration and delete &lt;code&gt;db/cable_schema.rb&lt;/code&gt;"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This isn't the primary recommended approach — it's a workaround for single-database setups. But it's documented and it works.&lt;/p&gt;

&lt;p&gt;Here's what we did:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Created regular migrations in &lt;code&gt;db/migrate/&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# db/migrate/20260306054425_create_solid_cache_entries.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateSolidCacheEntries&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;up&lt;/span&gt;
    &lt;span class="nb"&gt;load&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"db/cache_schema.rb"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;down&lt;/span&gt;
    &lt;span class="n"&gt;drop_table&lt;/span&gt; &lt;span class="ss"&gt;:solid_cache_entries&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# db/migrate/20260306062125_create_solid_cable_messages.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateSolidCableMessages&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;up&lt;/span&gt;
    &lt;span class="nb"&gt;load&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"db/cable_schema.rb"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;down&lt;/span&gt;
    &lt;span class="n"&gt;drop_table&lt;/span&gt; &lt;span class="ss"&gt;:solid_cable_messages&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A note on this approach: loading the schema files uses &lt;code&gt;force: :cascade&lt;/code&gt;, which drops and recreates the table if it already exists. For a first-time setup this is fine, but for an established database you may want to copy the table definition explicitly instead. The Solid Cable README recommends the explicit copy approach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Removed separate database configs from production &lt;code&gt;database.yml&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;production&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*default&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV["DATABASE_URL"] %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;queue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*default&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV["DATABASE_URL"] %&amp;gt;&lt;/span&gt;
    &lt;span class="na"&gt;migrations_paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db/queue_migrate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We kept &lt;code&gt;queue&lt;/code&gt; because Solid Queue ships with actual migration files in &lt;code&gt;db/queue_migrate/&lt;/code&gt;, unlike Cache and Cable which only have schema files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Removed connection directives:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# cable.yml — removed connects_to block&lt;/span&gt;
&lt;span class="na"&gt;production&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;solid_cable&lt;/span&gt;
  &lt;span class="na"&gt;polling_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.1.seconds&lt;/span&gt;
  &lt;span class="na"&gt;message_retention&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.day&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# cache.yml — removed database: cache&lt;/span&gt;
&lt;span class="na"&gt;production&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these changes, &lt;code&gt;db:migrate&lt;/code&gt; creates the tables and both gems fall back to the primary connection pool.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 3: A Misleading Error
&lt;/h2&gt;

&lt;p&gt;After fixing the cache table, we hit a new error when cancelling bookings from the admin panel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ArgumentError: No unique index found for id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The top of the stack trace pointed to our Turbo broadcast code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;broadcast_status_update&lt;/span&gt;
  &lt;span class="n"&gt;broadcast_replace_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;target: &lt;/span&gt;&lt;span class="s2"&gt;"booking_status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s2"&gt;"bookings/status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;locals: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;booking: &lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our bookings table uses UUID primary keys. We assumed the problem was that ActiveRecord couldn't find a unique index on the UUID &lt;code&gt;id&lt;/code&gt; column. We spent time trying various workarounds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Switching to &lt;code&gt;Turbo::StreamsChannel.broadcast_replace_to&lt;/code&gt; with string-based channel names&lt;/li&gt;
&lt;li&gt;Rendering HTML with &lt;code&gt;ApplicationController.render&lt;/code&gt; instead of passing &lt;code&gt;partial:&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Adding an explicit unique index on &lt;code&gt;bookings.id&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of it worked.&lt;/p&gt;

&lt;h3&gt;
  
  
  What actually happened
&lt;/h3&gt;

&lt;p&gt;We eventually pulled the full stack trace from Bugsnag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;activerecord/insert_all.rb:165:in `find_unique_index_for'
  ...
solid_cable/message.rb:15:in `broadcast'
solid_cable/lib/action_cable/subscription_adapter/solid_cable.rb:19:in `broadcast'
  ...
turbo-rails/app/channels/turbo/streams/broadcasts.rb:13:in `broadcast_replace_to'
app/models/booking.rb:174:in `broadcast_status_update'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The error wasn't in our code. It was in &lt;strong&gt;Solid Cable&lt;/strong&gt;. When our broadcast fired, Turbo handed the message to Action Cable, which handed it to Solid Cable, which tried to insert into the &lt;code&gt;solid_cable_messages&lt;/code&gt; table. That table didn't exist yet — the Solid Cable migration hadn't been deployed at that point.&lt;/p&gt;

&lt;p&gt;We're not 100% certain about the exact mechanism — whether the error was because the table was missing entirely or because the table existed without proper indexes. The error message ("No unique index found for id") suggests an index issue, but a missing table could also surface differently depending on how ActiveRecord checks for indexes. What we know for certain is that once the migration ran and the table was properly created, the error went away.&lt;/p&gt;

&lt;h3&gt;
  
  
  What we learned
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Read the full stack trace.&lt;/strong&gt; The error message and top frame made it look like a UUID primary key problem on our bookings table. We spent time adding indexes and rewriting broadcast code. The actual cause was several frames deep in a gem we hadn't thought to check.&lt;/p&gt;

&lt;p&gt;It's also a reminder that errors don't always mean what they say. "No unique index found for id" led us to chase index problems when the underlying issue was a missing table.&lt;/p&gt;




&lt;h2&gt;
  
  
  A note on &lt;code&gt;db:prepare&lt;/code&gt; and schema files
&lt;/h2&gt;

&lt;p&gt;During debugging, we also tried switching our deploy command to &lt;code&gt;db:prepare&lt;/code&gt;, thinking it would load the schema files. It's worth noting that &lt;a href="https://github.com/rails/solid_cache/issues/248" rel="noopener noreferrer"&gt;there are known inconsistencies&lt;/a&gt; with how &lt;code&gt;db:prepare&lt;/code&gt; handles secondary database schema files. It may not work reliably in all scenarios. The migration approach we ended up with, while not the primary recommended path, turned out to be more predictable.&lt;/p&gt;

&lt;p&gt;There's also a &lt;a href="https://github.com/rails/rails/issues/52829" rel="noopener noreferrer"&gt;known Rails issue&lt;/a&gt; where running &lt;code&gt;db:migrate&lt;/code&gt; can erroneously empty secondary schema files like &lt;code&gt;queue_schema.rb&lt;/code&gt;. Something to watch out for if you're keeping Solid Queue on a separate database config.&lt;/p&gt;




&lt;h2&gt;
  
  
  Verifying Everything Works
&lt;/h2&gt;

&lt;p&gt;After deploying, we verified each component via the Rails console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# SolidCache&lt;/span&gt;
&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"test_key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"test_key"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "hello"&lt;/span&gt;

&lt;span class="c1"&gt;# SolidCable&lt;/span&gt;
&lt;span class="no"&gt;SolidCable&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 0&lt;/span&gt;

&lt;span class="c1"&gt;# The original failing action — cancelling a booking&lt;/span&gt;
&lt;span class="n"&gt;booking&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Booking&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;confirmed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;
&lt;span class="n"&gt;booking&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;soft_delete!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;AdminUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; No error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;If you're deploying Rails 8's Solid trio to a single-database environment where &lt;code&gt;db:migrate&lt;/code&gt; is your deploy command:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Check your deploy command.&lt;/strong&gt; If it runs &lt;code&gt;db:migrate&lt;/code&gt; (not &lt;code&gt;db:prepare&lt;/code&gt;), schema files for Solid Cache and Solid Cable won't be loaded. You need regular migrations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create migrations for Solid Cache and Solid Cable.&lt;/strong&gt; The &lt;a href="https://github.com/rails/solid_cable" rel="noopener noreferrer"&gt;Solid Cable README&lt;/a&gt; documents this: copy the table definitions from &lt;code&gt;db/cable_schema.rb&lt;/code&gt; into a standard migration. Same for Solid Cache. This is a documented workaround for single-database setups, not the primary recommended approach.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Remove separate database configs&lt;/strong&gt; from &lt;code&gt;database.yml&lt;/code&gt; if you're using one database. Remove &lt;code&gt;connects_to&lt;/code&gt; from &lt;code&gt;cable.yml&lt;/code&gt; and &lt;code&gt;database&lt;/code&gt; from &lt;code&gt;cache.yml&lt;/code&gt;. Both gems will fall back to the primary connection pool.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keep Solid Queue's separate config.&lt;/strong&gt; Solid Queue ships with actual migrations in &lt;code&gt;db/queue_migrate/&lt;/code&gt; that &lt;code&gt;db:migrate&lt;/code&gt; can find.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Read the full stack trace.&lt;/strong&gt; When debugging production errors with the Solid stack, the error origin might be several frames deep in a gem you didn't expect. Don't assume the top frame tells the whole story.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The Solid trio works well once properly set up. The default Rails 8 configuration assumes you can provision multiple databases and run &lt;code&gt;db:prepare&lt;/code&gt; — a reasonable assumption, but not universal. If your hosting doesn't support that, the workaround is straightforward and documented, though not prominently.&lt;/p&gt;

&lt;p&gt;We made our debugging harder by not reading the full stack trace early enough. The lesson there is universal, not specific to the Solid stack.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>postgres</category>
      <category>deployment</category>
    </item>
    <item>
      <title>I made a thing for recording demos from the browser. No app install required.</title>
      <dc:creator>Sebastian Jimenez</dc:creator>
      <pubDate>Wed, 18 Feb 2026 07:57:00 +0000</pubDate>
      <link>https://dev.to/sebasjimenezvel/i-made-a-thing-for-recording-demos-from-the-browser-no-app-install-required-fmg</link>
      <guid>https://dev.to/sebasjimenezvel/i-made-a-thing-for-recording-demos-from-the-browser-no-app-install-required-fmg</guid>
      <description>&lt;p&gt;The problem nobody talks about&lt;/p&gt;

&lt;p&gt;You just shipped something. Maybe it's a side project, maybe it's a feature at work. You want to show it off. Simple, right?&lt;/p&gt;

&lt;p&gt;Not really.&lt;/p&gt;

&lt;p&gt;The usual flow looks something like this: find a screen recording app, download it, install it, figure out the settings, hit record, mess something up, re-record, then open&lt;br&gt;
another app to trim and edit. Oh, and half the decent options want your credit card before you even get started.&lt;/p&gt;

&lt;p&gt;All of that just to get a 30-second clip you can share in a tweet or a PR description.&lt;/p&gt;

&lt;p&gt;What I actually wanted&lt;/p&gt;

&lt;p&gt;A way to record a quick demo and get a shareable video out of it. No installs. No editing rabbit holes. No paywall.&lt;/p&gt;

&lt;p&gt;That's it. That was the whole requirement.&lt;/p&gt;

&lt;p&gt;I looked around for a while and everything I found either had too much friction or too many strings attached. So I did what any reasonable developer would do and built the&lt;br&gt;
thing myself.&lt;/p&gt;

&lt;p&gt;Recall&lt;/p&gt;

&lt;p&gt;I put together Recall to solve exactly this. You open it, record your demo, and get a shareable video. The whole point is to stay out of your way.&lt;/p&gt;

&lt;p&gt;It's free, no limits, no account required.&lt;/p&gt;

&lt;p&gt;If you're someone who builds things and wants a fast way to show what you're working on, give it a shot. I'd genuinely love to hear what you think and what would make it&lt;br&gt;
more useful for your workflow.&lt;/p&gt;

&lt;p&gt;Link: &lt;a href="https://recall.sebastianjimenez.co/" rel="noopener noreferrer"&gt;https://recall.sebastianjimenez.co/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>demos</category>
      <category>screenshare</category>
      <category>screenrecording</category>
    </item>
    <item>
      <title>Catch N+1 Queries in Rails Request Specs with 11 Lines of Ruby</title>
      <dc:creator>Sebastian Jimenez</dc:creator>
      <pubDate>Sun, 01 Feb 2026 05:23:55 +0000</pubDate>
      <link>https://dev.to/sebasjimenezvel/catch-n1-queries-in-rails-request-specs-with-11-lines-of-ruby-3708</link>
      <guid>https://dev.to/sebasjimenezvel/catch-n1-queries-in-rails-request-specs-with-11-lines-of-ruby-3708</guid>
      <description>&lt;h2&gt;
  
  
  Catch N+1 Queries in Rails Request Specs with 11 Lines of Ruby
&lt;/h2&gt;

&lt;p&gt;N+1 queries are one of the most common performance pitfalls in Rails applications. Tools like Bullet and Prosopite can help detect them, but they add dependencies and configuration overhead. Sometimes you just want a lightweight, transparent way to assert on query counts directly in your request specs.&lt;/p&gt;

&lt;p&gt;Here's how to do it with nothing more than what Rails already gives you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Technique
&lt;/h2&gt;

&lt;p&gt;Rails instruments every SQL query through &lt;code&gt;ActiveSupport::Notifications&lt;/code&gt; under the &lt;code&gt;sql.active_record&lt;/code&gt; event. We can subscribe to that event for the duration of a block and collect every query that fires.&lt;/p&gt;

&lt;p&gt;Create a file at &lt;code&gt;spec/support/query_counter.rb&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;QueryCounter&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;capture_queries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;queries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_finish&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;queries&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:sql&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:sql&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;match?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/SCHEMA|BEGIN|COMMIT|SAVEPOINT|RELEASE/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"sql.active_record"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;queries&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include&lt;/span&gt; &lt;span class="no"&gt;QueryCounter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :request&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The key method here is &lt;code&gt;ActiveSupport::Notifications.subscribed&lt;/code&gt;, which temporarily subscribes a callback for the duration of the block and automatically unsubscribes afterward. The regex filter excludes noise — schema introspection and transaction control statements — so you're left with only the queries your application code actually triggers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using It in a Spec
&lt;/h2&gt;

&lt;p&gt;Suppose you have an endpoint that returns a post with its comments. A naive implementation might load the post, then issue a separate query for each comment — the classic N+1. Here's how you'd catch that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"Posts API"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :request&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;let!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:with_comments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:target_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"avoids N+1 queries when fetching post details"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;queries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;capture_queries&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/api/posts/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;target_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;comment_queries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comment_queries&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test makes the request inside &lt;code&gt;capture_queries&lt;/code&gt;, then filters the collected SQL strings to count how many hit the comments table. If someone removes an &lt;code&gt;includes&lt;/code&gt; call or introduces a lazy load, this test fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Works Well
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;It's explicit.&lt;/strong&gt; Instead of relying on a background tool to warn you, you're making a direct assertion. The intent is clear to anyone reading the spec.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It tests the full stack.&lt;/strong&gt; Because this runs inside a request spec, you're exercising the actual controller, serializer, and any eager loading — not a unit test on an isolated query.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It gives you the raw SQL.&lt;/strong&gt; The &lt;code&gt;queries&lt;/code&gt; array contains the actual SQL strings, so when a test fails, you can inspect exactly what fired. This makes debugging straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;queries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;capture_queries&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/api/posts/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;target_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;queries&lt;/span&gt; &lt;span class="c1"&gt;# see every query that ran&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;No dependencies.&lt;/strong&gt; This uses &lt;code&gt;ActiveSupport::Notifications&lt;/code&gt;, which is part of Rails. There's nothing to install, configure, or keep up to date.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;This approach is intentionally minimal. It doesn't automatically detect N+1s — you have to know what to assert on. It doesn't give you RSpec matchers like &lt;code&gt;expect { }.to execute_queries(at_most: 3)&lt;/code&gt;. And it won't alert you to regressions unless you've written a test for that specific endpoint.&lt;/p&gt;

&lt;p&gt;For projects that need automatic detection across every request, tools like &lt;a href="https://github.com/charkost/prosopite" rel="noopener noreferrer"&gt;Prosopite&lt;/a&gt; or &lt;a href="https://github.com/flyerhzm/bullet" rel="noopener noreferrer"&gt;Bullet&lt;/a&gt; are better suited. But for targeted assertions on critical endpoints, 12 lines of Ruby and a clear test go a long way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips for Adoption
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start with your slowest endpoints.&lt;/strong&gt; Add &lt;code&gt;capture_queries&lt;/code&gt; to the request specs for endpoints you know are performance-sensitive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assert on specific tables.&lt;/strong&gt; Counting all queries is brittle — a schema change might add or remove a join. Filtering by table name (like the &lt;code&gt;comments&lt;/code&gt; example above) makes tests more resilient.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use it during development.&lt;/strong&gt; Wrap a controller action in &lt;code&gt;capture_queries&lt;/code&gt; in a scratch test to see what's actually happening before you optimize.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best performance tools are the ones you'll actually use. Sometimes that's a full-featured gem. Sometimes it's 12 lines in &lt;code&gt;spec/support&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks for reading this far!
&lt;/h2&gt;

&lt;p&gt;I'm currently exploring solutions for my own problems. I recently created &lt;a href="https://shaicli.dev" rel="noopener noreferrer"&gt;shaicli.dev&lt;/a&gt; and &lt;a href="https://frostcode.io" rel="noopener noreferrer"&gt;frost&lt;/a&gt;. Please check them out, I'd love to know what you think!&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>performance</category>
      <category>database</category>
    </item>
    <item>
      <title>I got mass downvoted for sharing my Claude configuration. So I built a tool to fix this.</title>
      <dc:creator>Sebastian Jimenez</dc:creator>
      <pubDate>Wed, 24 Dec 2025 04:02:00 +0000</pubDate>
      <link>https://dev.to/sebasjimenezvel/i-got-mass-downvoted-for-sharing-my-claude-configuration-so-i-built-a-tool-to-fix-this-aij</link>
      <guid>https://dev.to/sebasjimenezvel/i-got-mass-downvoted-for-sharing-my-claude-configuration-so-i-built-a-tool-to-fix-this-aij</guid>
      <description>&lt;p&gt;Last month I shared my Claude Code configuration on Reddit.&lt;/p&gt;

&lt;p&gt;The response?&lt;/p&gt;

&lt;p&gt;"Cool but how do I actually use this?"&lt;br&gt;
"Can you just give me the files?"&lt;br&gt;
"Where do I put these?"&lt;/p&gt;

&lt;p&gt;I spent 30 minutes answering the same questions. Then someone asked me to update it. Then someone forked my gist and it got out of sync.&lt;/p&gt;

&lt;p&gt;I mass-deleted my comments and rage-quit the thread.&lt;/p&gt;




&lt;p&gt;The problem nobody talks about&lt;/p&gt;

&lt;p&gt;Every dev using AI coding tools has this workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find someone's config that looks useful&lt;/li&gt;
&lt;li&gt;Copy-paste from a gist/notion/tweet thread&lt;/li&gt;
&lt;li&gt;Figure out where the files go&lt;/li&gt;
&lt;li&gt;Realize it's outdated&lt;/li&gt;
&lt;li&gt;Give up and write your own&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We've solved package management, dotfiles, and infrastructure-as-code. But AI configurations? Still living in the dark ages of copy-paste.&lt;/p&gt;




&lt;p&gt;What if it was just one command?&lt;/p&gt;

&lt;p&gt;shai install anthropic/claude-expert&lt;/p&gt;

&lt;p&gt;That's it. Files land exactly where they belong. No cloning repos. No navigating folders. No "where does this go?"&lt;/p&gt;

&lt;p&gt;✓ Created .claude/&lt;br&gt;
✓ Created .claude/settings.json&lt;br&gt;
✓ Created CLAUDE.md&lt;br&gt;
✓ Installed 6 items from anthropic/claude-expert&lt;/p&gt;




&lt;p&gt;I built Shai in a weekend&lt;/p&gt;

&lt;p&gt;Shai is a CLI for sharing AI configurations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;shai search "rails" → find configs others have shared&lt;/li&gt;
&lt;li&gt;shai install user/config → install with one command&lt;/li&gt;
&lt;li&gt;shai push → share your own setup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No login required to search or install public configs. Takes 10 seconds to set up:&lt;/p&gt;

&lt;p&gt;curl -fsSL &lt;a href="https://shaicli.dev/install.sh" rel="noopener noreferrer"&gt;https://shaicli.dev/install.sh&lt;/a&gt; | bash&lt;/p&gt;




&lt;p&gt;Why not just use Git?&lt;/p&gt;

&lt;p&gt;You could store configs in a Git repo. But when you want someone else's config:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Git&lt;/th&gt;
&lt;th&gt;Shai&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Clone repo, find files, copy them&lt;/td&gt;
&lt;td&gt;shai install user/config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need the repo URL upfront&lt;/td&gt;
&lt;td&gt;Search and discover&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Get README, LICENSE, CI files, .git&lt;/td&gt;
&lt;td&gt;Get just the config files&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Git is for code you develop. Shai is for configs you use.&lt;/p&gt;




&lt;p&gt;Show me your setup&lt;/p&gt;

&lt;p&gt;I'm building a library of community configurations. Here's what I'm looking for:&lt;/p&gt;

&lt;p&gt;Claude Code setups for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rails / Django / Next.js / Laravel&lt;/li&gt;
&lt;li&gt;Mobile (React Native, Flutter, Swift)&lt;/li&gt;
&lt;li&gt;DevOps / Infrastructure&lt;/li&gt;
&lt;li&gt;Data Science / ML&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cursor configurations for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Specific frameworks&lt;/li&gt;
&lt;li&gt;Code review workflows&lt;/li&gt;
&lt;li&gt;Testing patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have a config worth sharing:&lt;/p&gt;

&lt;p&gt;shai login&lt;br&gt;
shai init&lt;br&gt;
shai push&lt;/p&gt;

&lt;p&gt;Drop your username/config in the comments. I'll feature the best ones on the &lt;a href="https://shaicli.dev/explore" rel="noopener noreferrer"&gt;https://shaicli.dev/explore&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;The first 50 configs get featured&lt;/p&gt;

&lt;p&gt;I'm hand-picking the first 50 public configurations to feature on the homepage. If you publish this week, you're basically guaranteed a spot.&lt;/p&gt;

&lt;p&gt;What makes a good config:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Actually works (tested it yourself)&lt;/li&gt;
&lt;li&gt;Has a clear use case (not "my random settings")&lt;/li&gt;
&lt;li&gt;Includes a description&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Try it now&lt;/p&gt;

&lt;h2&gt;
  
  
  Install (10 seconds)
&lt;/h2&gt;

&lt;p&gt;curl -fsSL &lt;a href="https://shaicli.dev/install.sh" rel="noopener noreferrer"&gt;https://shaicli.dev/install.sh&lt;/a&gt; | bash&lt;/p&gt;

&lt;h2&gt;
  
  
  Search configs
&lt;/h2&gt;

&lt;p&gt;shai search "claude"&lt;/p&gt;

&lt;h2&gt;
  
  
  Install one
&lt;/h2&gt;

&lt;p&gt;shai install community/claude-for-rails&lt;/p&gt;

&lt;h2&gt;
  
  
  Share your own
&lt;/h2&gt;

&lt;p&gt;shai login&lt;br&gt;
shai init&lt;br&gt;
shai push&lt;/p&gt;

&lt;p&gt;Links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🌐 &lt;a href="https://shaicli.dev" rel="noopener noreferrer"&gt;https://shaicli.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📦 &lt;a href="https://github.com/infinitlab/shai-cli" rel="noopener noreferrer"&gt;https://github.com/infinitlab/shai-cli&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📚 &lt;a href="https://shaicli.dev/docs" rel="noopener noreferrer"&gt;https://shaicli.dev/docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;What's your current AI config workflow? I'm curious how others are handling this. Drop a comment below.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>opensource</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
