<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Software Engineering consultant</title>
    <description>Experienced Ruby on Rails and Go developer specializing in scalable web applications</description>
    <link>https://lukin.io/</link>
    <atom:link href="https://lukin.io/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Sun, 10 May 2026 18:50:05 +0000</pubDate>
    <lastBuildDate>Sun, 10 May 2026 18:50:05 +0000</lastBuildDate>
    <generator>Jekyll v4.4.1</generator>
    
      <item>
        <title>Mergeable by Default: Why AI Coding Needs a Context Engine, Not Just a Bigger Model</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;“The hard part is no longer getting AI to generate code. The hard part is getting AI to generate code that belongs in your system.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;tldr&quot;&gt;TL;DR&lt;/h2&gt;

&lt;p&gt;AI coding tools are getting better, but most teams still lose time because generated code is only &lt;strong&gt;almost right&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The useful target is not:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;AI writes code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The useful target is:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;AI writes code that is &lt;strong&gt;mergeable by default&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That requires a &lt;strong&gt;context engine&lt;/strong&gt;: a reasoning layer that knows your codebase, conventions, previous decisions, permissions, docs, tests, ownership, and current task — then gives the agent only the context it actually needs.&lt;/p&gt;

&lt;p&gt;This is the practical point from the talk:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;bigger context windows are not enough&lt;/li&gt;
  &lt;li&gt;naive RAG is not enough&lt;/li&gt;
  &lt;li&gt;random MCP servers are not enough&lt;/li&gt;
  &lt;li&gt;code search alone is not enough&lt;/li&gt;
  &lt;li&gt;AI without context creates review debt&lt;/li&gt;
  &lt;li&gt;AI with the right context can reduce token cost, review cycles, and rework&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For my own Rails / API / AI-assisted workflow, this maps almost directly to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PROMPT.md&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GUIDE.md&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;doc/requirements/**&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Flow docs&lt;/li&gt;
  &lt;li&gt;PRDs&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/contract_audit&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;RSpec / rswag / Pundit / Blueprinter conventions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words: the best AI coding system is not just a model.&lt;br /&gt;
It is an engineering operating system around the model.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-youtube-talk-worth-knowing&quot;&gt;The YouTube talk worth knowing&lt;/h2&gt;

&lt;p&gt;The video is:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=5ID22ACI7IM&quot;&gt;Mergeable by default: Building the context engine to save time and tokens — Peter Werry, Unblocked&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The core description is simple:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Agents can generate code. The hard part is generating code that’s right for your system, team conventions, and past decisions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That line is the whole problem.&lt;/p&gt;

&lt;p&gt;Modern coding agents can produce code fast. But fast code is not automatically useful code. If the agent does not know your real system, it creates a second job for the team:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;pay for generation in tokens&lt;/li&gt;
  &lt;li&gt;pay again in review, correction, and cleanup&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is why the talk focuses on a &lt;strong&gt;context engine&lt;/strong&gt;: the layer that decides what the agent should know before it acts.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;why-this-matters&quot;&gt;Why this matters&lt;/h2&gt;

&lt;p&gt;Most AI coding demos optimize for the wrong thing.&lt;/p&gt;

&lt;p&gt;They show:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;a prompt&lt;/li&gt;
  &lt;li&gt;a generated file&lt;/li&gt;
  &lt;li&gt;a green-looking result&lt;/li&gt;
  &lt;li&gt;a fast demo loop&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But production engineering is not a demo.&lt;/p&gt;

&lt;p&gt;Production engineering has:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;old decisions&lt;/li&gt;
  &lt;li&gt;team conventions&lt;/li&gt;
  &lt;li&gt;naming rules&lt;/li&gt;
  &lt;li&gt;API contracts&lt;/li&gt;
  &lt;li&gt;permission boundaries&lt;/li&gt;
  &lt;li&gt;security requirements&lt;/li&gt;
  &lt;li&gt;test expectations&lt;/li&gt;
  &lt;li&gt;serializer rules&lt;/li&gt;
  &lt;li&gt;migration history&lt;/li&gt;
  &lt;li&gt;review standards&lt;/li&gt;
  &lt;li&gt;product requirements&lt;/li&gt;
  &lt;li&gt;half-finished work&lt;/li&gt;
  &lt;li&gt;legacy constraints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If AI ignores those, the generated code may look good and still be wrong.&lt;/p&gt;

&lt;p&gt;That is the dangerous part: AI often fails in ways that are plausible.&lt;/p&gt;

&lt;p&gt;It creates code that looks like something a developer would write, but it may not match the system.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-real-ai-coding-problem&quot;&gt;The real AI coding problem&lt;/h2&gt;

&lt;p&gt;The problem is not:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Can the model write Ruby, Go, TypeScript, or Python?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It can.&lt;/p&gt;

&lt;p&gt;The problem is:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Does the model know what &lt;strong&gt;correct&lt;/strong&gt; means inside this repo?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Correct is not only syntax.&lt;/p&gt;

&lt;p&gt;Correct means:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the endpoint follows the existing API shape&lt;/li&gt;
  &lt;li&gt;the response uses the right envelope&lt;/li&gt;
  &lt;li&gt;the serializer follows the project convention&lt;/li&gt;
  &lt;li&gt;authorization is enforced in the expected way&lt;/li&gt;
  &lt;li&gt;search uses the expected search library&lt;/li&gt;
  &lt;li&gt;pagination follows the standard project pattern&lt;/li&gt;
  &lt;li&gt;tests cover the contract&lt;/li&gt;
  &lt;li&gt;docs are updated in the right place&lt;/li&gt;
  &lt;li&gt;generated OpenAPI is produced from specs, not edited by hand&lt;/li&gt;
  &lt;li&gt;no new representational drift is introduced&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where AI coding becomes an engineering systems problem.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;bigger-context-windows-do-not-solve-this-alone&quot;&gt;Bigger context windows do not solve this alone&lt;/h2&gt;

&lt;p&gt;It is tempting to think the solution is:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Just give the model more context.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But bigger context is not the same as better context.&lt;/p&gt;

&lt;p&gt;A huge context window can still contain:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;irrelevant files&lt;/li&gt;
  &lt;li&gt;outdated docs&lt;/li&gt;
  &lt;li&gt;conflicting instructions&lt;/li&gt;
  &lt;li&gt;stale decisions&lt;/li&gt;
  &lt;li&gt;too many examples&lt;/li&gt;
  &lt;li&gt;private information the agent should not see&lt;/li&gt;
  &lt;li&gt;similar-but-wrong patterns&lt;/li&gt;
  &lt;li&gt;code that should not be copied&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More context can even make the output worse if the agent cannot tell which source has authority.&lt;/p&gt;

&lt;p&gt;The real need is not maximum context.&lt;/p&gt;

&lt;p&gt;The real need is &lt;strong&gt;selected, ranked, permission-aware, task-specific context&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is what a context engine should do.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;naive-rag-is-also-not-enough&quot;&gt;Naive RAG is also not enough&lt;/h2&gt;

&lt;p&gt;Basic RAG usually means:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;embed documents&lt;/li&gt;
  &lt;li&gt;search for similar chunks&lt;/li&gt;
  &lt;li&gt;paste the top results into the prompt&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That helps, but it is not enough for serious engineering work.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because code work often needs reasoning across sources:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;requirement says one thing&lt;/li&gt;
  &lt;li&gt;Flow doc says another&lt;/li&gt;
  &lt;li&gt;current controller does a third thing&lt;/li&gt;
  &lt;li&gt;specs define the real contract&lt;/li&gt;
  &lt;li&gt;old PR explains why the strange behavior exists&lt;/li&gt;
  &lt;li&gt;security policy limits what can be touched&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simple similarity search does not know which source should win.&lt;/p&gt;

&lt;p&gt;A useful context engine needs to answer:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;which document is authoritative?&lt;/li&gt;
  &lt;li&gt;which code path is actually active?&lt;/li&gt;
  &lt;li&gt;which convention is current?&lt;/li&gt;
  &lt;li&gt;which files changed recently?&lt;/li&gt;
  &lt;li&gt;who owns this area?&lt;/li&gt;
  &lt;li&gt;what should be excluded?&lt;/li&gt;
  &lt;li&gt;what is relevant to this exact task?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is different from “search and paste.”&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;mcp-servers-are-useful-but-not-magic&quot;&gt;MCP servers are useful, but not magic&lt;/h2&gt;

&lt;p&gt;MCP-style tool access can connect agents to more systems:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;GitHub&lt;/li&gt;
  &lt;li&gt;Linear / Jira&lt;/li&gt;
  &lt;li&gt;docs&lt;/li&gt;
  &lt;li&gt;Slack&lt;/li&gt;
  &lt;li&gt;databases&lt;/li&gt;
  &lt;li&gt;observability&lt;/li&gt;
  &lt;li&gt;local tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is useful.&lt;/p&gt;

&lt;p&gt;But tool access is not the same as understanding.&lt;/p&gt;

&lt;p&gt;An agent with 20 tools can still fail if it does not know:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;when to use which tool&lt;/li&gt;
  &lt;li&gt;which result is authoritative&lt;/li&gt;
  &lt;li&gt;what the task boundary is&lt;/li&gt;
  &lt;li&gt;what the team convention expects&lt;/li&gt;
  &lt;li&gt;what must not be changed&lt;/li&gt;
  &lt;li&gt;what proof is required before completion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tools give the agent hands.&lt;/p&gt;

&lt;p&gt;A context engine gives the agent judgment boundaries.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-a-context-engine-should-contain&quot;&gt;What a context engine should contain&lt;/h2&gt;

&lt;p&gt;A useful engineering context engine should collect and reason over several layers.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Layer&lt;/th&gt;
      &lt;th&gt;What it answers&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Code context&lt;/td&gt;
      &lt;td&gt;What files, services, models, controllers, serializers, tests, and configs matter?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Product context&lt;/td&gt;
      &lt;td&gt;What requirement, PRD, or ticket defines the behavior?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Architecture context&lt;/td&gt;
      &lt;td&gt;What patterns does this repo already use?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Convention context&lt;/td&gt;
      &lt;td&gt;What naming, response, route, and test rules must be followed?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Decision context&lt;/td&gt;
      &lt;td&gt;Why was this built this way before?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Permission context&lt;/td&gt;
      &lt;td&gt;What is the agent allowed to read or change?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ownership context&lt;/td&gt;
      &lt;td&gt;Who owns the area, or what review path matters?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Verification context&lt;/td&gt;
      &lt;td&gt;What command proves the change is safe?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Drift context&lt;/td&gt;
      &lt;td&gt;What kinds of changes are forbidden even if tests pass?&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The important word is &lt;strong&gt;context&lt;/strong&gt;, not &lt;strong&gt;documents&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The engine is not just a folder of markdown files.&lt;br /&gt;
It is the system that decides what matters for the current task.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-mergeable-by-default-really-means&quot;&gt;What “mergeable by default” really means&lt;/h2&gt;

&lt;p&gt;“Mergeable by default” does not mean AI code is automatically merged.&lt;/p&gt;

&lt;p&gt;It means the AI output is shaped so that the default review path is:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;verify, review, merge&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;rewrite, argue with the model, fix conventions, add missing tests, update docs, clean up payload shape, then maybe merge&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A mergeable-by-default change has these properties:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Property&lt;/th&gt;
      &lt;th&gt;Meaning&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Contract-aware&lt;/td&gt;
      &lt;td&gt;It implements the actual requirement, not a guessed version&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Convention-aware&lt;/td&gt;
      &lt;td&gt;It follows existing repo patterns&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Test-backed&lt;/td&gt;
      &lt;td&gt;It includes relevant tests/specs&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Permission-safe&lt;/td&gt;
      &lt;td&gt;It does not cross access boundaries&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Drift-safe&lt;/td&gt;
      &lt;td&gt;It does not invent new response shapes or naming styles&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Minimal&lt;/td&gt;
      &lt;td&gt;It touches only what the task requires&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Explainable&lt;/td&gt;
      &lt;td&gt;It can point to why files changed&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Verifiable&lt;/td&gt;
      &lt;td&gt;One command can prove the change is safe enough to review&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;This is the difference between using AI as autocomplete and using AI as a controlled engineering assistant.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-review-cost-problem&quot;&gt;The review-cost problem&lt;/h2&gt;

&lt;p&gt;Bad AI code is expensive because it creates hidden work.&lt;/p&gt;

&lt;p&gt;The obvious cost is token usage.&lt;/p&gt;

&lt;p&gt;The bigger cost is review drag:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;reviewers must detect subtle mismatch&lt;/li&gt;
  &lt;li&gt;developers must check every convention manually&lt;/li&gt;
  &lt;li&gt;specs may be missing or shallow&lt;/li&gt;
  &lt;li&gt;docs may not reflect behavior&lt;/li&gt;
  &lt;li&gt;generated code may duplicate existing logic&lt;/li&gt;
  &lt;li&gt;authorization may be incomplete&lt;/li&gt;
  &lt;li&gt;migration or serializer decisions may drift&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why the talk’s “save time and tokens” framing matters.&lt;/p&gt;

&lt;p&gt;Tokens are not the main cost.&lt;/p&gt;

&lt;p&gt;The real cost is the human correction loop after bad context.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;how-this-maps-to-my-rails-workflow&quot;&gt;How this maps to my Rails workflow&lt;/h2&gt;

&lt;p&gt;This topic is especially relevant to my own Rails / API workflow because I already treat AI as part of a controlled delivery system.&lt;/p&gt;

&lt;p&gt;In my setup, the closest equivalent of a context engine is not one tool. It is the combination of:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Piece&lt;/th&gt;
      &lt;th&gt;Role&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Operating rules and delivery discipline&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PROMPT.md&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Task execution flow&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GUIDE.md&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Examples and reusable patterns&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;doc/requirements/**&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Source of truth for behavior&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Flow docs&lt;/td&gt;
      &lt;td&gt;Implementation traceability&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;PRDs&lt;/td&gt;
      &lt;td&gt;Product context&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;RSpec&lt;/td&gt;
      &lt;td&gt;Behavior proof&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;rswag&lt;/td&gt;
      &lt;td&gt;API contract proof&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Pundit policies&lt;/td&gt;
      &lt;td&gt;Authorization proof&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Blueprinter&lt;/td&gt;
      &lt;td&gt;Response-shape discipline&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ransack + Kaminari&lt;/td&gt;
      &lt;td&gt;Search and pagination convention&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Fast verification loop&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/contract_audit&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Drift prevention&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;This is why the talk feels immediately practical.&lt;/p&gt;

&lt;p&gt;It says: do not just buy a smarter model.&lt;br /&gt;
Build the environment where the model can make fewer wrong assumptions.&lt;/p&gt;

&lt;p&gt;That matches my own conclusion from AI-assisted backend work:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;AI productivity comes from externalized judgment, not prompt luck.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;a-concrete-rails-example&quot;&gt;A concrete Rails example&lt;/h2&gt;

&lt;p&gt;Imagine a task:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Add a new nested API endpoint for avatar achievements with artifact attachments.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A generic AI agent may create:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;a controller&lt;/li&gt;
  &lt;li&gt;a model&lt;/li&gt;
  &lt;li&gt;a serializer&lt;/li&gt;
  &lt;li&gt;a few routes&lt;/li&gt;
  &lt;li&gt;maybe a spec&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But a context-aware agent should know much more:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;endpoints must live under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/api/v1/avatars&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;JSON must stay snake_case&lt;/li&gt;
  &lt;li&gt;list responses must return &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{ data, meta }&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;uploads should use nested attributes in one request&lt;/li&gt;
  &lt;li&gt;Blueprinter should serialize file URLs consistently&lt;/li&gt;
  &lt;li&gt;Pundit authorization must be represented&lt;/li&gt;
  &lt;li&gt;request specs must include multipart create/update tests&lt;/li&gt;
  &lt;li&gt;Ransack and Kaminari are expected for searchable/paginated endpoints&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ransackable_attributes&lt;/code&gt; must be explicit&lt;/li&gt;
  &lt;li&gt;Flow docs must follow the existing template&lt;/li&gt;
  &lt;li&gt;rswag specs should drive OpenAPI generation&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt; and contract audit must pass&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is not “more code.”&lt;/p&gt;

&lt;p&gt;That is project-specific correctness.&lt;/p&gt;

&lt;p&gt;And this is exactly the kind of correctness that generic AI does not infer reliably unless the context system teaches it.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-context-engine-as-a-compiler-for-engineering-judgment&quot;&gt;The context engine as a compiler for engineering judgment&lt;/h2&gt;

&lt;p&gt;A good way to think about this:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Traditional compiler&lt;/th&gt;
      &lt;th&gt;AI engineering system&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Source code&lt;/td&gt;
      &lt;td&gt;Requirements + task&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Language spec&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt; / conventions&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Type checker&lt;/td&gt;
      &lt;td&gt;Tests + contracts&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Linter&lt;/td&gt;
      &lt;td&gt;Style / naming / route rules&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Static analyzer&lt;/td&gt;
      &lt;td&gt;Security + drift audit&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Build output&lt;/td&gt;
      &lt;td&gt;Mergeable PR&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The model generates the draft.&lt;/p&gt;

&lt;p&gt;The context engine controls the environment around the draft.&lt;/p&gt;

&lt;p&gt;That is the serious version of AI-assisted development.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;important-lessons-from-the-talk&quot;&gt;Important lessons from the talk&lt;/h2&gt;

&lt;h3 id=&quot;1-context-must-be-task-specific&quot;&gt;1. Context must be task-specific&lt;/h3&gt;

&lt;p&gt;Do not dump the whole repo into the model.&lt;/p&gt;

&lt;p&gt;Give it the files, docs, tests, decisions, and examples that matter for the task.&lt;/p&gt;

&lt;h3 id=&quot;2-authority-matters&quot;&gt;2. Authority matters&lt;/h3&gt;

&lt;p&gt;The agent must know which source wins when sources conflict.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;current requirement&lt;/li&gt;
  &lt;li&gt;current tests/specs&lt;/li&gt;
  &lt;li&gt;current implementation&lt;/li&gt;
  &lt;li&gt;older docs&lt;/li&gt;
  &lt;li&gt;old examples&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Without authority ranking, the agent can follow stale context confidently.&lt;/p&gt;

&lt;h3 id=&quot;3-permissions-matter&quot;&gt;3. Permissions matter&lt;/h3&gt;

&lt;p&gt;A production context engine must respect access boundaries.&lt;/p&gt;

&lt;p&gt;Not every agent should see every Slack thread, customer doc, security detail, or private dataset.&lt;/p&gt;

&lt;h3 id=&quot;4-personalization-matters&quot;&gt;4. Personalization matters&lt;/h3&gt;

&lt;p&gt;Two engineers may ask the same question but need different context.&lt;/p&gt;

&lt;p&gt;A backend engineer, frontend engineer, new hire, tech lead, and reviewer should not necessarily receive the same context bundle.&lt;/p&gt;

&lt;h3 id=&quot;5-review-cycles-are-the-hidden-cost&quot;&gt;5. Review cycles are the hidden cost&lt;/h3&gt;

&lt;p&gt;The output should be optimized for fewer review corrections, not just faster generation.&lt;/p&gt;

&lt;h3 id=&quot;6-the-best-context-system-learns-from-real-mistakes&quot;&gt;6. The best context system learns from real mistakes&lt;/h3&gt;

&lt;p&gt;A good context engine improves when the team discovers:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;wrong files were retrieved&lt;/li&gt;
  &lt;li&gt;stale docs were used&lt;/li&gt;
  &lt;li&gt;permissions were too broad&lt;/li&gt;
  &lt;li&gt;generated code missed a convention&lt;/li&gt;
  &lt;li&gt;tests passed but contract drift appeared&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That feedback should become system behavior, not tribal memory.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-is-worth-mentioning-from-the-youtube-link&quot;&gt;What is worth mentioning from the YouTube link&lt;/h2&gt;

&lt;p&gt;If I had to reduce the video into a few points worth remembering:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;AI codegen is already capable enough to be dangerous.&lt;/strong&gt;&lt;br /&gt;
The remaining problem is whether it writes code that fits the system.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Wrong context creates double cost.&lt;/strong&gt;&lt;br /&gt;
You pay once for generation and again during review/fix cycles.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Bigger context windows are not a strategy.&lt;/strong&gt;&lt;br /&gt;
They help, but they do not solve authority, relevance, conflict, permissions, or freshness.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Naive RAG is not the final answer.&lt;/strong&gt;&lt;br /&gt;
Search results need reasoning and ranking, especially across code, docs, history, and decisions.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;MCP/tool access is only plumbing.&lt;/strong&gt;&lt;br /&gt;
Tools expose information; they do not decide what matters.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;The real product is the context engine.&lt;/strong&gt;&lt;br /&gt;
It is the reasoning layer between the agent and the organization.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;“Mergeable by default” is the practical success metric.&lt;/strong&gt;&lt;br /&gt;
Not “did it generate code?”, but “did it reduce the path to a safe merge?”&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-teams-usually-get-wrong&quot;&gt;What teams usually get wrong&lt;/h2&gt;

&lt;h3 id=&quot;they-treat-prompts-as-architecture&quot;&gt;They treat prompts as architecture&lt;/h3&gt;

&lt;p&gt;A prompt is not enough.&lt;/p&gt;

&lt;p&gt;If the repo does not have clear requirements, tests, conventions, and verification gates, the prompt becomes a wish list.&lt;/p&gt;

&lt;h3 id=&quot;they-treat-docs-as-optional&quot;&gt;They treat docs as optional&lt;/h3&gt;

&lt;p&gt;For AI-assisted work, docs are not bureaucracy.&lt;/p&gt;

&lt;p&gt;Docs are training material for the engineering system.&lt;/p&gt;

&lt;p&gt;Bad docs create bad AI behavior.&lt;/p&gt;

&lt;h3 id=&quot;they-optimize-for-demo-speed&quot;&gt;They optimize for demo speed&lt;/h3&gt;

&lt;p&gt;Demo speed is not production speed.&lt;/p&gt;

&lt;p&gt;Production speed includes review, verification, rollback risk, onboarding, maintenance, and future changes.&lt;/p&gt;

&lt;h3 id=&quot;they-let-ai-invent-conventions&quot;&gt;They let AI invent conventions&lt;/h3&gt;

&lt;p&gt;This is one of the fastest ways to create long-term codebase drift.&lt;/p&gt;

&lt;p&gt;AI should follow conventions, not create a new local style every task.&lt;/p&gt;

&lt;h3 id=&quot;they-do-not-separate-generation-from-verification&quot;&gt;They do not separate generation from verification&lt;/h3&gt;

&lt;p&gt;Generation is cheap.&lt;/p&gt;

&lt;p&gt;Trust is expensive.&lt;/p&gt;

&lt;p&gt;The system must make trust cheaper.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-i-would-build-next&quot;&gt;What I would build next&lt;/h2&gt;

&lt;p&gt;For my own workflow, the next practical step is to make the context engine more explicit.&lt;/p&gt;

&lt;p&gt;Today, much of it already exists as files and scripts.&lt;/p&gt;

&lt;p&gt;The next version should behave more like a retrieval + reasoning layer.&lt;/p&gt;

&lt;h3 id=&quot;1-task-classifier&quot;&gt;1. Task classifier&lt;/h3&gt;

&lt;p&gt;Given a task like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GAME-142 Achievements&lt;/code&gt;, classify:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;endpoint work&lt;/li&gt;
  &lt;li&gt;model work&lt;/li&gt;
  &lt;li&gt;attachment work&lt;/li&gt;
  &lt;li&gt;docs work&lt;/li&gt;
  &lt;li&gt;authorization work&lt;/li&gt;
  &lt;li&gt;specs work&lt;/li&gt;
  &lt;li&gt;swagger work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then load relevant rules automatically.&lt;/p&gt;

&lt;h3 id=&quot;2-context-bundle-generator&quot;&gt;2. Context bundle generator&lt;/h3&gt;

&lt;p&gt;Produce a task-specific context bundle:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Task: GAME-142 Achievements

Authoritative sources:
- doc/requirements/GAME-142_*.md
- related Flow doc
- related PRD if present

Relevant implementation:
- app/controllers/api/v1/avatars/...
- app/models/achievement.rb
- app/blueprints/...
- spec/requests/...

Rules:
- snake_case only
- Ransack + Kaminari
- Blueprinter envelope
- Pundit authorization
- nested attachments in one request
- multipart request specs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;3-drift-checker-expansion&quot;&gt;3. Drift checker expansion&lt;/h3&gt;

&lt;p&gt;Extend &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/contract_audit&lt;/code&gt; to catch more AI-specific drift:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;new response shapes outside blueprints&lt;/li&gt;
  &lt;li&gt;missing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ransackable_attributes&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;missing multipart specs for attachable models&lt;/li&gt;
  &lt;li&gt;missing docs section updates&lt;/li&gt;
  &lt;li&gt;inconsistent endpoint naming&lt;/li&gt;
  &lt;li&gt;unauthorized direct JSON rendering&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;4-retrieval-audit&quot;&gt;4. Retrieval audit&lt;/h3&gt;

&lt;p&gt;Every AI-generated change should be able to answer:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;what context did it use?&lt;/li&gt;
  &lt;li&gt;which sources were authoritative?&lt;/li&gt;
  &lt;li&gt;which files were intentionally ignored?&lt;/li&gt;
  &lt;li&gt;what proof command passed?&lt;/li&gt;
  &lt;li&gt;what contract did it implement?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This would make AI output much easier to trust.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;software-fundamentals-matter-more-than-ever-in-the-ai-era&quot;&gt;Software fundamentals matter more than ever in the AI era&lt;/h2&gt;

&lt;p&gt;Another strong talk that connects directly to the “mergeable by default” idea is:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Software Fundamentals Matter More Than Ever”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The message is extremely important because it explains &lt;em&gt;why&lt;/em&gt; many AI-assisted codebases start degrading even when teams use powerful models.&lt;/p&gt;

&lt;p&gt;The core point is simple:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;AI amplifies the quality of the engineering system around it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That amplification works in both directions.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Existing engineering quality&lt;/th&gt;
      &lt;th&gt;What AI does&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Strong architecture + conventions&lt;/td&gt;
      &lt;td&gt;Accelerates delivery&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Weak architecture + drift&lt;/td&gt;
      &lt;td&gt;Accelerates chaos&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;This is why AI adoption often produces very different outcomes between teams using similar tools.&lt;/p&gt;

&lt;p&gt;One team gets:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;faster delivery&lt;/li&gt;
  &lt;li&gt;cleaner onboarding&lt;/li&gt;
  &lt;li&gt;fewer repetitive tasks&lt;/li&gt;
  &lt;li&gt;safer iteration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another team gets:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;more review debt&lt;/li&gt;
  &lt;li&gt;duplicated abstractions&lt;/li&gt;
  &lt;li&gt;inconsistent APIs&lt;/li&gt;
  &lt;li&gt;growing architectural entropy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difference is rarely the model itself.&lt;/p&gt;

&lt;p&gt;The difference is usually:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;system boundaries&lt;/li&gt;
  &lt;li&gt;naming consistency&lt;/li&gt;
  &lt;li&gt;decomposition quality&lt;/li&gt;
  &lt;li&gt;verification&lt;/li&gt;
  &lt;li&gt;contracts&lt;/li&gt;
  &lt;li&gt;maintainability discipline&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;ai-amplifies-entropy-if-the-codebase-is-already-chaotic&quot;&gt;AI amplifies entropy if the codebase is already chaotic&lt;/h2&gt;

&lt;p&gt;One of the strongest ideas from the second talk is:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;AI makes bad codebases worse faster.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sounds obvious, but it has major implications.&lt;/p&gt;

&lt;p&gt;Before AI:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;weak engineering practices slowed teams down&lt;/li&gt;
  &lt;li&gt;messy systems evolved more slowly&lt;/li&gt;
  &lt;li&gt;architectural damage accumulated gradually&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;AI can generate large amounts of plausible code very quickly&lt;/li&gt;
  &lt;li&gt;bad patterns replicate faster&lt;/li&gt;
  &lt;li&gt;local shortcuts spread across the repo&lt;/li&gt;
  &lt;li&gt;inconsistent abstractions become normalized&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The dangerous part is that the output often &lt;em&gt;looks&lt;/em&gt; reasonable.&lt;/p&gt;

&lt;p&gt;That creates a false sense of progress.&lt;/p&gt;

&lt;p&gt;A weak engineering system with AI can produce:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;more code&lt;/li&gt;
  &lt;li&gt;more PRs&lt;/li&gt;
  &lt;li&gt;more surface area&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;while simultaneously reducing long-term maintainability.&lt;/p&gt;

&lt;p&gt;This is why software fundamentals suddenly matter &lt;em&gt;more&lt;/em&gt;, not less.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;why-rails-works-unusually-well-with-ai&quot;&gt;Why Rails works unusually well with AI&lt;/h2&gt;

&lt;p&gt;This also explains why Rails often performs surprisingly well in AI-assisted development compared to fragmented stacks.&lt;/p&gt;

&lt;p&gt;Rails strongly reduces ambiguity through conventions:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Concern&lt;/th&gt;
      &lt;th&gt;Rails convention&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Models&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/models/**&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Controllers&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/controllers/**&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Request specs&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/requests/**&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Background jobs&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/jobs/**&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Serialization&lt;/td&gt;
      &lt;td&gt;predictable serializer/blueprint layer&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Naming&lt;/td&gt;
      &lt;td&gt;convention over configuration&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Routing&lt;/td&gt;
      &lt;td&gt;consistent REST structure&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;LLMs benefit heavily from predictability.&lt;/p&gt;

&lt;p&gt;The less time the model spends asking:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;“How is this project organized?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;the more time it spends solving the actual task.&lt;/p&gt;

&lt;p&gt;This is one reason why convention-heavy systems become powerful AI multipliers.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;decomposition-becomes-critical&quot;&gt;Decomposition becomes critical&lt;/h2&gt;

&lt;p&gt;Another important point from the second talk:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;LLMs perform much better on small, deterministic tasks than vague, giant problems.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That has major implications for engineering workflows.&lt;/p&gt;

&lt;p&gt;Good AI-assisted systems should encourage:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;bounded contexts&lt;/li&gt;
  &lt;li&gt;small implementation scopes&lt;/li&gt;
  &lt;li&gt;explicit requirements&lt;/li&gt;
  &lt;li&gt;narrow responsibilities&lt;/li&gt;
  &lt;li&gt;isolated verification loops&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This maps directly to:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;feature decomposition&lt;/li&gt;
  &lt;li&gt;Flow docs&lt;/li&gt;
  &lt;li&gt;PRDs&lt;/li&gt;
  &lt;li&gt;contract-first APIs&lt;/li&gt;
  &lt;li&gt;verification-driven development&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without decomposition, AI receives:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;too much ambiguity&lt;/li&gt;
  &lt;li&gt;too many unrelated files&lt;/li&gt;
  &lt;li&gt;unclear authority&lt;/li&gt;
  &lt;li&gt;mixed responsibilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That increases hallucination and drift.&lt;/p&gt;

&lt;p&gt;With decomposition:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;retrieval becomes cleaner&lt;/li&gt;
  &lt;li&gt;verification becomes cheaper&lt;/li&gt;
  &lt;li&gt;generated code becomes more deterministic&lt;/li&gt;
  &lt;li&gt;review becomes faster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is another reason why a context engine matters:
it reduces entropy before generation even starts.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;verification-matters-more-than-generation&quot;&gt;Verification matters more than generation&lt;/h2&gt;

&lt;p&gt;One of the biggest misconceptions in AI engineering is:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;faster generation = better engineering&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In reality:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;verification quality matters more than generation speed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AI is extremely good at producing:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;plausible code&lt;/li&gt;
  &lt;li&gt;plausible architecture&lt;/li&gt;
  &lt;li&gt;plausible explanations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But plausible is not the same as correct.&lt;/p&gt;

&lt;p&gt;That is why systems like:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;contract audits&lt;/li&gt;
  &lt;li&gt;OpenAPI contracts&lt;/li&gt;
  &lt;li&gt;request specs&lt;/li&gt;
  &lt;li&gt;drift prevention&lt;/li&gt;
  &lt;li&gt;architectural rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;become increasingly important in the AI era.&lt;/p&gt;

&lt;p&gt;The stronger the generation becomes, the more important proof becomes.&lt;/p&gt;

&lt;p&gt;Or more simply:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;verification &amp;gt; generation&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;senior-engineers-become-more-important-not-less&quot;&gt;Senior engineers become more important, not less&lt;/h2&gt;

&lt;p&gt;The second talk also indirectly explains why strong senior engineers become &lt;em&gt;more&lt;/em&gt; valuable in AI-assisted environments.&lt;/p&gt;

&lt;p&gt;Junior engineers with AI can generate large amounts of code quickly.&lt;/p&gt;

&lt;p&gt;But senior engineers contribute something different:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;architectural judgment&lt;/li&gt;
  &lt;li&gt;decomposition&lt;/li&gt;
  &lt;li&gt;boundary design&lt;/li&gt;
  &lt;li&gt;tradeoff analysis&lt;/li&gt;
  &lt;li&gt;drift detection&lt;/li&gt;
  &lt;li&gt;maintainability intuition&lt;/li&gt;
  &lt;li&gt;verification discipline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI accelerates implementation.&lt;/p&gt;

&lt;p&gt;Senior engineers define:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;what should exist&lt;/li&gt;
  &lt;li&gt;what should not exist&lt;/li&gt;
  &lt;li&gt;what should remain stable&lt;/li&gt;
  &lt;li&gt;what correctness means&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why the real leverage comes from:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;externalized engineering judgment&lt;/li&gt;
  &lt;li&gt;codified conventions&lt;/li&gt;
  &lt;li&gt;reusable verification systems&lt;/li&gt;
  &lt;li&gt;structured context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not from prompt tricks.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-combined-lesson-from-both-talks&quot;&gt;The combined lesson from both talks&lt;/h2&gt;

&lt;p&gt;The two talks together point toward the same conclusion.&lt;/p&gt;

&lt;p&gt;The future of AI-assisted engineering is not:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;bigger prompts&lt;/li&gt;
  &lt;li&gt;more vibe coding&lt;/li&gt;
  &lt;li&gt;infinite context dumping&lt;/li&gt;
  &lt;li&gt;raw generation speed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The future is:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;explicit context systems&lt;/li&gt;
  &lt;li&gt;deterministic workflows&lt;/li&gt;
  &lt;li&gt;engineering conventions&lt;/li&gt;
  &lt;li&gt;retrieval quality&lt;/li&gt;
  &lt;li&gt;decomposition&lt;/li&gt;
  &lt;li&gt;verification&lt;/li&gt;
  &lt;li&gt;drift prevention&lt;/li&gt;
  &lt;li&gt;mergeability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model matters.&lt;/p&gt;

&lt;p&gt;But the surrounding engineering operating system matters even more.&lt;/p&gt;

&lt;h2 id=&quot;context-retrieval-is-not-enough-without-meaning&quot;&gt;Context retrieval is not enough without meaning&lt;/h2&gt;

&lt;p&gt;One of the most important missing ideas in many AI engineering discussions is this:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;retrieval alone does not explain system meaning.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A vector database can retrieve:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;files&lt;/li&gt;
  &lt;li&gt;specs&lt;/li&gt;
  &lt;li&gt;migrations&lt;/li&gt;
  &lt;li&gt;tickets&lt;/li&gt;
  &lt;li&gt;controllers&lt;/li&gt;
  &lt;li&gt;docs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But retrieval alone does not explain:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;business semantics&lt;/li&gt;
  &lt;li&gt;conceptual relationships&lt;/li&gt;
  &lt;li&gt;authority hierarchy&lt;/li&gt;
  &lt;li&gt;intent boundaries&lt;/li&gt;
  &lt;li&gt;domain meaning&lt;/li&gt;
  &lt;li&gt;organizational language&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where conceptual modeling becomes critical for AI systems.&lt;/p&gt;

&lt;p&gt;A useful phrase from recent discussions is:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Conceptual modeling is the context engineering nobody is doing.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That idea changes how we should think about AI-assisted engineering.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;storage-is-not-the-same-as-meaning&quot;&gt;Storage is not the same as meaning&lt;/h2&gt;

&lt;p&gt;Database schemas primarily describe storage.&lt;/p&gt;

&lt;p&gt;Conceptual models describe meaning.&lt;/p&gt;

&lt;p&gt;For example, a database may contain:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;players&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;guilds&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;scouts&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;guild_pacts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But the semantic/business layer explains things like:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;scout players must belong to verified non-mercenary guilds&lt;/li&gt;
  &lt;li&gt;contracts only apply to certain organization types&lt;/li&gt;
  &lt;li&gt;some users are prospects while others are active customers&lt;/li&gt;
  &lt;li&gt;access states affect workflow permissions&lt;/li&gt;
  &lt;li&gt;ownership and approval rules differ across flows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI model often cannot infer those rules reliably from tables alone.&lt;/p&gt;

&lt;p&gt;Without semantic structure:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;retrieval returns conflicting interpretations&lt;/li&gt;
  &lt;li&gt;agents make incorrect assumptions&lt;/li&gt;
  &lt;li&gt;local correctness breaks global meaning&lt;/li&gt;
  &lt;li&gt;teams accumulate semantic drift&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;semantic-drift-is-becoming-a-new-category-of-engineering-failure&quot;&gt;Semantic drift is becoming a new category of engineering failure&lt;/h2&gt;

&lt;p&gt;One especially important idea is:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;code can technically work while system meaning is already broken.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is semantic drift.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;endpoint contracts technically pass, but business terminology changed&lt;/li&gt;
  &lt;li&gt;authorization still works, but ownership semantics changed&lt;/li&gt;
  &lt;li&gt;response structures remain valid, but workflow meaning shifted&lt;/li&gt;
  &lt;li&gt;entities serialize correctly, but relationships no longer reflect reality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Traditional verification usually catches:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;syntax problems&lt;/li&gt;
  &lt;li&gt;test failures&lt;/li&gt;
  &lt;li&gt;runtime errors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But semantic drift is harder.&lt;/p&gt;

&lt;p&gt;The system still compiles.
The tests may still pass.&lt;/p&gt;

&lt;p&gt;Yet the business meaning has already degraded.&lt;/p&gt;

&lt;p&gt;AI systems make this problem more important because they replicate patterns rapidly.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;metadata-suddenly-became-ai-infrastructure&quot;&gt;Metadata suddenly became AI infrastructure&lt;/h2&gt;

&lt;p&gt;A surprising shift happening across the industry is that many metadata concepts are suddenly becoming foundational AI infrastructure.&lt;/p&gt;

&lt;p&gt;Things like:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;glossaries&lt;/li&gt;
  &lt;li&gt;ontologies&lt;/li&gt;
  &lt;li&gt;semantic layers&lt;/li&gt;
  &lt;li&gt;ownership metadata&lt;/li&gt;
  &lt;li&gt;conceptual models&lt;/li&gt;
  &lt;li&gt;lineage systems&lt;/li&gt;
  &lt;li&gt;business vocabularies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;are turning into:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;navigation systems for AI reasoning.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is one reason why many data and metadata teams are suddenly becoming highly relevant to AI engineering workflows.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-next-stage-of-context-engines&quot;&gt;The next stage of context engines&lt;/h2&gt;

&lt;p&gt;The first generation of AI engineering systems focused mostly on:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;code retrieval&lt;/li&gt;
  &lt;li&gt;embeddings&lt;/li&gt;
  &lt;li&gt;vector search&lt;/li&gt;
  &lt;li&gt;file access&lt;/li&gt;
  &lt;li&gt;larger context windows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next stage is likely semantic-aware systems.&lt;/p&gt;

&lt;p&gt;Instead of only asking:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;“Which files are relevant?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;the system will increasingly ask:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;“What does this system actually mean?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A semantic-aware context system can explain:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;why a concept exists&lt;/li&gt;
  &lt;li&gt;how concepts relate&lt;/li&gt;
  &lt;li&gt;which definitions are authoritative&lt;/li&gt;
  &lt;li&gt;which workflows matter&lt;/li&gt;
  &lt;li&gt;which constraints are organizational vs technical&lt;/li&gt;
  &lt;li&gt;what should remain stable across implementations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a much deeper level of context than code search alone.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;how-this-maps-to-my-current-workflow&quot;&gt;How this maps to my current workflow&lt;/h2&gt;

&lt;p&gt;Looking at my own Rails/API workflow, I already externalized a large amount of engineering judgment through:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;AGENTS.md&lt;/li&gt;
  &lt;li&gt;Flow docs&lt;/li&gt;
  &lt;li&gt;PRDs&lt;/li&gt;
  &lt;li&gt;requirements&lt;/li&gt;
  &lt;li&gt;verification systems&lt;/li&gt;
  &lt;li&gt;contract audits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But conceptual modeling adds another important layer:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Existing layer&lt;/th&gt;
      &lt;th&gt;Role&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Requirements&lt;/td&gt;
      &lt;td&gt;behavioral intent&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Flow docs&lt;/td&gt;
      &lt;td&gt;implementation semantics&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;AGENTS.md&lt;/td&gt;
      &lt;td&gt;engineering rules&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Verification&lt;/td&gt;
      &lt;td&gt;correctness proof&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Conceptual modeling&lt;/td&gt;
      &lt;td&gt;business meaning&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;This changes the shape of the system.&lt;/p&gt;

&lt;p&gt;It stops being only:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;an engineering operating system&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and starts becoming:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;a semantic operating system for AI-assisted engineering.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;the-final-takeaway&quot;&gt;The final takeaway&lt;/h2&gt;

&lt;p&gt;The future of AI coding is not just smarter code generation.&lt;/p&gt;

&lt;p&gt;It is better engineering context.&lt;/p&gt;

&lt;p&gt;The best teams will not only ask:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Which model should we use?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They will ask:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;What system makes the model correct inside our codebase?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That system will include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;requirements&lt;/li&gt;
  &lt;li&gt;conventions&lt;/li&gt;
  &lt;li&gt;code search&lt;/li&gt;
  &lt;li&gt;decision history&lt;/li&gt;
  &lt;li&gt;permission control&lt;/li&gt;
  &lt;li&gt;task-specific retrieval&lt;/li&gt;
  &lt;li&gt;verification&lt;/li&gt;
  &lt;li&gt;drift prevention&lt;/li&gt;
  &lt;li&gt;review feedback loops&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the real lesson from “Mergeable by default.”&lt;/p&gt;

&lt;p&gt;AI does not become valuable because it writes more code.&lt;/p&gt;

&lt;p&gt;AI becomes valuable when the surrounding engineering system makes its output &lt;strong&gt;safe enough, relevant enough, and consistent enough&lt;/strong&gt; to merge.&lt;/p&gt;

&lt;p&gt;For me, that means the next stage of AI-assisted Rails development is clear:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;turn the existing verification-driven workflow into an explicit context engine.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not more prompt magic.&lt;br /&gt;
Not bigger context for the sake of bigger context.&lt;br /&gt;
Not vibe-coded PRs.&lt;/p&gt;

&lt;p&gt;A system that gives the agent the right context, blocks the wrong drift, proves the result, and makes the final state boring:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;all good — merge PR.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=5ID22ACI7IM&quot;&gt;Mergeable by default: Building the context engine to save time and tokens — YouTube&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.ai.engineer/europe/schedule&quot;&gt;AI Engineer Europe 2026 schedule&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://app.daily.dev/posts/mergeable-by-default-building-the-context-engine-to-save-time-and-tokens-peter-werry-unblocked-pmhqjsuzp&quot;&gt;Daily.dev listing for the talk&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Wed, 06 May 2026 00:00:00 +0000</pubDate>
        <link>https://lukin.io/blog/mergeable-by-default-context-engine-ai-codegen/</link>
        <guid isPermaLink="true">https://lukin.io/blog/mergeable-by-default-context-engine-ai-codegen/</guid>
        
        <category>ai</category>
        
        <category>software-engineering</category>
        
        <category>context-engineering</category>
        
        <category>agents</category>
        
        <category>codegen</category>
        
        <category>verification</category>
        
        <category>productivity</category>
        
        
        <category>engineering</category>
        
        <category>AI</category>
        
        <category>software-engineering</category>
        
      </item>
    
      <item>
        <title>Why Test Refactoring Matters: Turning 12-Minute Builds Into a 39-Second Proof Loop</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;“Slow tests do not only slow CI. They change how often you are willing to prove that the system still works.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;tldr&quot;&gt;TL;DR&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Metric&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Before&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Current&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Change&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Old build/proof loop used for planning math&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;~12 min&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;39.42s local fast verification&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;~18.3x faster&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Initial broad profiled RSpec runtime&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;4m 8.6s&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;2m 43.1s split profiled runtime&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;~34% faster&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;SQL events&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;571,641&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;413,055&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;-158,586 / ~28% fewer&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Factory calls&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;36,133&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;31,935&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;-4,198 / ~12% fewer&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Fast verification proof&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;n/a&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;8,659 examples / 0 failures / 28 pending&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;green&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Parallel RSpec phase&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;n/a&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;32.35s&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;green&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Full &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt; local loop&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;n/a&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;39.42s&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;green&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Processed executable test files&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;n/a&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;~72 / 714&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;~10.1%&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Current heavy-tail status&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;n/a&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;39 tests still use broad local reference context&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;next target&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The important part is not that 10% of files were touched.&lt;/p&gt;

&lt;p&gt;The important part is that the &lt;strong&gt;right 10%&lt;/strong&gt; was touched first.&lt;/p&gt;

&lt;p&gt;That 10.1% of executable test files produced:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;~34% less profiled runtime&lt;/li&gt;
  &lt;li&gt;~28% fewer SQL events&lt;/li&gt;
  &lt;li&gt;a fast local proof loop around 39 seconds&lt;/li&gt;
  &lt;li&gt;no API behavior changes&lt;/li&gt;
  &lt;li&gt;no removed assertions&lt;/li&gt;
  &lt;li&gt;no reduction in request/integration confidence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are shipping around &lt;strong&gt;10 PRs per day&lt;/strong&gt;, and each PR gets about &lt;strong&gt;3 commits&lt;/strong&gt;, that is roughly:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;10 PR/day * 3 commits = 30 proof loops/day
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With a 12-minute build:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;30 * 12 min = 360 min/day = 6 hours/day waiting on proof
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With the current 39.42-second local proof loop:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;30 * 39.42s = 1,182.6s = 19.7 min/day
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So the saved time is:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;360 min - 19.7 min = 340.3 min/day
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That is &lt;strong&gt;5 hours 40 minutes per day&lt;/strong&gt; back.&lt;/p&gt;

&lt;p&gt;Or, if you reinvest only the saved waiting time into additional current-speed proof loops:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;340.3 min / 0.657 min = ~518 extra proof loops/day
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That does not mean you should make 518 more commits per day.&lt;/p&gt;

&lt;p&gt;It means the old loop made verification expensive enough that you would naturally batch risk. The new loop makes verification cheap enough that you can commit smaller, check more often, and keep moving without gambling.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-cost-of-not-refactoring&quot;&gt;The cost of not refactoring&lt;/h2&gt;

&lt;p&gt;The easiest way to undervalue test performance is to look at one build.&lt;/p&gt;

&lt;p&gt;One 12-minute build is annoying.&lt;/p&gt;

&lt;p&gt;Thirty 12-minute proof loops per day is a workflow tax.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Cadence&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Old 12-minute loop&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Current 39.42-second loop&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Time recovered&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;1 proof loop&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;12 min&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;39.42 sec&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;11m 20s&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;30 proof loops/day&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;6h/day&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;19.7m/day&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;5h 40m/day&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;150 proof loops/week&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;30h/week&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1h 38m/week&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;28h 22m/week&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;600 proof loops/month&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;120h/month&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;6h 34m/month&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;113h 26m/month&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;This is why slow tests are not only a developer-experience issue.&lt;/p&gt;

&lt;p&gt;They change engineering behavior:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;you commit less often&lt;/li&gt;
  &lt;li&gt;you batch more risk&lt;/li&gt;
  &lt;li&gt;you delay verification&lt;/li&gt;
  &lt;li&gt;you wait longer to discover drift&lt;/li&gt;
  &lt;li&gt;you become tempted to skip checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fast tests do not guarantee quality.&lt;/p&gt;

&lt;p&gt;But slow tests make quality expensive enough that people naturally start negotiating with it.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-honest-caveat&quot;&gt;The honest caveat&lt;/h2&gt;

&lt;p&gt;There are two “current time” numbers worth separating:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Current number&lt;/th&gt;
      &lt;th&gt;What it means&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;39.42s&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Local &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt; fast loop: RuboCop, parallel RSpec, RSwag generation. This is the day-to-day proof loop.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;2m 43.1s&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Instrumented TestProf split profile. This pays profiling overhead and is used for diagnosis, not every commit.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The old &lt;strong&gt;~12 min&lt;/strong&gt; number is the build-time pain point. The new &lt;strong&gt;39.42s&lt;/strong&gt; is the observed local fast verification loop after parallelization and test cleanup. CI still has container, bundle, database, and service overhead, so I do not treat local time as a perfect CI replacement.&lt;/p&gt;

&lt;p&gt;But the engineering lesson is still valid:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The closer the proof loop gets to seconds instead of minutes, the more often you can afford to prove correctness.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;why-this-refactor-happened&quot;&gt;Why this refactor happened&lt;/h2&gt;

&lt;p&gt;The trigger was simple: CI started hitting build-time limits.&lt;/p&gt;

&lt;p&gt;The request/integration step was the visible pain:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rspec spec/integration spec/requests &lt;span class=&quot;nt&quot;&gt;--tag&lt;/span&gt; ~full_only &lt;span class=&quot;nt&quot;&gt;--fail-fast&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--format&lt;/span&gt; progress
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;At first, the temptation is obvious:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;increase CI timeout&lt;/li&gt;
  &lt;li&gt;split one huge file&lt;/li&gt;
  &lt;li&gt;skip slow tests&lt;/li&gt;
  &lt;li&gt;move on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We did increase the Bitbucket &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;max-time&lt;/code&gt; guard to 12 minutes, but that was not the fix. That was only a safety margin.&lt;/p&gt;

&lt;p&gt;The actual fix was to profile the suite and stop guessing.&lt;/p&gt;

&lt;p&gt;The first broad profile showed the shape of the problem:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Run&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Examples&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Runtime&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;SQL events&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;SQL time&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Factories&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Initial broad &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/&lt;/code&gt; profile&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;8,659&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;4m 8.6s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;571,641&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1m 57.4s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;36,133&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Almost half the profiled runtime was SQL.&lt;/p&gt;

&lt;p&gt;That matters because SQL-heavy tests are rarely fixed by staring at one assertion. The cost usually lives in setup:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;create(:user)&lt;/code&gt; cascades&lt;/li&gt;
  &lt;li&gt;avatar auto-creation&lt;/li&gt;
  &lt;li&gt;guild/reference catalogs&lt;/li&gt;
  &lt;li&gt;callbacks&lt;/li&gt;
  &lt;li&gt;chat/message graphs&lt;/li&gt;
  &lt;li&gt;access-request state machines&lt;/li&gt;
  &lt;li&gt;serializer renders that pull large association trees&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the question changed from:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Which test is slow?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Which setup is repeated, realistic, and unnecessary for the behavior under test?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a better question.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-operating-rule-faster-not-weaker&quot;&gt;The operating rule: faster, not weaker&lt;/h2&gt;

&lt;p&gt;The rule for the session was strict:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Optimize tests without losing confidence.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That meant no cheap wins like deleting assertions or converting request tests into shallow unit tests just to make the graph look better.&lt;/p&gt;

&lt;p&gt;The safety rules were:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Rule&lt;/th&gt;
      &lt;th&gt;How it was applied&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Keep request/integration confidence&lt;/td&gt;
      &lt;td&gt;Request tests still hit real endpoints, auth, serializers, callbacks, and DB writes.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Keep mutation behavior real&lt;/td&gt;
      &lt;td&gt;Command, callback, uniqueness, destroy, chat/message, access-request, and quest-state examples keep persisted rows.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build_stubbed&lt;/code&gt; only for read-only checks&lt;/td&gt;
      &lt;td&gt;Policy predicates, logger metadata, and serializer-only cases can be stubbed when the DB is not the behavior.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let_it_be&lt;/code&gt; only for stable records&lt;/td&gt;
      &lt;td&gt;Shared users, guilds, quests, and avatars are read-only parents; rows mutated by examples stay local.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Decouple app tests from seed runtime&lt;/td&gt;
      &lt;td&gt;App tests use explicit factory-backed local reference data, not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lib/tasks/seeds/**&lt;/code&gt;.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Do not remove assertions for speed&lt;/td&gt;
      &lt;td&gt;The target is setup shape, not coverage reduction.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;This distinction matters.&lt;/p&gt;

&lt;p&gt;Bad test optimization asks:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;How do we make the suite green faster?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Good test optimization asks:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Which expensive work is unrelated to the behavior this example proves?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-we-did-not-do&quot;&gt;What we did not do&lt;/h2&gt;

&lt;p&gt;This part matters because “test optimization” often sounds like a polite way to say “less coverage.”&lt;/p&gt;

&lt;p&gt;That was not the deal.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Shortcut&lt;/th&gt;
      &lt;th&gt;Why we did not take it&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Delete slow tests&lt;/td&gt;
      &lt;td&gt;Deleted tests make dashboards faster and confidence lower. That is not optimization.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Mock request/integration behavior&lt;/td&gt;
      &lt;td&gt;Request tests are expensive because they prove routing, auth, controller envelopes, policies, serializers, callbacks, and DB behavior together.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Replace mutation tests with read-only doubles&lt;/td&gt;
      &lt;td&gt;Commands, callbacks, uniqueness, destroy behavior, chat/messages, access requests, credits, and quest state need persisted rows.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Hide seed problems by deleting seed tests&lt;/td&gt;
      &lt;td&gt;Seed runtime tests remain in the repo. They are skipped in the default suite and can be run when seed work is active.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Change API code to satisfy faster tests&lt;/td&gt;
      &lt;td&gt;This was test-only work. API behavior, JSON shape, authorization, and persistence rules stayed unchanged.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Split huge files just to make charts look better&lt;/td&gt;
      &lt;td&gt;Splitting can help ownership, but it does not remove SQL or factory cost by itself. Setup shape was the real target.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The strongest signal from the session was this:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;We got faster by removing unrelated setup, not by reducing the behavioral contract.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-changed&quot;&gt;What changed&lt;/h2&gt;

&lt;h3 id=&quot;1-added-profiling-as-a-real-workflow&quot;&gt;1. Added profiling as a real workflow&lt;/h3&gt;

&lt;p&gt;We made TestProf the profiling path and used:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;env &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;FPROF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;EVENT_PROF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;sql.active_record &lt;span class=&quot;nv&quot;&gt;EVENT_PROF_EXAMPLES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;EVENT_PROF_TOP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;40 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rspec spec/integration spec/requests &lt;span class=&quot;nt&quot;&gt;--tag&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;~full_only&apos;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--format&lt;/span&gt; progress
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;env &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;FPROF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;EVENT_PROF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;sql.active_record &lt;span class=&quot;nv&quot;&gt;EVENT_PROF_EXAMPLES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;EVENT_PROF_TOP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;40 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rspec spec/bin spec/blueprints spec/channels spec/config spec/controllers spec/db &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  spec/forms spec/initializers spec/workers spec/lib spec/mailers spec/middleware spec/models &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  spec/policies spec/queries spec/routing spec/services spec/smoke spec/tasks spec/validators &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--tag&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;~full_only&apos;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--format&lt;/span&gt; progress
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That gave two useful views:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EventProf&lt;/code&gt; showed SQL cost by file and example&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FactoryProf&lt;/code&gt; showed factory cascades and top-level setup cost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the suite had numbers, the refactor stopped being emotional.&lt;/p&gt;

&lt;h3 id=&quot;2-split-ci-by-work-type&quot;&gt;2. Split CI by work type&lt;/h3&gt;

&lt;p&gt;The Bitbucket flow now separates:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;lint/security&lt;/li&gt;
  &lt;li&gt;unit/model/blueprint/policy/service/worker/query/form/lib tests&lt;/li&gt;
  &lt;li&gt;request/integration tests&lt;/li&gt;
  &lt;li&gt;Swagger generation&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;full_only&lt;/code&gt; tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Request/integration and non-request tests can run in parallel workers.&lt;/p&gt;

&lt;p&gt;The result is not just shorter wall time. It also makes slow areas easier to reason about. A slow request test and a slow model test are usually slow for different reasons.&lt;/p&gt;

&lt;h3 id=&quot;3-removed-seed-runtime-from-app-tests&quot;&gt;3. Removed seed runtime from app tests&lt;/h3&gt;

&lt;p&gt;This was a key cleanup.&lt;/p&gt;

&lt;p&gt;The app tests used to lean on seeded reference data. That made them realistic, but it also tied normal verification to seed runtime behavior and large catalogs.&lt;/p&gt;

&lt;p&gt;We replaced that with explicit factory-backed local reference data:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;guild types&lt;/li&gt;
  &lt;li&gt;guild domains&lt;/li&gt;
  &lt;li&gt;guild sizes&lt;/li&gt;
  &lt;li&gt;avatar taxonomy&lt;/li&gt;
  &lt;li&gt;quest board categories&lt;/li&gt;
  &lt;li&gt;realms&lt;/li&gt;
  &lt;li&gt;message channels&lt;/li&gt;
  &lt;li&gt;training tiers&lt;/li&gt;
  &lt;li&gt;guild permit validation rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Seed tests still exist. They are not deleted. They are skipped in the default suite and can be run locally when working on seeds.&lt;/p&gt;

&lt;p&gt;This made the boundary cleaner:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Area&lt;/th&gt;
      &lt;th&gt;Responsibility&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;App tests&lt;/td&gt;
      &lt;td&gt;Verify app behavior with explicit local reference rows.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Seed tests&lt;/td&gt;
      &lt;td&gt;Verify seed runtime when seed work is being changed.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;4-started-splitting-broad-local-reference-data&quot;&gt;4. Started splitting broad local reference data&lt;/h3&gt;

&lt;p&gt;After moving away from seed runtime, a new cost appeared: the replacement local reference context was intentionally broad.&lt;/p&gt;

&lt;p&gt;That was the right first move for reliability, but it became the next optimization target.&lt;/p&gt;

&lt;p&gt;The broad context created:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;guild options&lt;/li&gt;
  &lt;li&gt;avatar taxonomy&lt;/li&gt;
  &lt;li&gt;realms&lt;/li&gt;
  &lt;li&gt;message channels&lt;/li&gt;
  &lt;li&gt;training tiers&lt;/li&gt;
  &lt;li&gt;avatar link labels&lt;/li&gt;
  &lt;li&gt;guild permit validation rows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some tests only needed one slice.&lt;/p&gt;

&lt;p&gt;So the first split introduced narrower contexts:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;with local guild reference data&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;with local guild types&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;with local guild domains&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;with local guild sizes&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;with local guild permit validation rules&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;with local training tiers&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;with local message channels&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then seven tests moved off the broad context:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/services/guilds/guild_permit_validator_spec.rb&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/models/guild_permit_validation_setting_spec.rb&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/models/guild_size_spec.rb&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/models/guild_type_spec.rb&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/models/guild_domain_spec.rb&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/requests/api/v1/message_channels_spec.rb&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/requests/api/v1/public/vocabulary/training_tiers_spec.rb&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Focused result:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Batch&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Examples&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Runtime&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;SQL events&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Factories&lt;/th&gt;
      &lt;th&gt;Result&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Narrow reference-context batch&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;106&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.92s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1,558&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;160&lt;/td&gt;
      &lt;td&gt;0 failures&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;5-optimized-the-heaviest-files-first&quot;&gt;5. Optimized the heaviest files first&lt;/h3&gt;

&lt;p&gt;This is where the 10% number becomes important.&lt;/p&gt;

&lt;p&gt;We did not process 10% of files randomly.&lt;/p&gt;

&lt;p&gt;We processed roughly &lt;strong&gt;72 of 714 executable test files&lt;/strong&gt;, or &lt;strong&gt;~10.1%&lt;/strong&gt;, sorted by profiling impact.&lt;/p&gt;

&lt;p&gt;That included:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;request workflow tests&lt;/li&gt;
  &lt;li&gt;policy tests&lt;/li&gt;
  &lt;li&gt;blueprint tests&lt;/li&gt;
  &lt;li&gt;model tests&lt;/li&gt;
  &lt;li&gt;service tests&lt;/li&gt;
  &lt;li&gt;seed/reference support boundaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples of file-level wins:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Test file&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Before&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;After&lt;/th&gt;
      &lt;th&gt;Main change&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/policies/quest_assignment_policy_spec.rb&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;~1,320 SQL&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;74 SQL&lt;/td&gt;
      &lt;td&gt;Role/guild policy matrix moved to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build_stubbed&lt;/code&gt;; scope records stayed persisted.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/policies/avatar_policy_spec.rb&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;~1,358 SQL&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;83 SQL&lt;/td&gt;
      &lt;td&gt;Policy predicates moved to stubbed users/avatars; scope stayed persisted.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/blueprints/messages/location_setting_blueprint_spec.rb&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;~1,207 SQL&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;10 SQL&lt;/td&gt;
      &lt;td&gt;Serializer-only behavior stopped creating a full persisted avatar graph.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/blueprints/avatar_note_blueprint_spec.rb&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;~742 SQL&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0 SQL&lt;/td&gt;
      &lt;td&gt;Plain object replaced DB-backed setup for serializer-only behavior.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/services/visibility/visibility_flags_spec.rb&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;~1,702 SQL&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;219 SQL&lt;/td&gt;
      &lt;td&gt;Read examples use stubbed/assigned avatar; update behavior stayed persisted.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/models/guild_size_spec.rb&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1,545 SQL&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;153 SQL&lt;/td&gt;
      &lt;td&gt;Narrow guild-size reference context.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/models/guild_type_spec.rb&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1,560 SQL&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;256 SQL&lt;/td&gt;
      &lt;td&gt;Narrow guild-type reference context.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/models/guild_domain_spec.rb&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1,628 SQL&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;687 SQL&lt;/td&gt;
      &lt;td&gt;Guild type + domain rows only.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/requests/api/v1/users/avatars/request_access_spec.rb&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;5,659 SQL&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1,847 SQL&lt;/td&gt;
      &lt;td&gt;Stable actors shared; access, credits, chats, and quests stayed real.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;This is why profiler order matters.&lt;/p&gt;

&lt;p&gt;If the current 10.1% of files produced a 34% runtime reduction, the naive math says:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;34% / 10.1% = 3.37x impact per file-percent
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If that scaled linearly to 100%, it would imply:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;3.37 * 100% = 337% runtime reduction
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That is impossible.&lt;/p&gt;

&lt;p&gt;And that impossibility is the point.&lt;/p&gt;

&lt;p&gt;The first 10% was not normal. It was the expensive slice. Profiling let us find the part of the suite where small, careful changes had disproportionate return.&lt;/p&gt;

&lt;p&gt;The realistic lesson is:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Do not optimize all tests equally. Optimize the tests that are paying rent in every build.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-time-math-at-shipping-cadence&quot;&gt;The time math at shipping cadence&lt;/h2&gt;

&lt;p&gt;Assume this daily cadence:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;10 PRs/day
3 commits/PR
30 proof loops/day
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;old-loop-12-minutes&quot;&gt;Old loop: 12 minutes&lt;/h3&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;30 * 12 min = 360 min/day
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That is:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;6 hours/day&lt;/li&gt;
  &lt;li&gt;30 hours/week&lt;/li&gt;
  &lt;li&gt;120 hours/month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is not a test suite.&lt;/p&gt;

&lt;p&gt;That is a part-time job waiting for CI.&lt;/p&gt;

&lt;h3 id=&quot;current-fast-loop-3942-seconds&quot;&gt;Current fast loop: 39.42 seconds&lt;/h3&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;39.42s = 0.657 min
30 * 0.657 min = 19.7 min/day
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That is:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;19.7 minutes/day&lt;/li&gt;
  &lt;li&gt;1 hour 38 minutes/week&lt;/li&gt;
  &lt;li&gt;6 hours 34 minutes/month&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;time-recovered&quot;&gt;Time recovered&lt;/h3&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;360 min/day - 19.7 min/day = 340.3 min/day
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That is:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;5 hours 40 minutes/day&lt;/li&gt;
  &lt;li&gt;28 hours 22 minutes/week&lt;/li&gt;
  &lt;li&gt;113 hours/month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you spend that recovered time only on additional current-speed proof loops:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;340.3 min/day / 0.657 min = ~518 extra proof loops/day
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Again, this is not a recommendation to make hundreds of commits.&lt;/p&gt;

&lt;p&gt;It is a way to feel the magnitude.&lt;/p&gt;

&lt;p&gt;A slow build taxes every commit. A fast proof loop makes small commits cheap.&lt;/p&gt;

&lt;h3 id=&quot;conservative-profiler-time-version&quot;&gt;Conservative profiler-time version&lt;/h3&gt;

&lt;p&gt;If we use the slower instrumented profile number instead:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;2m 43.1s = 2.718 min
30 * 2.718 min = 81.5 min/day
360 - 81.5 = 278.5 min/day saved
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Even with the conservative number, the suite gives back:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;4 hours 38 minutes/day&lt;/li&gt;
  &lt;li&gt;23 hours 12 minutes/week&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is still huge.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;why-this-matters-more-with-ai-assisted-development&quot;&gt;Why this matters more with AI-assisted development&lt;/h2&gt;

&lt;p&gt;With AI, generating a patch is no longer the bottleneck.&lt;/p&gt;

&lt;p&gt;The bottleneck becomes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;proving the patch&lt;/li&gt;
  &lt;li&gt;reviewing the patch&lt;/li&gt;
  &lt;li&gt;finding subtle drift&lt;/li&gt;
  &lt;li&gt;avoiding slow feedback loops&lt;/li&gt;
  &lt;li&gt;keeping confidence high while throughput rises&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can create 10 PRs a day, but each proof loop costs 12 minutes, the system pushes you toward bad habits:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;bigger commits&lt;/li&gt;
  &lt;li&gt;fewer local checks&lt;/li&gt;
  &lt;li&gt;more batching&lt;/li&gt;
  &lt;li&gt;delayed feedback&lt;/li&gt;
  &lt;li&gt;risk discovered late&lt;/li&gt;
  &lt;li&gt;more review anxiety&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is how technical debt quietly taxes AI productivity.&lt;/p&gt;

&lt;p&gt;AI can generate code quickly, but CI decides how quickly you can trust it.&lt;/p&gt;

&lt;p&gt;This is why test refactoring belongs in the same conversation as AI productivity. It is not janitorial work. It is throughput infrastructure.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-stayed-reliable&quot;&gt;What stayed reliable&lt;/h2&gt;

&lt;p&gt;The most important result is not speed. It is speed without weakening the test suite.&lt;/p&gt;

&lt;p&gt;The current proof still runs:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Check&lt;/th&gt;
      &lt;th&gt;Current result&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;RuboCop&lt;/td&gt;
      &lt;td&gt;clean&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Parallel RSpec&lt;/td&gt;
      &lt;td&gt;8,659 examples, 0 failures, 28 pending&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;RSwag Swaggerize&lt;/td&gt;
      &lt;td&gt;generated successfully&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Contract audit&lt;/td&gt;
      &lt;td&gt;6/6 checks passed&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;And the behavioral boundaries stayed intact:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;request tests still exercise real routes&lt;/li&gt;
  &lt;li&gt;authentication is still real&lt;/li&gt;
  &lt;li&gt;serializers are still rendered&lt;/li&gt;
  &lt;li&gt;DB-backed mutation behavior stays DB-backed&lt;/li&gt;
  &lt;li&gt;authorization scopes remain persisted where scope behavior is under test&lt;/li&gt;
  &lt;li&gt;seed tests remain in the repository&lt;/li&gt;
  &lt;li&gt;app tests no longer accidentally depend on seed runtime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the difference between optimization and coverage erosion.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-is-still-left&quot;&gt;What is still left&lt;/h2&gt;

&lt;p&gt;This is not done.&lt;/p&gt;

&lt;p&gt;The remaining heaviest chunk is still visible:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;continue splitting broad &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;with local reference data&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/requests/llm_gateway/sessions_spec.rb&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/requests/api/v1/avatars/avatar_cards_spec.rb&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/requests/api/v1/guilds/avatar/public_quests_spec.rb&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/requests/api/v1/guilds/team_management_spec.rb&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/requests/api/v1/users/conversation_messages_spec.rb&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;broad &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/requests/api/v1/avatars_spec.rb&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are still 39 tests using the broad local reference context.&lt;/p&gt;

&lt;p&gt;There are still request tests with 4,000-6,700 SQL events each.&lt;/p&gt;

&lt;p&gt;There are still workflow tests where the cost is real and should not be mocked away.&lt;/p&gt;

&lt;p&gt;That is fine.&lt;/p&gt;

&lt;p&gt;The goal is not to make every test microscopic.&lt;/p&gt;

&lt;p&gt;The goal is to make expensive tests expensive for a reason.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;how-to-start-tomorrow&quot;&gt;How to start tomorrow&lt;/h2&gt;

&lt;p&gt;If I had to start this again in another Rails API, I would not begin by editing tests.&lt;/p&gt;

&lt;p&gt;I would begin with a short checklist:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Step&lt;/th&gt;
      &lt;th&gt;Command or action&lt;/th&gt;
      &lt;th&gt;Why&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;Install or enable TestProf&lt;/td&gt;
      &lt;td&gt;You need numbers before opinions.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FPROF=1 EVENT_PROF=sql.active_record&lt;/code&gt; on the slowest suite&lt;/td&gt;
      &lt;td&gt;This finds SQL and factory pressure.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt;Sort files by SQL events and factory count&lt;/td&gt;
      &lt;td&gt;Optimize cost, not file count.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt;Pick one file&lt;/td&gt;
      &lt;td&gt;Small batches keep reliability easy to verify.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;5&lt;/td&gt;
      &lt;td&gt;Identify examples above ~250 SQL events&lt;/td&gt;
      &lt;td&gt;Those examples usually have setup duplication.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;6&lt;/td&gt;
      &lt;td&gt;Move stable read-only setup to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let_it_be&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Avoid rebuilding the same parent graph.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;7&lt;/td&gt;
      &lt;td&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build_stubbed&lt;/code&gt; only for pure read/predicate checks&lt;/td&gt;
      &lt;td&gt;Do not fake DB behavior that is under test.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;8&lt;/td&gt;
      &lt;td&gt;Keep mutation rows per-example&lt;/td&gt;
      &lt;td&gt;State transitions must stay isolated.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;9&lt;/td&gt;
      &lt;td&gt;Rerun the focused file&lt;/td&gt;
      &lt;td&gt;Prove the local change first.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10&lt;/td&gt;
      &lt;td&gt;Rerun full verification&lt;/td&gt;
      &lt;td&gt;Make sure the suite still tells the truth.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The first useful command is usually:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;env &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;FPROF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;EVENT_PROF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;sql.active_record &lt;span class=&quot;nv&quot;&gt;EVENT_PROF_EXAMPLES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;EVENT_PROF_TOP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;40 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rspec spec/path/to/heavy_spec.rb &lt;span class=&quot;nt&quot;&gt;--format&lt;/span&gt; progress
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The first useful question is:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;What did this example create that it never actually used?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question is where most safe wins start.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-practical-playbook&quot;&gt;The practical playbook&lt;/h2&gt;

&lt;p&gt;This is the pattern I would reuse:&lt;/p&gt;

&lt;h3 id=&quot;1-measure-before-changing&quot;&gt;1. Measure before changing&lt;/h3&gt;

&lt;p&gt;Use SQL and factory profiling:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;env &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;FPROF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;EVENT_PROF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;sql.active_record &lt;span class=&quot;nv&quot;&gt;EVENT_PROF_EXAMPLES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;EVENT_PROF_TOP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;40 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rspec spec/path/to/heavy_spec.rb &lt;span class=&quot;nt&quot;&gt;--format&lt;/span&gt; progress
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Do not guess.&lt;/p&gt;

&lt;h3 id=&quot;2-sort-by-cost-not-file-count&quot;&gt;2. Sort by cost, not file count&lt;/h3&gt;

&lt;p&gt;The current 10.1% progress produced a 34% runtime reduction because the files were chosen by cost.&lt;/p&gt;

&lt;p&gt;Randomly refactoring 10% of files would not do that.&lt;/p&gt;

&lt;h3 id=&quot;3-separate-read-behavior-from-mutation-behavior&quot;&gt;3. Separate read behavior from mutation behavior&lt;/h3&gt;

&lt;p&gt;Use:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build_stubbed&lt;/code&gt; for pure read/predicate checks&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let_it_be&lt;/code&gt; for stable read-only parents&lt;/li&gt;
  &lt;li&gt;persisted per-example rows for mutations, callbacks, uniqueness, scopes, and state transitions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;4-split-broad-fixtures-into-narrow-catalogs&quot;&gt;4. Split broad fixtures into narrow catalogs&lt;/h3&gt;

&lt;p&gt;This is especially important for reference data.&lt;/p&gt;

&lt;p&gt;If a test only needs training tiers, do not load guild domains, realms, quest categories, skill trees, and guild permit rules.&lt;/p&gt;

&lt;h3 id=&quot;5-keep-request-tests-honest&quot;&gt;5. Keep request tests honest&lt;/h3&gt;

&lt;p&gt;Do not replace request tests with unit tests just because request tests are slower.&lt;/p&gt;

&lt;p&gt;Request tests buy confidence across:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;routing&lt;/li&gt;
  &lt;li&gt;auth&lt;/li&gt;
  &lt;li&gt;controller envelope&lt;/li&gt;
  &lt;li&gt;policy&lt;/li&gt;
  &lt;li&gt;serializer&lt;/li&gt;
  &lt;li&gt;DB state&lt;/li&gt;
  &lt;li&gt;callbacks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only move assertions down to blueprint/model/service tests when the request test is only duplicating shape checks already covered elsewhere.&lt;/p&gt;

&lt;h3 id=&quot;6-make-verification-cheap-enough-to-run-constantly&quot;&gt;6. Make verification cheap enough to run constantly&lt;/h3&gt;

&lt;p&gt;The goal is not only faster CI.&lt;/p&gt;

&lt;p&gt;The goal is a proof loop cheap enough that you naturally run it before you are nervous.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;closing&quot;&gt;Closing&lt;/h2&gt;

&lt;p&gt;Test refactoring is easy to postpone because it rarely ships a visible feature.&lt;/p&gt;

&lt;p&gt;But at high throughput, slow verification is not a background inconvenience. It is a tax on every commit.&lt;/p&gt;

&lt;p&gt;At &lt;strong&gt;10 PRs/day&lt;/strong&gt; and &lt;strong&gt;3 commits/PR&lt;/strong&gt;, a 12-minute build turns into &lt;strong&gt;6 hours/day&lt;/strong&gt; of waiting.&lt;/p&gt;

&lt;p&gt;At the current &lt;strong&gt;39.42-second&lt;/strong&gt; proof loop, that same cadence costs about &lt;strong&gt;20 minutes/day&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is the difference between:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;batching risk because proof is expensive&lt;/li&gt;
  &lt;li&gt;proving continuously because proof is cheap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why the refactor mattered.&lt;/p&gt;

&lt;p&gt;Not because the suite became pretty.&lt;/p&gt;

&lt;p&gt;Because the suite became usable at the speed the work was happening.&lt;/p&gt;
</description>
        <pubDate>Thu, 30 Apr 2026 00:00:00 +0000</pubDate>
        <link>https://lukin.io/blog/why-test-refactoring-matters/</link>
        <guid isPermaLink="true">https://lukin.io/blog/why-test-refactoring-matters/</guid>
        
        <category>rails</category>
        
        <category>rspec</category>
        
        <category>ci</category>
        
        <category>testprof</category>
        
        <category>profiling</category>
        
        <category>technical-debt</category>
        
        <category>productivity</category>
        
        <category>verification</category>
        
        
        <category>engineering</category>
        
        <category>rails</category>
        
        <category>best-practices</category>
        
        <category>testing</category>
        
        <category>ci</category>
        
      </item>
    
      <item>
        <title>How I Turned AI Into a 5x Backend Force Multiplier as One Principal Engineer</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;“The biggest AI productivity gain I’ve seen wasn’t faster code generation. It was turning senior judgment into a system that makes drift expensive and correctness cheap.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;tldr&quot;&gt;TL;DR&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Metric&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Value&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Solo backend output&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;400+ PRs&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Wider comparison group&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;11 engineers / 1,000+ PRs&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Visible PR share&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;40%+&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Visible PR multiple vs average engineer&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;4.4x+&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;True working multiplier&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;5x&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Local proof loop&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;~39s&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Median first commit → green CI&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;10 min&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Proof signal&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;7,445 tests / 0 failures / 6/6 audit checks&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;PRs merged without post-merge hotfixes&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;95%&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;New engineer: useful work started&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;15 min&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;New engineer: first PRs&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;same day&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;11-month role-equivalent savings&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;$115.5k-$132k&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;11-month tooling savings&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;$41.8k+&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;11-month combined estimated savings&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;$157.3k-$173.8k&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The visible number is &lt;strong&gt;4.4x+&lt;/strong&gt;. That is the conservative, countable PR multiple: &lt;strong&gt;400+ PRs&lt;/strong&gt; from me alone versus an average of &lt;strong&gt;90.9 PRs&lt;/strong&gt; per engineer in an &lt;strong&gt;11-person&lt;/strong&gt; comparison group that shipped &lt;strong&gt;1,000+ PRs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The real operating number is &lt;strong&gt;5x&lt;/strong&gt;. That is the number I use once I include what PR counts do not show well: &lt;strong&gt;CD and pipeline work, DevOps support, API ownership, security, migrations, seeds, review load, management tasks, QA automation, verification, documentation, onboarding compression, and release confidence&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This post is the production result of the ideas I described earlier in:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://lukin.io/blog/building-browser-mmorpg-with-rails-and-ai&quot;&gt;Building a Browser-Based MMORPG with Ruby on Rails&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://lukin.io/blog/zero-gap-api-development-with-ai&quot;&gt;Zero-Gap API Development&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://lukin.io/blog/from-zero-gap-to-zero-drift&quot;&gt;From Zero-Gap to Zero-Drift&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those posts explained the pieces. This one is about what happened when I ran those ideas every day, against real work, for months.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;1-the-honest-math-behind-44x-and-5x&quot;&gt;1. The honest math behind &lt;strong&gt;4.4x+&lt;/strong&gt; and &lt;strong&gt;5x&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;Here is the clean visible comparison:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Metric&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Value&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Wider comparison group&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;11 engineers&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Team mix&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;5 frontend, 3 AI, 2 mobile, 1 DevOps&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Wider visible PR output&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1,000+ PRs&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;My solo backend output&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;400+ PRs&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;My share of that visible volume&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;40%+&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Average PRs per engineer in that group&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;90.9&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;My PR volume vs that average&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;4.4x+&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;That &lt;strong&gt;4.4x+&lt;/strong&gt; is the conservative, visible number.&lt;/p&gt;

&lt;p&gt;It is conservative for two reasons:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;the source numbers are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;400+&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1,000+&lt;/code&gt;, so the real totals are higher than the rounded comparison&lt;/li&gt;
  &lt;li&gt;PR count does not include a lot of the work I was carrying daily&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Using the documented &lt;strong&gt;11-month floor&lt;/strong&gt; as a reference point, that solo output works out to roughly:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Cadence view&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Value&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Reference PR cadence&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;~36.4 PRs/month&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Reference PR cadence&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;~8.4 PRs/week&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;That is already a strong result. But it is still not the whole result.&lt;/p&gt;

&lt;p&gt;The reason I call the broader system a &lt;strong&gt;5x force multiplier&lt;/strong&gt; is that my daily work was bigger than the visible PR math:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Bitbucket and GitHub pipelines for CD&lt;/li&gt;
  &lt;li&gt;DevOps-side support&lt;/li&gt;
  &lt;li&gt;API design and implementation&lt;/li&gt;
  &lt;li&gt;security and authorization&lt;/li&gt;
  &lt;li&gt;migrations and seed maintenance&lt;/li&gt;
  &lt;li&gt;testing and verification&lt;/li&gt;
  &lt;li&gt;feature architecture&lt;/li&gt;
  &lt;li&gt;review work&lt;/li&gt;
  &lt;li&gt;management and coordination tasks&lt;/li&gt;
  &lt;li&gt;onboarding acceleration&lt;/li&gt;
  &lt;li&gt;documentation and contract alignment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the useful question is not:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;How did one engineer write this many PRs?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;How did one engineer remove this much uncertainty from shipping?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is where AI became multiplicative for me. Not when it wrote code faster, but when it operated inside a system that already knew what “correct” meant.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;2-the-work-hidden-under-the-pr-count&quot;&gt;2. The work hidden under the PR count&lt;/h2&gt;

&lt;p&gt;Backend impact is easy to undervalue because the visible product surface is usually UI. The invisible surfaces are where concentration becomes easy to miss.&lt;/p&gt;

&lt;p&gt;The repo scan changes that.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Surface&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Value&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Total tracked lines&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;612,468&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Total tracked files&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2,878&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Named routes&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;656&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/api/v1&lt;/code&gt; routes&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;559&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Controllers&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;162&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Blueprints&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;203&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Models&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;107&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Services&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;97&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Policies&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;28&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Migrations&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;100&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Seed files&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;48&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Requirement docs&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;108&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Flow docs&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;110&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;PRDs&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;86&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;That means I was not just moving tickets. I was operating a backend with:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;1,373 implementation surfaces&lt;/strong&gt; before counting policies or docs&lt;br /&gt;
(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;routes + controllers + blueprints + models + services + migrations + seeds&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;1,705 total structured surfaces&lt;/strong&gt; once policies and requirement / Flow / PRD docs are included&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There were also focused platform modules under the same umbrella:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Module&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Files&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Lines&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SERVICE_1&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;91&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;21,927&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SERVICE_2&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;88&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;106,788&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SERVICE_3&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;96&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;62,227&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;And there was a non-trivial runtime protection layer too:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Protection surface&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Count&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Pundit policies&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;28&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Rack::Attack throttle rules&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;14&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Rack::Attack blocklists&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Total runtime protection surfaces&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;44&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;This is why I do not describe the result as “AI helped me code faster.”&lt;/p&gt;

&lt;p&gt;The real story is that I carried a large backend platform &lt;strong&gt;and&lt;/strong&gt; built the operating system that made that platform safer to change.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;3-the-leverage-came-from-proof-not-just-generation&quot;&gt;3. The leverage came from proof, not just generation&lt;/h2&gt;

&lt;p&gt;One metric in the repo explains almost everything about why this system worked.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Surface&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Lines&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Ratio vs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/**&lt;/code&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/**&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;56,215&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1.00x&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/**&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;125,772&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2.24x&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;doc/**&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;117,953&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2.10x&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/** + doc/**&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;243,725&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;4.34x&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;That means the proof surface and transfer surface around the application are &lt;strong&gt;4.34x larger than the app code itself&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is the part many AI productivity discussions miss.&lt;/p&gt;

&lt;p&gt;The biggest gain did not come from generating code faster.&lt;br /&gt;
It came from reducing:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;ambiguity&lt;/li&gt;
  &lt;li&gt;rework&lt;/li&gt;
  &lt;li&gt;onboarding drag&lt;/li&gt;
  &lt;li&gt;review noise&lt;/li&gt;
  &lt;li&gt;contract drift&lt;/li&gt;
  &lt;li&gt;post-merge risk&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is also why the onboarding story is so strong: the surrounding system explains the codebase before a new engineer has to guess its rules.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;4-i-stopped-using-ai-as-autocomplete&quot;&gt;4. I stopped using AI as autocomplete&lt;/h2&gt;

&lt;p&gt;A lot of AI productivity conversations are shallow because they treat the model as the product.&lt;/p&gt;

&lt;p&gt;That is backwards.&lt;/p&gt;

&lt;p&gt;AI is not the product.&lt;br /&gt;
&lt;strong&gt;The operating model is the product.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I like to describe my stack like this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt; is the senior software engineer I can run on every task&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt; is the QA automation engineer I can run in one command&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/contract_audit&lt;/code&gt; is the drift detector that goes beyond compiler-green&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That framing matters because I did not just ask AI to write code.&lt;/p&gt;

&lt;p&gt;I asked AI to work inside:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;a contract system&lt;/li&gt;
  &lt;li&gt;a planning workflow&lt;/li&gt;
  &lt;li&gt;a verification gate&lt;/li&gt;
  &lt;li&gt;a drift-prevention layer&lt;/li&gt;
  &lt;li&gt;a documentation discipline&lt;/li&gt;
  &lt;li&gt;a release model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a different level of leverage.&lt;/p&gt;

&lt;p&gt;A principal engineer with AI but no operating system gets faster drafts.&lt;/p&gt;

&lt;p&gt;A principal engineer with AI &lt;strong&gt;and&lt;/strong&gt; an operating system gets safer throughput, shorter ramp time, lower variance, and reusable decision quality.&lt;/p&gt;

&lt;p&gt;That is what happened here.&lt;/p&gt;

&lt;p&gt;It is also why I think the “AI replaced the engineer” framing is wrong.&lt;/p&gt;

&lt;p&gt;The real upgrade is this:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;16 years of experience turned into a system that can be run daily.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;5-agentsmd-turning-principal-judgment-into-a-reusable-senior-engineer&quot;&gt;5. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt;: turning principal judgment into a reusable senior engineer&lt;/h2&gt;

&lt;p&gt;The current &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt; is not a prompt blob. It is a &lt;strong&gt;delivery runtime&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One of the most telling details is that the tools kept evolving instead of freezing into documents nobody touches:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Tool&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;March snapshot&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Current attached source&lt;/th&gt;
      &lt;th&gt;Signal&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;987 lines&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1,143 lines&lt;/td&gt;
      &lt;td&gt;the operating contract kept absorbing real-world lessons&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;370 lines&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;370 lines&lt;/td&gt;
      &lt;td&gt;the daily verification core stabilized early&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/contract_audit&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;452 lines&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;540 lines&lt;/td&gt;
      &lt;td&gt;drift prevention kept getting sharper&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;That is the opposite of a random prompt file.&lt;br /&gt;
It is a maintained operating system.&lt;/p&gt;

&lt;p&gt;What makes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt; powerful is not length. It is what the file externalizes.&lt;/p&gt;

&lt;h3 id=&quot;it-defines-authority-before-coding-starts&quot;&gt;It defines authority before coding starts&lt;/h3&gt;

&lt;p&gt;It makes the source of truth explicit:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;process and workflow live in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;feature behavior lives in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;doc/requirements/**&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Flow and PRD docs are derived artifacts&lt;/li&gt;
  &lt;li&gt;requirements stay read-only during implementation&lt;/li&gt;
  &lt;li&gt;docs update only after verification is green&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That removes a huge amount of ambiguity for both humans and AI.&lt;/p&gt;

&lt;h3 id=&quot;it-forces-planning-before-implementation&quot;&gt;It forces planning before implementation&lt;/h3&gt;

&lt;p&gt;It creates hard no-code phases:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;extract the contract&lt;/li&gt;
  &lt;li&gt;scan the repo&lt;/li&gt;
  &lt;li&gt;build a file-by-file plan&lt;/li&gt;
  &lt;li&gt;map requirements to specs&lt;/li&gt;
  &lt;li&gt;stop for confirmation&lt;/li&gt;
  &lt;li&gt;only then implement&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is senior-engineer behavior made explicit.&lt;/p&gt;

&lt;h3 id=&quot;it-turns-done-into-something-executable&quot;&gt;It turns “done” into something executable&lt;/h3&gt;

&lt;p&gt;It does not stop at “follow best practices.” It defines:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;canonical success and error envelopes&lt;/li&gt;
  &lt;li&gt;representational invariants&lt;/li&gt;
  &lt;li&gt;safe defaults for required fields&lt;/li&gt;
  &lt;li&gt;verification order&lt;/li&gt;
  &lt;li&gt;compliance audit rules&lt;/li&gt;
  &lt;li&gt;documentation timing&lt;/li&gt;
  &lt;li&gt;final-output structure&lt;/li&gt;
  &lt;li&gt;a never-list of things that must not ship&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why I say I wrote my own senior software engineer.&lt;/p&gt;

&lt;p&gt;Not because it sounds cool.&lt;br /&gt;
Because it converts tacit judgment into repeatable constraints.&lt;/p&gt;

&lt;h3 id=&quot;it-also-solves-prompt-drift&quot;&gt;It also solves prompt drift&lt;/h3&gt;

&lt;p&gt;A subtle but important design choice is the split between:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;[NORMATIVE]&lt;/strong&gt; sections: actual rules&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;[ILLUSTRATIVE]&lt;/strong&gt; sections: examples and patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That stops examples from silently becoming law, which is one of the most common ways AI drifts while pretending it is compliant.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;6-binverify-the-qa-automation-engineer-i-wanted-next-to-me&quot;&gt;6. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt;: the QA automation engineer I wanted next to me&lt;/h2&gt;

&lt;p&gt;A lot of teams say they care about quality, but their verification path is too slow, too manual, or too annoying to run constantly.&lt;/p&gt;

&lt;p&gt;That means the real workflow becomes:
“verify when nervous.”&lt;/p&gt;

&lt;p&gt;That is not a system.&lt;br /&gt;
That is mood-based engineering.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt; is the opposite.&lt;/p&gt;

&lt;p&gt;At a glance, its structure looks simple:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;CHECK_PROFILES&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;fast: &lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;%i[rubocop rspec swagger]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;full: &lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;%i[rubocop rspec brakeman audit swagger seeds]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But the important part is not that there are two characters.&lt;/p&gt;

&lt;p&gt;It is that the script is &lt;strong&gt;diff-aware&lt;/strong&gt;, &lt;strong&gt;parallelized&lt;/strong&gt;, and &lt;strong&gt;cheap enough to run continuously&lt;/strong&gt;.&lt;/p&gt;

&lt;h3 id=&quot;what-the-fast-character-really-does&quot;&gt;What the fast character really does&lt;/h3&gt;

&lt;p&gt;The default path is intentionally practical:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;RuboCop&lt;/li&gt;
  &lt;li&gt;RSpec&lt;/li&gt;
  &lt;li&gt;Swagger generation only when API-relevant surfaces changed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point matters a lot.&lt;/p&gt;

&lt;p&gt;The script checks changed files and auto-skips swagger if controllers, blueprints, routes, or API specs were untouched. Routine work stays fast.&lt;/p&gt;

&lt;h3 id=&quot;what-the-full-character-adds&quot;&gt;What the full character adds&lt;/h3&gt;

&lt;p&gt;For heavier changes, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--full&lt;/code&gt; adds:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Brakeman&lt;/li&gt;
  &lt;li&gt;bundle audit&lt;/li&gt;
  &lt;li&gt;db seed replant&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This split is one of the reasons the system works in real life.&lt;/p&gt;

&lt;p&gt;A daily loop must be cheap enough to become muscle memory.&lt;br /&gt;
A full compliance loop must exist when the change deserves it.&lt;/p&gt;

&lt;h3 id=&quot;it-is-optimized-like-build-infrastructure-not-like-a-shell-alias&quot;&gt;It is optimized like build infrastructure, not like a shell alias&lt;/h3&gt;

&lt;p&gt;There are several details in the actual source that matter:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;8-worker parallel RSpec by default&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;fast mode excludes specs tagged &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:full_only&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;schema fingerprinting avoids unnecessary &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parallel:prepare&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;lock files and stamp files prevent redundant work&lt;/li&gt;
  &lt;li&gt;ergonomics like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--only&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--fail-fast&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--workers&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--sequential&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--list&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is not “nice tooling.”&lt;br /&gt;
That is what makes the tool runnable every day.&lt;/p&gt;

&lt;h3 id=&quot;why-that-mattered-to-output&quot;&gt;Why that mattered to output&lt;/h3&gt;

&lt;p&gt;The proof loop now has two speeds that matter to me:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Signal&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Value&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Local proof loop (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt;)&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;~39s&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Median first commit → green CI&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;10 min&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Test examples&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;7,445&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Failures&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Contract audit&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;6/6 checks passed&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;PRs merged without post-merge hotfixes&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;95%&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;That is the kind of feedback character that preserves flow.&lt;/p&gt;

&lt;p&gt;It is fast enough to run locally.&lt;br /&gt;
It is fast enough to keep CI useful.&lt;br /&gt;
And it is reliable enough that most merged PRs do not need cleanup after the fact.&lt;/p&gt;

&lt;p&gt;That is why I say &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt; is the QA automation engineer I built for myself.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;7-bincontract_audit-going-beyond-compiler-green&quot;&gt;7. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/contract_audit&lt;/code&gt;: going beyond compiler-green&lt;/h2&gt;

&lt;p&gt;This tool reflects how I think about engineering more than almost anything else in the stack.&lt;/p&gt;

&lt;p&gt;Compilers tell you whether syntax is valid.&lt;br /&gt;
Tests tell you whether known behavior still passes.&lt;br /&gt;
Neither one guarantees that the implementation still matches:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the requirement&lt;/li&gt;
  &lt;li&gt;the docs&lt;/li&gt;
  &lt;li&gt;the route law&lt;/li&gt;
  &lt;li&gt;the serializer discipline&lt;/li&gt;
  &lt;li&gt;the portability constraints&lt;/li&gt;
  &lt;li&gt;the team’s rules for how APIs are allowed to evolve&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why I built &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/contract_audit&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Its top-level checks are explicit:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;CHECKS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;requirements_read_only: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Requirements docs remain untouched (doc/requirements/**)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;swagger_generated_source: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Swagger YAML not edited alone (must be driven by rswag specs)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;db_specific_sql: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;No DB-specific SQL tokens introduced&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;route_style: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Route literals avoid kebab-case and trailing slashes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;controller_manual_json: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;No new ad-hoc render json hash payloads in controllers&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;docs_templates: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Changed Flow/PRD docs follow required section templates&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That list says a lot about the philosophy behind it.&lt;/p&gt;

&lt;p&gt;This is not a generic lint script.&lt;br /&gt;
It is a &lt;strong&gt;drift-prevention script&lt;/strong&gt;.&lt;/p&gt;

&lt;h3 id=&quot;the-most-important-design-choice-it-is-legacy-safe&quot;&gt;The most important design choice: it is legacy-safe&lt;/h3&gt;

&lt;p&gt;By default, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/contract_audit&lt;/code&gt; does &lt;strong&gt;not&lt;/strong&gt; try to police the whole repository every time.&lt;/p&gt;

&lt;p&gt;Its default mode audits:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;changed files&lt;/li&gt;
  &lt;li&gt;added lines&lt;/li&gt;
  &lt;li&gt;untracked files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And only &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--all&lt;/code&gt; audits the full repository.&lt;/p&gt;

&lt;p&gt;That is a very practical design.&lt;/p&gt;

&lt;p&gt;It means you can introduce strictness into a real codebase without freezing delivery until every historical inconsistency is cleaned up.&lt;/p&gt;

&lt;p&gt;That is one of the biggest lessons in this system:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The best guardrail is not the strictest one.&lt;br /&gt;
It is the strict one people can actually adopt.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;it-audits-the-mistakes-that-usually-slip-through&quot;&gt;It audits the mistakes that usually slip through&lt;/h3&gt;

&lt;p&gt;The script blocks the kinds of drift that create long-term chaos:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;requirements doc edits during normal implementation&lt;/li&gt;
  &lt;li&gt;hand-edited swagger without spec changes&lt;/li&gt;
  &lt;li&gt;DB-specific SQL shortcuts&lt;/li&gt;
  &lt;li&gt;kebab-case or trailing-slash route drift&lt;/li&gt;
  &lt;li&gt;ad-hoc controller JSON outside the envelope helpers&lt;/li&gt;
  &lt;li&gt;malformed Flow and PRD doc structure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is what I mean by &lt;strong&gt;drift-free&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Not just “does it run?”&lt;br /&gt;
Also “does it still mean what we think it means?”&lt;/p&gt;

&lt;p&gt;If I had to describe the stack in compiler terms, it would be this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;doc/requirements/**&lt;/code&gt; is the source program for intent&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt; is the language law&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt; is the fast proof loop&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/contract_audit&lt;/code&gt; is the semantic checker that blocks drift&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;6/6&lt;/code&gt; audit pass matters so much more than a green line by itself.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;8-why-onboarding-collapsed-from-weeks-to-minutes&quot;&gt;8. Why onboarding collapsed from weeks to minutes&lt;/h2&gt;

&lt;p&gt;One of the strongest signals in this story had nothing to do with my own output.&lt;/p&gt;

&lt;p&gt;The first engineer who followed this system:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;started working on the &lt;strong&gt;two highest-priority tasks and bugs after 15 minutes&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;created the &lt;strong&gt;first PRs the same day&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That result matters to me more than a vanity metric because it proves the system works outside my own head.&lt;/p&gt;

&lt;p&gt;It also explains why frontend engineers started wanting to pick up tasks inside the same flow.&lt;/p&gt;

&lt;p&gt;Why did onboarding collapse like that?&lt;/p&gt;

&lt;p&gt;Because the repo answers the questions that usually stay trapped in tribal knowledge:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;what is the source of truth?&lt;/li&gt;
  &lt;li&gt;where does behavior live?&lt;/li&gt;
  &lt;li&gt;what must not drift?&lt;/li&gt;
  &lt;li&gt;what command proves the change is safe?&lt;/li&gt;
  &lt;li&gt;what docs must reflect the change?&lt;/li&gt;
  &lt;li&gt;what does “done” mean here?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is what makes onboarding fast.&lt;/p&gt;

&lt;p&gt;People often think onboarding is about reading architecture docs.&lt;/p&gt;

&lt;p&gt;It is not.&lt;/p&gt;

&lt;p&gt;Onboarding is about removing ambiguity from the first real task.&lt;/p&gt;

&lt;p&gt;This system does that because:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;requirements define behavior&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt; defines execution&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt; defines proof&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/contract_audit&lt;/code&gt; defines drift boundaries&lt;/li&gt;
  &lt;li&gt;Flow docs define implementation traceability&lt;/li&gt;
  &lt;li&gt;PRDs define product context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is not just faster ramp.&lt;br /&gt;
It is &lt;strong&gt;faster trust&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And trust is what lets new engineers ship instead of shadowing.&lt;/p&gt;

&lt;p&gt;For comparison, in a large company environment like Apple, getting to this level of useful contribution could take &lt;strong&gt;2-3 weeks&lt;/strong&gt;, and sometimes &lt;strong&gt;4 weeks with training&lt;/strong&gt;. Here, the first useful work started in &lt;strong&gt;15 minutes&lt;/strong&gt; and the first PRs landed the &lt;strong&gt;same day&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is not a small improvement.&lt;br /&gt;
That is a different operating model.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;9-why-this-is-language-agnostic&quot;&gt;9. Why this is language-agnostic&lt;/h2&gt;

&lt;p&gt;Yes, the current implementation is Rails-heavy.&lt;/p&gt;

&lt;p&gt;It uses:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Blueprinter&lt;/li&gt;
  &lt;li&gt;Pundit&lt;/li&gt;
  &lt;li&gt;Ransack&lt;/li&gt;
  &lt;li&gt;Kaminari&lt;/li&gt;
  &lt;li&gt;RSpec&lt;/li&gt;
  &lt;li&gt;RuboCop&lt;/li&gt;
  &lt;li&gt;Brakeman&lt;/li&gt;
  &lt;li&gt;rswag&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the underlying model is not Ruby-specific at all.&lt;/p&gt;

&lt;p&gt;The reusable pattern is this:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;requirements are explicit&lt;/li&gt;
  &lt;li&gt;implementation follows contract&lt;/li&gt;
  &lt;li&gt;verification is one command&lt;/li&gt;
  &lt;li&gt;drift is checked separately from tests&lt;/li&gt;
  &lt;li&gt;docs are derived from verified behavior&lt;/li&gt;
  &lt;li&gt;onboarding happens against the system, not folklore&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That ports cleanly.&lt;/p&gt;

&lt;h3 id=&quot;typescript--node&quot;&gt;TypeScript / Node&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;requirements: TypeScript interfaces, Zod, OpenAPI&lt;/li&gt;
  &lt;li&gt;verify: ESLint, Jest or Vitest, OpenAPI generation, security checks&lt;/li&gt;
  &lt;li&gt;audit: route naming, response-shape discipline, docs templates, SQL portability&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;python&quot;&gt;Python&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;requirements: Pydantic models, OpenAPI, JSON Schema&lt;/li&gt;
  &lt;li&gt;verify: Ruff, pytest, mypy, security checks&lt;/li&gt;
  &lt;li&gt;audit: serializer boundaries, route shape, docs structure, query portability&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;go&quot;&gt;Go&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;requirements: structs, OpenAPI, protobuf or JSON schema&lt;/li&gt;
  &lt;li&gt;verify: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go test&lt;/code&gt;, lint, vuln checks, generated spec validation&lt;/li&gt;
  &lt;li&gt;audit: handler response discipline, route law, portability, docs templates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The language-specific pieces are adapters.&lt;/p&gt;

&lt;p&gt;The real product is the operating model.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;10-the-business-math-is-stronger-than-the-pr-math&quot;&gt;10. The business math is stronger than the PR math&lt;/h2&gt;

&lt;p&gt;Raw PR throughput already makes the case:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;average engineer in the 11-person comparison group: &lt;strong&gt;90.9 PRs&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;my visible solo output: &lt;strong&gt;400+ PRs&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;visible ratio: &lt;strong&gt;4.4x+&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the business story gets stronger when you include the role-equivalent system I built.&lt;/p&gt;

&lt;h3 id=&quot;i-effectively-replaced-two-roles-in-my-daily-loop&quot;&gt;I effectively replaced two roles in my daily loop&lt;/h3&gt;

&lt;p&gt;The way I use the system, it acts like I wrote:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;a &lt;strong&gt;senior backend engineer assistant&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;a &lt;strong&gt;QA automation engineer assistant&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using the salary assumptions I gave for this comparison:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Role-equivalent value&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Estimate&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Senior backend engineer&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;$6.5k-$8k / month&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;QA automation engineer&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;$4k / month&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Combined role-equivalent cost&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;$10.5k-$12k / month&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;That means the system replaced &lt;strong&gt;$10.5k-$12k per month&lt;/strong&gt; of role-equivalent work before counting tooling.&lt;/p&gt;

&lt;h3 id=&quot;corrected-savings-math&quot;&gt;Corrected savings math&lt;/h3&gt;

&lt;p&gt;Using the documented &lt;strong&gt;11-month&lt;/strong&gt; period for comparison:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Savings view&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Estimate&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;11-month role-equivalent savings&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;$115.5k-$132k&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Tooling delta (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$4,000+ - $200&lt;/code&gt;) over 11 months&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;$41.8k+&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;11-month combined estimated savings&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;$157.3k-$173.8k&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Annualized, that becomes:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Annualized view&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Estimate&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Role-equivalent savings&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;$126k-$144k / year&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Tooling savings&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;$45.6k+ / year&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Combined annualized estimate&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;$171.6k-$189.6k / year&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;And even that is still incomplete, because it does &lt;strong&gt;not&lt;/strong&gt; fully price in:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;DevOps-side work&lt;/li&gt;
  &lt;li&gt;management and coordination time&lt;/li&gt;
  &lt;li&gt;PR review load&lt;/li&gt;
  &lt;li&gt;release-risk reduction&lt;/li&gt;
  &lt;li&gt;onboarding compression&lt;/li&gt;
  &lt;li&gt;lower rework&lt;/li&gt;
  &lt;li&gt;fewer hotfixes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;safe-speed-beats-raw-speed&quot;&gt;Safe speed beats raw speed&lt;/h3&gt;

&lt;p&gt;The most important thing I did &lt;strong&gt;not&lt;/strong&gt; want was fake velocity.&lt;/p&gt;

&lt;p&gt;Fast delivery only matters if it is safe enough to merge without fear.&lt;/p&gt;

&lt;p&gt;That is why these numbers matter together:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;~39s&lt;/strong&gt; local proof loop&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;10 min&lt;/strong&gt; median first commit to green CI&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;7,445&lt;/strong&gt; tests&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;0&lt;/strong&gt; failures&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;6/6&lt;/strong&gt; audit checks passed&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;95%&lt;/strong&gt; of PRs merged without post-merge hotfixes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is what businesses are actually buying when they say they want “10x engineering.”&lt;/p&gt;

&lt;p&gt;They do not want more code.&lt;br /&gt;
They want more &lt;strong&gt;safe change per unit of time&lt;/strong&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;11-what-i-think-this-actually-says-about-ai-and-senior-engineers&quot;&gt;11. What I think this actually says about AI and senior engineers&lt;/h2&gt;

&lt;p&gt;After re-reading all of the material, including the actual &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/contract_audit&lt;/code&gt; sources, I think there are five especially important ideas hiding inside this result.&lt;/p&gt;

&lt;h3 id=&quot;1-the-best-systems-are-strict-and-ergonomic-at-the-same-time&quot;&gt;1) The best systems are strict and ergonomic at the same time&lt;/h3&gt;

&lt;p&gt;Fast character vs full character.&lt;br /&gt;
Changed-lines mode vs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--all&lt;/code&gt;.&lt;br /&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--only&lt;/code&gt;.&lt;br /&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--fail-fast&lt;/code&gt;.&lt;br /&gt;
Schema-aware prepare.&lt;br /&gt;
Swagger auto-skip.&lt;/p&gt;

&lt;p&gt;That is not polish.&lt;br /&gt;
That is adoption strategy.&lt;/p&gt;

&lt;h3 id=&quot;2-legacy-safe-is-a-force-multiplier&quot;&gt;2) “Legacy-safe” is a force multiplier&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/contract_audit&lt;/code&gt; does not require a perfect repo before it becomes useful.&lt;br /&gt;
It prevents &lt;strong&gt;new drift first&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is how serious systems actually get adopted.&lt;/p&gt;

&lt;h3 id=&quot;3-proof-is-part-of-the-product&quot;&gt;3) Proof is part of the product&lt;/h3&gt;

&lt;p&gt;When &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/**&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;doc/**&lt;/code&gt; together dwarf &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/**&lt;/code&gt;, that is not overhead.&lt;/p&gt;

&lt;p&gt;That is the mechanism that lets a platform change quickly without dissolving into review theater.&lt;/p&gt;

&lt;h3 id=&quot;4-principal-engineering-in-the-ai-era-is-about-externalized-judgment&quot;&gt;4) Principal engineering in the AI era is about externalized judgment&lt;/h3&gt;

&lt;p&gt;The old model was:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;senior engineers make better decisions in their head&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The new model is:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;principal engineers turn those decisions into reusable infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the real upgrade.&lt;/p&gt;

&lt;h3 id=&quot;5-ai-gets-more-valuable-as-the-rules-get-sharper&quot;&gt;5) AI gets more valuable as the rules get sharper&lt;/h3&gt;

&lt;p&gt;Most AI disappointment is really systems disappointment.&lt;/p&gt;

&lt;p&gt;If the repo does not define truth, proof, drift boundaries, and handoff rules, then the model amplifies ambiguity.&lt;/p&gt;

&lt;p&gt;If the repo does define those things, the model amplifies throughput.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;12-closing&quot;&gt;12. Closing&lt;/h2&gt;

&lt;p&gt;I do not think the most important AI question is:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Can the model write code?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I think the real question is:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Can the engineering system make correctness cheap enough that the model can safely accelerate it?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the difference between novelty and leverage.&lt;/p&gt;

&lt;p&gt;In my case, one principal backend engineer shipped &lt;strong&gt;400+ PRs&lt;/strong&gt;, carried &lt;strong&gt;40%+&lt;/strong&gt; of the visible PR volume of an &lt;strong&gt;11-engineer&lt;/strong&gt; comparison group, delivered a conservative visible multiple of &lt;strong&gt;4.4x+&lt;/strong&gt;, operated at a true working multiple of &lt;strong&gt;5x&lt;/strong&gt;, validated daily work in about &lt;strong&gt;39 seconds&lt;/strong&gt;, reached green CI in a median of &lt;strong&gt;10 minutes&lt;/strong&gt;, passed &lt;strong&gt;7,445 tests with 0 failures&lt;/strong&gt;, passed &lt;strong&gt;6/6 contract-audit checks&lt;/strong&gt;, got a new engineer onto the two highest-priority tasks in &lt;strong&gt;15 minutes&lt;/strong&gt;, saw first PRs the &lt;strong&gt;same day&lt;/strong&gt;, and delivered &lt;strong&gt;95%&lt;/strong&gt; of PRs without post-merge hotfixes.&lt;/p&gt;

&lt;p&gt;I did not get there by asking AI to be smarter.&lt;/p&gt;

&lt;p&gt;I got there by building a system where:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;requirements are explicit&lt;/li&gt;
  &lt;li&gt;execution is governed&lt;/li&gt;
  &lt;li&gt;verification is fast&lt;/li&gt;
  &lt;li&gt;drift is blocked&lt;/li&gt;
  &lt;li&gt;docs are derived&lt;/li&gt;
  &lt;li&gt;onboarding is compressed&lt;/li&gt;
  &lt;li&gt;and the only truly acceptable final response becomes:&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;all good - merge PR&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is what 16 years of experience bought me in the AI era.&lt;/p&gt;

&lt;p&gt;Not better autocomplete.&lt;br /&gt;
A better engineering operating system.&lt;/p&gt;
</description>
        <pubDate>Tue, 07 Apr 2026 00:00:00 +0000</pubDate>
        <link>https://lukin.io/blog/1-backend-engineer-vs-11-engineers-with-ai/</link>
        <guid isPermaLink="true">https://lukin.io/blog/1-backend-engineer-vs-11-engineers-with-ai/</guid>
        
        <category>ai</category>
        
        <category>software-engineering</category>
        
        <category>principal-engineer</category>
        
        <category>rails</category>
        
        <category>verification</category>
        
        <category>contracts</category>
        
        <category>productivity</category>
        
        
        <category>engineering</category>
        
        <category>rails</category>
        
        <category>AI</category>
        
        <category>leadership</category>
        
      </item>
    
      <item>
        <title>From Monolithic Seeds to Enterprise-Ready Data Generation: Building a Scalable Rails Seed System</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;“Good seed data is the difference between a demo that impresses and a demo that embarrasses.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every growing Rails application faces the same challenge: how do you populate your database with realistic data for development, demos, testing, and stakeholder presentations? Simple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;db/seeds.rb&lt;/code&gt; files work for small projects, but as your application scales, so do your seeding requirements.&lt;/p&gt;

&lt;p&gt;This post documents how we transformed a 3,600-line monolithic seed file into an enterprise-ready data generation system that creates 100,000+ full candidate avatars with all associations—serving AI training, QA stress testing, and frontend performance validation.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;#1-the-problem-why-seed-data-matters&quot;&gt;The Problem: Why Seed Data Matters&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#2-before-the-monolithic-nightmare&quot;&gt;Before: The Monolithic Nightmare&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#3-the-refactoring-journey&quot;&gt;The Refactoring Journey&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#4-feature-1-modular-step-architecture&quot;&gt;Feature 1: Modular Step Architecture&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#5-feature-2-context-object-for-state-management&quot;&gt;Feature 2: Context Object for State Management&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#6-feature-3-conditional-execution-via-env-flags&quot;&gt;Feature 3: Conditional Execution via ENV Flags&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#7-feature-4-progress-reporting-with-timing&quot;&gt;Feature 4: Progress Reporting with Timing&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#8-feature-5-post-seed-data-validation&quot;&gt;Feature 5: Post-Seed Data Validation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#9-feature-6-100k-full-avatar-scaling&quot;&gt;Feature 6: 100k+ Full Avatar Scaling&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#10-benefits-and-use-cases&quot;&gt;Benefits and Use Cases&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#11-testing-the-seed-system&quot;&gt;Testing the Seed System&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#12-three-bonus-ideas-worth-implementing&quot;&gt;Three Bonus Ideas Worth Implementing&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#13-conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;1-the-problem-why-seed-data-matters&quot;&gt;1. The Problem: Why Seed Data Matters&lt;/h2&gt;

&lt;h3 id=&quot;11-who-uses-seed-data&quot;&gt;1.1 Who Uses Seed Data?&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Stakeholder&lt;/th&gt;
      &lt;th&gt;What They Need&lt;/th&gt;
      &lt;th&gt;Why It Matters&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Frontend Developers&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Realistic UI data&lt;/td&gt;
      &lt;td&gt;Test pagination, search, empty states&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Backend Developers&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Consistent test fixtures&lt;/td&gt;
      &lt;td&gt;Debug API responses locally&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;QA Team&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Volume for stress testing&lt;/td&gt;
      &lt;td&gt;Find N+1 queries, memory leaks&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;AI/ML Team&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Training data at scale&lt;/td&gt;
      &lt;td&gt;Validate models with realistic distributions&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Product Managers&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Demo-ready environments&lt;/td&gt;
      &lt;td&gt;Impress stakeholders, test features&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;CEO/Leadership&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Production-like demos&lt;/td&gt;
      &lt;td&gt;Investor presentations, board meetings&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;New Engineers&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Quick environment setup&lt;/td&gt;
      &lt;td&gt;Onboard in minutes, not hours&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;12-the-reality-check&quot;&gt;1.2 The Reality Check&lt;/h3&gt;

&lt;p&gt;Most Rails projects start with something like this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# db/seeds.rb&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;email: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;admin@example.com&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;password: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;password&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;Test Company&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# ... done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then requirements grow:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;“We need 50 players with different roles”&lt;/li&gt;
  &lt;li&gt;“Avatars need abilities, work experiences, education”&lt;/li&gt;
  &lt;li&gt;“QA needs 100,000 avatars for load testing”&lt;/li&gt;
  &lt;li&gt;“AI team needs realistic job title distributions”&lt;/li&gt;
  &lt;li&gt;“CEO wants the demo to look like a real platform”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before you know it, your seed file is 3,600 lines of spaghetti.&lt;/p&gt;

&lt;h3 id=&quot;13-our-starting-point&quot;&gt;1.3 Our Starting Point&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Metric&lt;/th&gt;
      &lt;th&gt;Value&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Seed file size&lt;/td&gt;
      &lt;td&gt;3,610 lines&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Total methods&lt;/td&gt;
      &lt;td&gt;59&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Instance variables&lt;/td&gt;
      &lt;td&gt;8 (shared state)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Dependencies&lt;/td&gt;
      &lt;td&gt;Complex, undocumented&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Test coverage&lt;/td&gt;
      &lt;td&gt;0%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Documentation&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;Pain points:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;❌ Changes broke unrelated features&lt;/li&gt;
  &lt;li&gt;❌ No way to run individual steps&lt;/li&gt;
  &lt;li&gt;❌ No progress indication&lt;/li&gt;
  &lt;li&gt;❌ No validation of seeded data&lt;/li&gt;
  &lt;li&gt;❌ Impossible to scale to 100k records&lt;/li&gt;
  &lt;li&gt;❌ New engineers couldn’t understand the flow&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;2-before-the-monolithic-nightmare&quot;&gt;2. Before: The Monolithic Nightmare&lt;/h2&gt;

&lt;h3 id=&quot;21-the-original-structure&quot;&gt;2.1 The Original Structure&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# db/seeds/steps.rb - 3,610 lines!&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Seeds&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Steps&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;seed_predefined_avatar_assets&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;seed_company&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;seed_job_titles&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;seed_admin_users&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# ... 32 more method calls&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;print_summary&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;seed_company&lt;/span&gt;
        &lt;span class=&quot;vi&quot;&gt;@company&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_or_initialize_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;Bluegeko&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;vi&quot;&gt;@company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;assign_attributes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# ... 50 lines of attributes&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;vi&quot;&gt;@company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;save!&lt;/span&gt;
        
        &lt;span class=&quot;c1&quot;&gt;# ... 200 more lines for demo companies&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;seed_admin_users&lt;/span&gt;
        &lt;span class=&quot;vi&quot;&gt;@admin_user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_or_initialize_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;email: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;admin@bluegeko.com&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# ... 300 lines&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# ... 56 more methods&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;22-the-problems&quot;&gt;2.2 The Problems&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Problem&lt;/th&gt;
      &lt;th&gt;Impact&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Single 3,600-line file&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;IDE struggles, git conflicts&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Implicit dependencies&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Can’t run steps individually&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Instance variable soup&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@company&lt;/code&gt; used 28 times across methods&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;No progress feedback&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;“Is it stuck or working?”&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;No validation&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Broken seeds discovered in production demos&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;No scalability&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Creating 100k records = 100k individual inserts&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Zero documentation&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;New engineers lost for days&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;23-instance-variable-reference-count&quot;&gt;2.3 Instance Variable Reference Count&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Variable&lt;/th&gt;
      &lt;th&gt;References&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@company&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;28&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@admin_user&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;18&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@candidate_demo_user&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;15&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@access_request_user&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;14&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@standard_user&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;8&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@full_data_candidate_users&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;5&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@demo_job_posts&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;5&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@admin_avatar&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;These shared variables made refactoring terrifying—change one method, break five others.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;3-the-refactoring-journey&quot;&gt;3. The Refactoring Journey&lt;/h2&gt;

&lt;h3 id=&quot;31-implementation-priority-matrix&quot;&gt;3.1 Implementation Priority Matrix&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;#&lt;/th&gt;
      &lt;th&gt;Feature&lt;/th&gt;
      &lt;th&gt;Effort&lt;/th&gt;
      &lt;th&gt;Priority&lt;/th&gt;
      &lt;th&gt;Status&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;Modular step architecture&lt;/td&gt;
      &lt;td&gt;High&lt;/td&gt;
      &lt;td&gt;Critical&lt;/td&gt;
      &lt;td&gt;✅ Done&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt;Conditional ENV flags&lt;/td&gt;
      &lt;td&gt;Low&lt;/td&gt;
      &lt;td&gt;High&lt;/td&gt;
      &lt;td&gt;✅ Done&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt;Context object&lt;/td&gt;
      &lt;td&gt;Medium&lt;/td&gt;
      &lt;td&gt;High&lt;/td&gt;
      &lt;td&gt;✅ Done&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;5&lt;/td&gt;
      &lt;td&gt;Progress + timing&lt;/td&gt;
      &lt;td&gt;Medium&lt;/td&gt;
      &lt;td&gt;High&lt;/td&gt;
      &lt;td&gt;✅ Done&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;6&lt;/td&gt;
      &lt;td&gt;Data validation&lt;/td&gt;
      &lt;td&gt;Low&lt;/td&gt;
      &lt;td&gt;High&lt;/td&gt;
      &lt;td&gt;✅ Done&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;7&lt;/td&gt;
      &lt;td&gt;100k batch processing&lt;/td&gt;
      &lt;td&gt;High&lt;/td&gt;
      &lt;td&gt;Very High&lt;/td&gt;
      &lt;td&gt;✅ Done&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;Parallel execution&lt;/td&gt;
      &lt;td&gt;Medium-High&lt;/td&gt;
      &lt;td&gt;Medium&lt;/td&gt;
      &lt;td&gt;⏳ Future&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;32-final-architecture&quot;&gt;3.2 Final Architecture&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;db/seeds/
├── seeds.rb                    # Entry point (29 lines)
├── support.rb                  # Helpers (387 lines)
├── reference_data.rb           # Lookup tables (889 lines)
├── steps.rb                    # Orchestrator (217 lines)
├── context.rb                  # State management (233 lines)
├── timing.rb                   # Progress tracking (186 lines)
├── validation.rb               # Data integrity (331 lines)
├── batch_avatars.rb           # 100k scaling (825 lines)
├── README.md                   # Documentation (1,797 lines)
├── SEEDS_REFACTORING.md        # Implementation notes (643 lines)
└── steps/                      # 37 individual step files
    ├── 01_predefined_avatar_assets.rb
    ├── 02_company.rb
    ├── ...
    └── 37_batch_avatars.rb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Result: 3,610 lines → 37 focused files averaging 80 lines each&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;4-feature-1-modular-step-architecture&quot;&gt;4. Feature 1: Modular Step Architecture&lt;/h2&gt;

&lt;h3 id=&quot;41-the-approach-singleton-class-reopening&quot;&gt;4.1 The Approach: Singleton Class Reopening&lt;/h3&gt;

&lt;p&gt;Instead of a massive refactor, we used Ruby’s ability to reopen classes:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# db/seeds/steps/02_company.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Seeds&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Steps&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;
      &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;seed_company&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;company&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_or_initialize_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Bluegeko&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;assign_attributes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;company_attributes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;save!&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;log_seed&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Company upserted: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;seed_demo_companies&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;company_attributes&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;industry: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Technology&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;website_url: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://bluegeko.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# ... attributes&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;42-orchestrator-pattern&quot;&gt;4.2 Orchestrator Pattern&lt;/h3&gt;

&lt;p&gt;The main &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;steps.rb&lt;/code&gt; became a thin orchestrator:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# db/seeds/steps.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Seeds&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Steps&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;OPTIONAL_STEPS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;seed_reports: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;SKIP_SEED_REPORTS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;seed_full_data_candidates: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;SEED_FULL_CANDIDATES&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;seed_batch_avatars: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;CREATE_BATCH_PROFILES&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;initialize_context&lt;/span&gt;
        &lt;span class=&quot;no&quot;&gt;Seeds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Support&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Timing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start_run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;all_steps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;# Execute steps in order&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;run_step&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:seed_predefined_avatar_assets&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;run_step&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:seed_company&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;run_step&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:seed_job_titles&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# ... 34 more steps&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;run_step&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:seed_batch_avatars&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;# Validate and summarize&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;validate_seed_data&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;print_summary&lt;/span&gt;
        &lt;span class=&quot;no&quot;&gt;Seeds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Support&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Timing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;print_summary&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run_step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;skip_step?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;no&quot;&gt;Seeds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Support&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Timing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;track_step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
          &lt;span class=&quot;nb&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;skip_step?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;env_var&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;OPTIONAL_STEPS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;env_var&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;env_var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start_with?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;SKIP_&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;env_var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;true&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
          &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;env_var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;true&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;43-benefits-of-modular-architecture&quot;&gt;4.3 Benefits of Modular Architecture&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Aspect&lt;/th&gt;
      &lt;th&gt;Before&lt;/th&gt;
      &lt;th&gt;After&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;File size&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;3,610 lines&lt;/td&gt;
      &lt;td&gt;~80 lines avg&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Git conflicts&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Constant&lt;/td&gt;
      &lt;td&gt;Rare&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;IDE navigation&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Painful&lt;/td&gt;
      &lt;td&gt;Instant&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Step isolation&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Impossible&lt;/td&gt;
      &lt;td&gt;Easy&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Testing&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Untestable&lt;/td&gt;
      &lt;td&gt;Per-step specs&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Onboarding&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Days&lt;/td&gt;
      &lt;td&gt;Hours&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;5-feature-2-context-object-for-state-management&quot;&gt;5. Feature 2: Context Object for State Management&lt;/h2&gt;

&lt;h3 id=&quot;51-the-problem-instance-variable-chaos&quot;&gt;5.1 The Problem: Instance Variable Chaos&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Before: 95 instance variable references across 37 steps&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;seed_admin_users&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@admin_user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;seed_job_posts&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;job&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JobPost&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;company: &lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Where is @company set?&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Issues:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;No IDE autocomplete&lt;/li&gt;
  &lt;li&gt;No documentation of what’s available&lt;/li&gt;
  &lt;li&gt;No validation that required data exists&lt;/li&gt;
  &lt;li&gt;Testing requires complex setup&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;52-the-solution-explicit-context-object&quot;&gt;5.2 The Solution: Explicit Context Object&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# db/seeds/context.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Seeds&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Support&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# Core entities&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;attr_accessor&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:company&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;attr_accessor&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:admin_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:standard_user&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;attr_accessor&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:candidate_demo_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:access_request_user&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;attr_accessor&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:admin_avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:full_data_candidate_users&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;attr_accessor&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:demo_job_posts&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Client roles&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;attr_accessor&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:client_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:client_owner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:client_manager&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Batch processing&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;attr_accessor&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:batch_avatar_result&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;
        &lt;span class=&quot;vi&quot;&gt;@full_data_candidate_users&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
        &lt;span class=&quot;vi&quot;&gt;@demo_job_posts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Convenience methods&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;company?&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;nil?&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;batch_avatars?&lt;/span&gt;
        &lt;span class=&quot;vi&quot;&gt;@batch_avatar_result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;batch_avatar_count&lt;/span&gt;
        &lt;span class=&quot;vi&quot;&gt;@batch_avatar_result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;avatars_created&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Dependency validation&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;require!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;missing&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;nil?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Missing required context: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;missing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;, &apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;missing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;any?&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Debugging helper&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;summary&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;company: &lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@company&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;admin_user: &lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@admin_user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;full_data_candidates: &lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@full_data_candidate_users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;batch_avatars: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;batch_avatar_count&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;53-usage-in-steps&quot;&gt;5.3 Usage in Steps&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Access via ctx helper&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;seed_job_posts&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;require!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:admin_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Fail fast if deps missing&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;job&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JobPost&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;company: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;player: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;admin_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;title: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Senior Engineer&quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;demo_job_posts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;job&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;54-why-context-object-matters&quot;&gt;5.4 Why Context Object Matters&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Aspect&lt;/th&gt;
      &lt;th&gt;Instance Variables&lt;/th&gt;
      &lt;th&gt;Context Object&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Discovery&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grep @&lt;/code&gt; across files&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ctx.&lt;/code&gt; autocomplete&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Documentation&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;attr_accessor&lt;/code&gt; list&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Validation&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Runtime errors&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ctx.require!&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Testing&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Mock everything&lt;/td&gt;
      &lt;td&gt;Inject test context&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Debugging&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;puts everywhere&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ctx.summary&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;6-feature-3-conditional-execution-via-env-flags&quot;&gt;6. Feature 3: Conditional Execution via ENV Flags&lt;/h2&gt;

&lt;h3 id=&quot;61-use-cases&quot;&gt;6.1 Use Cases&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Use Case&lt;/th&gt;
      &lt;th&gt;ENV Flag&lt;/th&gt;
      &lt;th&gt;Behavior&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Skip slow reports&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SKIP_SEED_REPORTS=true&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Reports step skipped&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Create 40 full candidates&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SEED_FULL_CANDIDATES=true&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Rich candidate data&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Create 100k avatars&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CREATE_BATCH_PROFILES=true&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Batch processing enabled&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;62-implementation&quot;&gt;6.2 Implementation&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# db/seeds/steps.rb&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;OPTIONAL_STEPS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Skip steps (default: run)&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;seed_reports: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;SKIP_SEED_REPORTS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Enable steps (default: skip)&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;seed_full_data_candidates: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;SEED_FULL_CANDIDATES&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;seed_full_data_candidates_job_board: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;SEED_FULL_CANDIDATES&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;seed_batch_avatars: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;CREATE_BATCH_PROFILES&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;skip_step?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;env_var&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;OPTIONAL_STEPS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;env_var&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;env_var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start_with?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;SKIP_&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;env_var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;true&quot;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Skip if true&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;env_var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;true&quot;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Skip unless true&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;63-usage-examples&quot;&gt;6.3 Usage Examples&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Standard development seed (fast)&lt;/span&gt;
rails db:seed

&lt;span class=&quot;c&quot;&gt;# Full candidates for feature testing&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;SEED_FULL_CANDIDATES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true &lt;/span&gt;rails db:seed

&lt;span class=&quot;c&quot;&gt;# 100k avatars for QA stress testing&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;CREATE_BATCH_PROFILES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true &lt;/span&gt;rails db:seed

&lt;span class=&quot;c&quot;&gt;# Skip reports for faster iteration&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;SKIP_SEED_REPORTS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true &lt;/span&gt;rails db:seed

&lt;span class=&quot;c&quot;&gt;# Combined flags&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;SEED_FULL_CANDIDATES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CREATE_BATCH_PROFILES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true &lt;/span&gt;rails db:seed
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;64-benefits&quot;&gt;6.4 Benefits&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Scenario&lt;/th&gt;
      &lt;th&gt;Without Flags&lt;/th&gt;
      &lt;th&gt;With Flags&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Daily development&lt;/td&gt;
      &lt;td&gt;~5 min (full seed)&lt;/td&gt;
      &lt;td&gt;~30 sec (minimal)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Feature testing&lt;/td&gt;
      &lt;td&gt;Edit code&lt;/td&gt;
      &lt;td&gt;Set ENV var&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;QA stress test&lt;/td&gt;
      &lt;td&gt;Impossible&lt;/td&gt;
      &lt;td&gt;One command&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CI pipeline&lt;/td&gt;
      &lt;td&gt;Full seed always&lt;/td&gt;
      &lt;td&gt;Skip reports&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;7-feature-4-progress-reporting-with-timing&quot;&gt;7. Feature 4: Progress Reporting with Timing&lt;/h2&gt;

&lt;h3 id=&quot;71-the-problem-silent-seeds&quot;&gt;7.1 The Problem: Silent Seeds&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;rails db:seed
&lt;span class=&quot;c&quot;&gt;# ... silence for 30 seconds ...&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# ... more silence ...&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Is it working? Is it stuck?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;72-the-solution-real-time-progress&quot;&gt;7.2 The Solution: Real-Time Progress&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# db/seeds/timing.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Seeds&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Support&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Timing&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;start_run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;total_steps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;vi&quot;&gt;@total_steps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_steps&lt;/span&gt;
          &lt;span class=&quot;vi&quot;&gt;@current_step&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
          &lt;span class=&quot;vi&quot;&gt;@step_timings&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
          &lt;span class=&quot;vi&quot;&gt;@run_started_at&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;track_step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;vi&quot;&gt;@current_step&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;started&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;

          &lt;span class=&quot;n&quot;&gt;print_progress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quiet?&lt;/span&gt;

          &lt;span class=&quot;k&quot;&gt;yield&lt;/span&gt;

          &lt;span class=&quot;n&quot;&gt;duration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;started&lt;/span&gt;
          &lt;span class=&quot;vi&quot;&gt;@step_timings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duration&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;print_progress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;s2&quot;&gt;&quot;[%2d/%2d] %-40s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;vi&quot;&gt;@current_step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@total_steps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;step_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gsub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/^seed_/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;humanize&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;print_summary&lt;/span&gt;
          &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;=&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
          &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;SEED TIMING SUMMARY&quot;&lt;/span&gt;
          &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;=&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;

          &lt;span class=&quot;vi&quot;&gt;@step_timings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sort_by&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
            &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;  %-40s %6.2fs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

          &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;-&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;
          &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;  %-40s %6.2fs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;TOTAL&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;=&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;73-output-example&quot;&gt;7.3 Output Example&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[ 1/37] Predefined avatar assets              
[ 2/37] Company                               
[ 3/37] Job titles                            
[ 4/37] Admin players                           
...
[35/37] Summary                               
[36/37] Demo recommendation                   

============================================================
SEED TIMING SUMMARY
============================================================
  seed_full_data_candidates                     12.34s
  seed_random_avatars                           8.21s
  seed_homepage_avatars                         4.56s
  seed_job_posts                                 3.12s
  ...
------------------------------------------------------------
  TOTAL                                         45.67s
============================================================
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;74-benefits&quot;&gt;7.4 Benefits&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Aspect&lt;/th&gt;
      &lt;th&gt;Silent Seeds&lt;/th&gt;
      &lt;th&gt;Progress + Timing&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Feedback&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;Real-time progress&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Debugging&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Which step failed?&lt;/td&gt;
      &lt;td&gt;Clear step names&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Optimization&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Guess which is slow&lt;/td&gt;
      &lt;td&gt;Timing breakdown&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;CI logs&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Useless&lt;/td&gt;
      &lt;td&gt;Actionable&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;8-feature-5-post-seed-data-validation&quot;&gt;8. Feature 5: Post-Seed Data Validation&lt;/h2&gt;

&lt;h3 id=&quot;81-the-problem-silent-failures&quot;&gt;8.1 The Problem: Silent Failures&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Seeds &quot;complete&quot; but data is broken&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;seed_admin_users&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@admin_user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_or_initialize_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;email: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;admin@bluegeko.com&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@admin_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;save&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# No bang! Silent failure&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Later, in production demo...&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# &quot;Why is there no admin player?!&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;82-the-solution-validation-module&quot;&gt;8.2 The Solution: Validation Module&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# db/seeds/validation.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Seeds&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Support&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Validation&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;CHECKS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# Integrity checks (errors)&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;orphaned_avatars: &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;left_joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;players: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;candidates_without_avatars: &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;candidate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;left_joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;avatars: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;orphaned_job_posts: &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JobPost&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;left_joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;companies: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;# Minimum data checks (errors)&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;minimum_users: &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;minimum_avatars: &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;minimum_companies: &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;minimum_job_posts: &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JobPost&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;# Quality checks (warnings)&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;avatars_with_abilities: &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:abilities&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;distinct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;avatars_with_work_experience: &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:raid_logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;distinct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;users_with_access_keys: &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;distinct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;skip_result&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;skip?&lt;/span&gt;

          &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ValidationResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;run_integrity_checks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;run_minimum_checks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;run_quality_checks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strict?&lt;/span&gt;

          &lt;span class=&quot;n&quot;&gt;print_results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

        &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run_integrity_checks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;sx&quot;&gt;%i[orphaned_avatars candidates_without_avatars orphaned_job_posts]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;check&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;CHECKS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;check&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
              &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add_error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;check&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Found &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;check&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;humanize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;downcase&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
              &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add_passed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;check&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;83-validation-output&quot;&gt;8.3 Validation Output&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;============================================================
POST-SEED VALIDATION
============================================================
Integrity Checks:
  ✓ No orphaned avatars
  ✓ No candidates without avatars
  ✓ No orphaned job posts

Minimum Data Checks:
  ✓ Players: 47 (minimum: 5)
  ✓ Avatars: 42 (minimum: 3)
  ✓ Companies: 5 (minimum: 1)
  ✓ Job Posts: 25 (minimum: 5)

Quality Checks:
  ✓ Avatars with abilities: 38
  ✓ Avatars with work experience: 35
  ⚠ Players with login methods: 0 (warning)

------------------------------------------------------------
Result: PASSED (14 checks, 1 warning)
============================================================
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;84-env-controls&quot;&gt;8.4 ENV Controls&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Skip validation (CI speed)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;SKIP_SEED_VALIDATION&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true &lt;/span&gt;rails db:seed

&lt;span class=&quot;c&quot;&gt;# Strict mode (fail on warnings)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;SEED_VALIDATION_STRICT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true &lt;/span&gt;rails db:seed
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;9-feature-6-100k-full-avatar-scaling&quot;&gt;9. Feature 6: 100k+ Full Avatar Scaling&lt;/h2&gt;

&lt;h3 id=&quot;91-the-challenge&quot;&gt;9.1 The Challenge&lt;/h3&gt;

&lt;p&gt;Our AI team needed 100,000 realistic candidate avatars for model training. QA needed the same for stress testing. Creating records one-by-one would take hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requirements:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Full avatars with ALL associations&lt;/li&gt;
  &lt;li&gt;Category-appropriate job titles&lt;/li&gt;
  &lt;li&gt;Skills, work experiences, education&lt;/li&gt;
  &lt;li&gt;Portfolio, contact info, avatars&lt;/li&gt;
  &lt;li&gt;Realistic data distributions&lt;/li&gt;
  &lt;li&gt;Conditional execution (don’t run in daily dev)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;92-what-gets-created&quot;&gt;9.2 What Gets Created&lt;/h3&gt;

&lt;p&gt;Each batch avatar is a &lt;strong&gt;complete candidate&lt;/strong&gt; with:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Association&lt;/th&gt;
      &lt;th&gt;Per Avatar&lt;/th&gt;
      &lt;th&gt;Total (100k)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Player + Login Security&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;100,000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Avatar&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;100,000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Skills&lt;/td&gt;
      &lt;td&gt;3-6&lt;/td&gt;
      &lt;td&gt;~450,000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Work Experiences&lt;/td&gt;
      &lt;td&gt;2-4&lt;/td&gt;
      &lt;td&gt;~300,000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Education Items&lt;/td&gt;
      &lt;td&gt;1-2&lt;/td&gt;
      &lt;td&gt;~150,000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Languages&lt;/td&gt;
      &lt;td&gt;1-3&lt;/td&gt;
      &lt;td&gt;~200,000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Contact Infos&lt;/td&gt;
      &lt;td&gt;2-4&lt;/td&gt;
      &lt;td&gt;~300,000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Portfolio + Links&lt;/td&gt;
      &lt;td&gt;3-9&lt;/td&gt;
      &lt;td&gt;~600,000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Location Setting&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;100,000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Salary Expectation&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;100,000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Avatar Session + Images&lt;/td&gt;
      &lt;td&gt;4-5&lt;/td&gt;
      &lt;td&gt;~450,000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Total Records&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;~20-30&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;~2,500,000+&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;93-category-specific-data&quot;&gt;9.3 Category-Specific Data&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;CATEGORY_OCCUPATIONS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;technology&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;Software Engineer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Senior Software Engineer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Staff Software Engineer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;DevOps Engineer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Data Scientist&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Machine Learning Engineer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;Mobile Developer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Cloud Architect&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Security Engineer&quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;design&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;UX Designer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Product Designer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Creative Director&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;Interaction Designer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Design Systems Lead&quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;business&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;Project Manager&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Product Manager&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Business Analyst&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;Strategy Consultant&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Operations Manager&quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ... 9 categories total&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;CATEGORY_SKILLS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;technology&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%w[Ruby Python JavaScript TypeScript Docker Kubernetes AWS]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;design&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%w[Figma Sketch Photoshop Wireframing UserResearch]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;business&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%w[Excel Salesforce Tableau Leadership Strategy]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;94-implementation-highlights&quot;&gt;9.4 Implementation Highlights&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# db/seeds/batch_avatars.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Seeds&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Support&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;BatchAvatars&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;DEFAULT_COUNT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100_000&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;CHUNK_SIZE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;count: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;target_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;on_progress: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BatchResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;target_count: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each_slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chunk_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;indices&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;chunk_result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;process_chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;indices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;record_chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chunk_result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;on_progress&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;finish!&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;process_chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;indices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;indices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# Select random category&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;category&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;categories&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sample&lt;/span&gt;

          &lt;span class=&quot;c1&quot;&gt;# Create player (ActiveRecord for Devise)&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;player&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create_batch_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;index: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

          &lt;span class=&quot;c1&quot;&gt;# Create avatar with category-appropriate job title&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create_batch_avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;player: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;category: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;category&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

          &lt;span class=&quot;c1&quot;&gt;# Create ALL associations&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;create_abilities&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;category&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;slug&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;create_raid_logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;category&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;slug&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;create_training_logs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;create_dialects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;create_beacons&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;create_portfolio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;create_map_rule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;create_bounty_rule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;create_avatar_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;95-usage&quot;&gt;9.5 Usage&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Create 100,000 full avatars (default)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;CREATE_BATCH_PROFILES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true &lt;/span&gt;rails db:seed

&lt;span class=&quot;c&quot;&gt;# Create 10,000 for smaller tests&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;CREATE_BATCH_PROFILES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;BATCH_PROFILE_COUNT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;10000 rails db:seed

&lt;span class=&quot;c&quot;&gt;# With verbose progress&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;CREATE_BATCH_PROFILES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;BATCH_VERBOSE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true &lt;/span&gt;rails db:seed
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;96-output&quot;&gt;9.6 Output&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;======================================================================
[BATCH] Starting FULL candidate avatar creation
======================================================================
  Target count:     100,000
  Chunk size:       500
  Associations:     Skills, Work Exp, Education, Languages, Contacts, etc.
======================================================================

  [=======&amp;gt;--------------------]  25.0% | Chunk 50/200 | 25000 avatars | 625,000 assoc

======================================================================
[BATCH] Full candidate avatar creation complete!
======================================================================
  Players created:        100,000
  Avatars created:     100,000
  Associations created: 2,543,721
  Total duration:       3h 12m
  Rate:                 8.7 avatars/second
======================================================================
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;97-performance-characteristics&quot;&gt;9.7 Performance Characteristics&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Count&lt;/th&gt;
      &lt;th&gt;Time&lt;/th&gt;
      &lt;th&gt;Records Created&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;1,000&lt;/td&gt;
      &lt;td&gt;~2-3 min&lt;/td&gt;
      &lt;td&gt;~25,000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10,000&lt;/td&gt;
      &lt;td&gt;~20-30 min&lt;/td&gt;
      &lt;td&gt;~250,000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;100,000&lt;/td&gt;
      &lt;td&gt;~3-5 hours&lt;/td&gt;
      &lt;td&gt;~2,500,000&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;10-benefits-and-use-cases&quot;&gt;10. Benefits and Use Cases&lt;/h2&gt;

&lt;h3 id=&quot;101-use-case-matrix&quot;&gt;10.1 Use Case Matrix&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Stakeholder&lt;/th&gt;
      &lt;th&gt;Command&lt;/th&gt;
      &lt;th&gt;Result&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Daily Dev&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rails db:seed&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;~30s, minimal data&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Feature Testing&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SEED_FULL_CANDIDATES=true rails db:seed&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;~2 min, 40 rich avatars&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;QA Stress Test&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CREATE_BATCH_PROFILES=true rails db:seed&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;~3h, 100k avatars&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;AI Training&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CREATE_BATCH_PROFILES=true BATCH_PROFILE_COUNT=500000 rails db:seed&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;~15h, 500k avatars&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;CEO Demo&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rails db:seed&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Production-like feel&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;New Engineer&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rails db:seed&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Working environment in 30s&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;102-before-vs-after-comparison&quot;&gt;10.2 Before vs After Comparison&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Aspect&lt;/th&gt;
      &lt;th&gt;Before&lt;/th&gt;
      &lt;th&gt;After&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;File organization&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;1 file, 3,610 lines&lt;/td&gt;
      &lt;td&gt;37 files, ~80 lines each&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Test coverage&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;0%&lt;/td&gt;
      &lt;td&gt;400+ specs&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Documentation&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;1,797-line README&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Progress feedback&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Silent&lt;/td&gt;
      &lt;td&gt;Real-time with timing&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Data validation&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;14 automated checks&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Scalability&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;~100 avatars&lt;/td&gt;
      &lt;td&gt;100,000+ avatars&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Conditional execution&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Edit code&lt;/td&gt;
      &lt;td&gt;ENV flags&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Onboarding time&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Days&lt;/td&gt;
      &lt;td&gt;Hours&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Git conflicts&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Daily&lt;/td&gt;
      &lt;td&gt;Rare&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;103-roi&quot;&gt;10.3 ROI&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Time saved per week:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;5 engineers × 10 min/day waiting for seeds = 4+ hours&lt;/li&gt;
  &lt;li&gt;2 QA engineers × 1 hour/week debugging broken seeds = 2 hours&lt;/li&gt;
  &lt;li&gt;1 demo preparation × 2 hours = 2 hours&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Total: ~8 hours/week = 1 FTE day&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;11-testing-the-seed-system&quot;&gt;11. Testing the Seed System&lt;/h2&gt;

&lt;h3 id=&quot;111-test-structure&quot;&gt;11.1 Test Structure&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;spec/seeds/
├── support/
│   ├── helpers_spec.rb           # Unit tests for helpers
│   ├── context_spec.rb           # Context object tests
│   ├── timing_spec.rb            # Timing module tests
│   ├── validation_spec.rb        # Validation tests
│   └── batch_avatars_spec.rb    # Batch processing tests
├── reference_data_spec.rb        # Reference data tests
├── integration_spec.rb           # Full seed run tests
└── steps/
    ├── shared_examples.rb        # Reusable step examples
    ├── company_spec.rb           # Step-specific tests
    └── ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;112-shared-examples-for-steps&quot;&gt;11.2 Shared Examples for Steps&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# spec/seeds/steps/shared_examples.rb&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;shared_examples&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;a seed step that creates records&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model_class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;expected_count&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;creates &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;expected_count&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model_class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pluralize&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;run_step&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;change&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model_class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;expected_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;shared_examples&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;a seed step that is idempotent&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;does not create duplicates on second run&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;run_step&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;run_step&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;not_to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;change&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;described_model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;113-test-coverage&quot;&gt;11.3 Test Coverage&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Component&lt;/th&gt;
      &lt;th&gt;Specs&lt;/th&gt;
      &lt;th&gt;Coverage&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Helpers&lt;/td&gt;
      &lt;td&gt;45&lt;/td&gt;
      &lt;td&gt;100%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Context&lt;/td&gt;
      &lt;td&gt;32&lt;/td&gt;
      &lt;td&gt;100%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Timing&lt;/td&gt;
      &lt;td&gt;28&lt;/td&gt;
      &lt;td&gt;100%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Validation&lt;/td&gt;
      &lt;td&gt;31&lt;/td&gt;
      &lt;td&gt;100%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Batch Avatars&lt;/td&gt;
      &lt;td&gt;49&lt;/td&gt;
      &lt;td&gt;100%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Reference Data&lt;/td&gt;
      &lt;td&gt;89&lt;/td&gt;
      &lt;td&gt;100%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Individual Steps&lt;/td&gt;
      &lt;td&gt;126&lt;/td&gt;
      &lt;td&gt;Key paths&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;400+&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;High&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;12-three-bonus-ideas-worth-implementing&quot;&gt;12. Three Bonus Ideas Worth Implementing&lt;/h2&gt;

&lt;h3 id=&quot;121-idea-1-seed-data-snapshots&quot;&gt;12.1 Idea 1: Seed Data Snapshots&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Recreating 100k avatars takes 3 hours every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Database snapshots for instant restore.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Potential implementation&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Seeds&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Snapshots&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;sb&quot;&gt;`pg_dump testapp_development &amp;gt; snapshots/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;.sql`&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;restore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;sb&quot;&gt;`psql testapp_development &amp;lt; snapshots/&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;.sql`&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Usage&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# After 3-hour batch: SEED_SNAPSHOT=save rails db:seed&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Next time: SEED_SNAPSHOT=restore:batch_100k rails db:seed (~30 sec)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Benefit:&lt;/strong&gt; 3 hours → 30 seconds for QA environments.&lt;/p&gt;

&lt;h3 id=&quot;122-idea-2-faker-seed-consistency&quot;&gt;12.2 Idea 2: Faker Seed Consistency&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Random data changes every seed run, breaking visual regression tests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Seed Faker’s random generator.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# db/seeds/support.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;with_consistent_faker&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;original_seed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Faker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;random&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Faker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;random&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;yield&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ensure&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Faker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;random&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;original_seed&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Usage&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;with_consistent_faker&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;seed_avatars&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Same names, emails, etc. every time&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Benefit:&lt;/strong&gt; Consistent screenshots for visual regression testing.&lt;/p&gt;

&lt;h3 id=&quot;123-idea-3-seed-data-metrics-dashboard&quot;&gt;12.3 Idea 3: Seed Data Metrics Dashboard&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; No visibility into what’s seeded without running queries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Post-seed metrics report.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# db/seeds/metrics.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Seeds&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Metrics&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;report&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;players: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;total: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;by_role: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;with_avatars: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;avatars: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;total: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;with_abilities: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:abilities&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;distinct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;avg_abilities: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:abilities&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;by_category: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:category&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;categories.slug&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;companies: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;total: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;ss&quot;&gt;with_jobs: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:job_posts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;distinct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;Players&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;total&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;100,047&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;by_role&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;admin&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;candidate&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;040&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;with_avatars&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;100,042&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;Avatars&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;total&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;100,042&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;with_abilities&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;99,800&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;avg_abilities&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;4.2&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;by_category&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;technology&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;45&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;000&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;design&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;000&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;Companies&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;total&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;5&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;with_jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Benefit:&lt;/strong&gt; Instant insight into seed data quality and distribution.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;13-conclusion&quot;&gt;13. Conclusion&lt;/h2&gt;

&lt;h3 id=&quot;131-what-we-achieved&quot;&gt;13.1 What We Achieved&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Metric&lt;/th&gt;
      &lt;th&gt;Before&lt;/th&gt;
      &lt;th&gt;After&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Lines in main file&lt;/td&gt;
      &lt;td&gt;3,610&lt;/td&gt;
      &lt;td&gt;217&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Number of files&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;37&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Test coverage&lt;/td&gt;
      &lt;td&gt;0%&lt;/td&gt;
      &lt;td&gt;400+ specs&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Documentation&lt;/td&gt;
      &lt;td&gt;0 lines&lt;/td&gt;
      &lt;td&gt;1,797 lines&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Max avatars&lt;/td&gt;
      &lt;td&gt;~100&lt;/td&gt;
      &lt;td&gt;100,000+&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Progress feedback&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;Real-time&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Data validation&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;14 checks&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Conditional execution&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;5 ENV flags&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;132-key-takeaways&quot;&gt;13.2 Key Takeaways&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Modular beats monolithic&lt;/strong&gt; — Split large files into focused modules&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Explicit state beats implicit&lt;/strong&gt; — Context objects over instance variables&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Conditional execution is essential&lt;/strong&gt; — ENV flags for different scenarios&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Visibility prevents pain&lt;/strong&gt; — Progress reporting and timing&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Validation catches bugs&lt;/strong&gt; — Automated checks before they reach demos&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Scale when needed&lt;/strong&gt; — Batch processing for volume requirements&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Document everything&lt;/strong&gt; — Future you (and new engineers) will thank you&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;133-the-real-win&quot;&gt;13.3 The Real Win&lt;/h3&gt;

&lt;p&gt;The technical improvements are measurable, but the real win is &lt;strong&gt;confidence&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Confident that seeds work (validation)&lt;/li&gt;
  &lt;li&gt;Confident about progress (timing)&lt;/li&gt;
  &lt;li&gt;Confident in isolation (modular files)&lt;/li&gt;
  &lt;li&gt;Confident at scale (batch processing)&lt;/li&gt;
  &lt;li&gt;Confident for demos (realistic data)&lt;/li&gt;
  &lt;li&gt;Confident for new engineers (documentation)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Invest in your seed system. It’s the foundation everything else is built on.&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Rails Guides on Seeds&lt;/strong&gt;: &lt;a href=&quot;https://guides.rubyonrails.org/active_record_migrations.html#migrations-and-seed-data&quot;&gt;guides.rubyonrails.org/active_record_migrations.html#migrations-and-seed-data&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Faker Gem&lt;/strong&gt;: &lt;a href=&quot;https://github.com/faker-ruby/faker&quot;&gt;github.com/faker-ruby/faker&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;FactoryBot&lt;/strong&gt;: &lt;a href=&quot;https://github.com/thoughtbot/factory_bot&quot;&gt;github.com/thoughtbot/factory_bot&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Database Cleaner&lt;/strong&gt;: &lt;a href=&quot;https://github.com/DatabaseCleaner/database_cleaner&quot;&gt;github.com/DatabaseCleaner/database_cleaner&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Our Seed System Documentation&lt;/strong&gt;: See &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;db/seeds/README.md&lt;/code&gt; in your codebase&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;This post documents the transformation of the TestApp seed system from a 3,600-line monolith to an enterprise-grade data generation platform. The patterns described here—modular architecture, context objects, conditional execution, progress tracking, validation, and batch processing—are applicable to any Rails application that has outgrown simple seeds.&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Sat, 24 Jan 2026 00:00:00 +0000</pubDate>
        <link>https://lukin.io/blog/enterprise-seed-system-rails/</link>
        <guid isPermaLink="true">https://lukin.io/blog/enterprise-seed-system-rails/</guid>
        
        <category>rails</category>
        
        <category>database</category>
        
        <category>seeding</category>
        
        <category>testing</category>
        
        <category>refactoring</category>
        
        <category>architecture</category>
        
        <category>performance</category>
        
        <category>qa</category>
        
        <category>ai-training</category>
        
        
        <category>engineering</category>
        
        <category>rails</category>
        
        <category>best-practices</category>
        
        <category>architecture</category>
        
      </item>
    
      <item>
        <title>From Zero-Gap to Zero-Drift: Making AI Follow Rails Rules (Frozen vs Strict)</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;Contract-first + verification gates eliminate &lt;em&gt;gaps&lt;/em&gt; (missing fields, regressions). fileciteturn2file0&lt;/li&gt;
    &lt;li&gt;But LLMs still create &lt;strong&gt;rule drift&lt;/strong&gt; (they quote rules, then violate them).&lt;/li&gt;
    &lt;li&gt;We fixed drift with &lt;strong&gt;guardrails&lt;/strong&gt;, not more prose: &lt;strong&gt;Hard-Fail rule IDs&lt;/strong&gt;, &lt;strong&gt;STOP gates&lt;/strong&gt;, &lt;strong&gt;mandatory evidence audits&lt;/strong&gt;, and &lt;strong&gt;negative examples&lt;/strong&gt;.&lt;/li&gt;
    &lt;li&gt;Entry point stays tiny and stable; the enforcement lives in versioned docs.&lt;/li&gt;
    &lt;li&gt;We keep two characters: &lt;strong&gt;Frozen (v1.0)&lt;/strong&gt; for speed, &lt;strong&gt;Strict (v1.1)&lt;/strong&gt; for enforcement.&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;why-this-follow-up-exists&quot;&gt;Why this follow-up exists&lt;/h2&gt;

&lt;p&gt;In &lt;strong&gt;Zero-Gap API Development&lt;/strong&gt;, the goal was “ship APIs with zero regressions” by turning implementation into a contract + verification problem: TypeScript interfaces as canonical truth, safe defaults, systematic verification gates, and discrepancy classification (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[IMPL]&lt;/code&gt; vs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[DOC]&lt;/code&gt;). fileciteturn2file0&lt;/p&gt;

&lt;p&gt;That framework works. The problem we hit next was different:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;AI doesn’t drift because rules are unclear. It drifts because rules have no consequences.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Even when an agent reads &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt; and repeats rules back, it can still “helpfully” shortcut:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;“Use Ransack for filtering/searching.” → agent writes custom &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;where(...)&lt;/code&gt; anyway.&lt;/li&gt;
  &lt;li&gt;“Avoid DB-specific SQL.” → agent sneaks in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ILIKE&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IFNULL&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;REGEXP&lt;/code&gt;, etc.&lt;/li&gt;
  &lt;li&gt;“Controllers only wrap envelopes.” → agent hand-builds JSON in controller.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So this session was about adding an enforcement layer on top of Zero-Gap.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-goal-of-this-session&quot;&gt;The goal of this session&lt;/h2&gt;

&lt;p&gt;We wanted a system where a reviewer can say:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“This violates &lt;strong&gt;two rules&lt;/strong&gt;: HF-1 and HF-2”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;…&lt;strong&gt;and the agent itself is forced to detect and correct that before shipping code.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To do that, we restructured the workflow into a toolchain:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PROMPT.md&lt;/code&gt; → execution runtime (phases, STOP gates, verification receipts)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt; → &lt;strong&gt;hard authority&lt;/strong&gt; (Hard-Fail rules, failure semantics)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GUIDE.md&lt;/code&gt; → patterns &amp;amp; examples (non-authority, includes anti-patterns)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt; → canonical verifier&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-single-entry-point-stable-forever&quot;&gt;The single entry point (stable forever)&lt;/h2&gt;

&lt;p&gt;Instead of embedding giant prompts in README/issues, every task uses the same tiny entry point:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Task: WEB-485 – User Notifications

Refs:
- Requirements: doc/requirements/users/USER_NOTIFICATIONS.md
- Flow: doc/flow/WEB-485_user_notifications.md
- PRD: doc/prd/WEB-485_user_notifications_PRD.md

Execute per PROMPT.md.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Important property:&lt;/strong&gt; the task block never changes.&lt;br /&gt;
Strictness evolves only inside &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PROMPT.md&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GUIDE.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This prevents the most common drift source: copying outdated prompt blobs across tickets.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;frozen-vs-strict-why-we-keep-both&quot;&gt;Frozen vs Strict: why we keep both&lt;/h2&gt;

&lt;p&gt;We ended up with two prompt characters:&lt;/p&gt;

&lt;h3 id=&quot;frozen-v10&quot;&gt;Frozen (v1.0)&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Optimized for: &lt;strong&gt;brevity&lt;/strong&gt;, human readability, speed&lt;/li&gt;
  &lt;li&gt;Best for: trusted contributors, small refactors, quick spikes&lt;/li&gt;
  &lt;li&gt;Weakness: rules are descriptive (“MUST”), so AI can rationalize shortcuts&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;strict-v11&quot;&gt;Strict (v1.1)&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Optimized for: &lt;strong&gt;compliance&lt;/strong&gt;, auditability, “no surprises” AI execution&lt;/li&gt;
  &lt;li&gt;Best for: contract-sensitive endpoints, anything touching search/filtering/auth/pagination&lt;/li&gt;
  &lt;li&gt;Weakness: longer text (but it’s enforcement scaffolding, not noise)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key is: &lt;strong&gt;both share the same entry point.&lt;/strong&gt; You swap the runtime, not the call site.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-actually-stops-drift-the-4-mechanisms&quot;&gt;What actually stops drift (the 4 mechanisms)&lt;/h2&gt;

&lt;h3 id=&quot;1-hard-fail-rule-ids-hf-&quot;&gt;1) Hard-Fail rule IDs (HF-*)&lt;/h3&gt;
&lt;p&gt;Instead of “Rule #5,” we introduced stable IDs:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;HF-1&lt;/strong&gt; — Ransack-only search/filtering&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;HF-2&lt;/strong&gt; — DB-agnostic queries only (no raw SQL / DB-specific funcs)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;HF-3&lt;/strong&gt; — Blueprinter-only JSON&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;HF-4&lt;/strong&gt; — snake_case only&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;HF-5&lt;/strong&gt; — Required fields never null (safe defaults)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why IDs matter:&lt;/strong&gt; they turn “standards” into enforceable references:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;easy review comments (“HF-2 violation”)&lt;/li&gt;
  &lt;li&gt;easy self-audit checkboxes&lt;/li&gt;
  &lt;li&gt;easy future evolution (rename content, keep ID stable)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;2-stop-gates-control-flow-not-suggestions&quot;&gt;2) STOP gates (control flow, not suggestions)&lt;/h3&gt;
&lt;p&gt;Strict mode adds explicit control flow:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Phase 0–2: &lt;strong&gt;NO CODE&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Stop after planning&lt;/li&gt;
  &lt;li&gt;If any HF-* would be violated → &lt;strong&gt;STOP&lt;/strong&gt; and report&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;LLMs follow control flow better than prose.&lt;/p&gt;

&lt;h3 id=&quot;3-mandatory-rule-compliance-audit-proof-not-vibes&quot;&gt;3) Mandatory Rule Compliance Audit (proof, not vibes)&lt;/h3&gt;
&lt;p&gt;Before final output, the agent must produce:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;RULE COMPLIANCE AUDIT
HF-1: COMPLIANT — evidence: UsersController#index uses User.ransack(params[:q])
HF-2: COMPLIANT — evidence: no raw SQL; only ActiveRecord/Arel
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This forces the agent to &lt;em&gt;prove&lt;/em&gt; it complied.
If it can’t produce evidence, it typically self-corrects before shipping.&lt;/p&gt;

&lt;h3 id=&quot;4-negative-examples-anti-pattern-firewall&quot;&gt;4) Negative examples (anti-pattern firewall)&lt;/h3&gt;
&lt;p&gt;Guides that only show “good” patterns still allow AI to invent “bad” ones.
So we added explicit “DO NOT COPY” snippets:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# ❌ HF-1 violation (manual SQL filtering)&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;email ILIKE ?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;%&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# ✅ Correct (Ransack owns filtering)&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ransack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:q&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;LLMs imitate examples aggressively. Negative examples prevent “helpful improvisation.”&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;example-the-exact-drift-were-preventing&quot;&gt;Example: the exact drift we’re preventing&lt;/h2&gt;

&lt;h3 id=&quot;bad-hf-1--hf-2-violation&quot;&gt;Bad (HF-1 + HF-2 violation)&lt;/h3&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# ❌ don&apos;t do this&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;users&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;email ILIKE ?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;%&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;good-hf-1-compliant&quot;&gt;Good (HF-1 compliant)&lt;/h3&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;search&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ransack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:q&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;users&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;per&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:per_page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In strict mode, if an agent proposes the bad version, it must output:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;❌ RULE VIOLATION
Rule: HF-1
Location: app/controllers/...:12
Reason: manual filtering used instead of Ransack
Required Fix: replace with Model.ransack(params[:q]).result
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;No debate. Fix it or stop.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;verification-one-command-one-receipt&quot;&gt;Verification: one command, one receipt&lt;/h2&gt;

&lt;p&gt;Zero-Gap already required verification gates. fileciteturn2file0&lt;br /&gt;
The strict upgrade makes verification harder to “forget” by standardizing:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Preferred: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Legacy commands remain in docs as commented fallback&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the final output requires a &lt;strong&gt;CHECKS&lt;/strong&gt; section with exit codes.&lt;/p&gt;

&lt;p&gt;That turns “I think I ran tests” into a receipt.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;operational-guidance-when-to-use-which-mode&quot;&gt;Operational guidance: when to use which mode&lt;/h2&gt;

&lt;p&gt;Use &lt;strong&gt;Frozen (v1.0)&lt;/strong&gt; when:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;small refactor&lt;/li&gt;
  &lt;li&gt;low contract risk&lt;/li&gt;
  &lt;li&gt;you want short prompts and fast iteration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use &lt;strong&gt;Strict (v1.1)&lt;/strong&gt; when:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;building/upgrading endpoints&lt;/li&gt;
  &lt;li&gt;touching filtering/search/pagination/auth&lt;/li&gt;
  &lt;li&gt;upgrading versioned requirement docs&lt;/li&gt;
  &lt;li&gt;AI is doing most of the work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If in doubt: default to &lt;strong&gt;Strict&lt;/strong&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;closing-zero-gap-is-the-architecture-zero-drift-is-governance&quot;&gt;Closing: Zero-Gap is the architecture; Zero-Drift is governance&lt;/h2&gt;

&lt;p&gt;Zero-Gap prevents missing fields and regressions by making contracts and verification non-negotiable. fileciteturn2file0&lt;br /&gt;
Zero-Drift makes AI reliably follow your engineering rules by adding:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;failure semantics&lt;/li&gt;
  &lt;li&gt;proof requirements&lt;/li&gt;
  &lt;li&gt;anti-pattern training&lt;/li&gt;
  &lt;li&gt;a stable entry point&lt;/li&gt;
  &lt;li&gt;a canonical verifier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, this reduces review churn dramatically:
you spend less time catching “obvious violations” and more time on product decisions.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;appendix-quick-mental-model&quot;&gt;Appendix: quick mental model&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;REQUIREMENT_DOC&lt;/code&gt; = spec&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PROMPT.md&lt;/code&gt; = runtime&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt; = language law + compiler errors (HF-*)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GUIDE.md&lt;/code&gt; = standard library + examples&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt; = test runner&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Wed, 07 Jan 2026 00:00:00 +0000</pubDate>
        <link>https://lukin.io/blog/from-zero-gap-to-zero-drift/</link>
        <guid isPermaLink="true">https://lukin.io/blog/from-zero-gap-to-zero-drift/</guid>
        
        <category>ai</category>
        
        <category>software-engineering</category>
        
        <category>rails</category>
        
        <category>api</category>
        
        <category>governance</category>
        
        <category>prompts</category>
        
        <category>verification</category>
        
        <category>contracts</category>
        
        
        <category>engineering</category>
        
        <category>rails</category>
        
        <category>AI</category>
        
        <category>best-practices</category>
        
      </item>
    
      <item>
        <title>How to Start with Agentic Development: What to Know, What to Automate, and How to Use It</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;Agentic development is a loop: &lt;strong&gt;Plan → Act (tools) → Observe → Decide → Repeat&lt;/strong&gt;.&lt;/li&gt;
    &lt;li&gt;The biggest productivity gains come from &lt;strong&gt;determinism, not prompting&lt;/strong&gt;: a single verify command, quality gates, and enforcement via hooks.&lt;/li&gt;
    &lt;li&gt;The “must-know” stack in 2026: &lt;strong&gt;AGENTS.md&lt;/strong&gt;, &lt;strong&gt;Skills&lt;/strong&gt;, &lt;strong&gt;MCP&lt;/strong&gt;, &lt;strong&gt;RAG&lt;/strong&gt;, &lt;strong&gt;LSP&lt;/strong&gt;, &lt;strong&gt;Hooks&lt;/strong&gt;, and &lt;strong&gt;Workflows&lt;/strong&gt;.&lt;/li&gt;
    &lt;li&gt;My two most effective multipliers:
      &lt;ul&gt;
        &lt;li&gt;&lt;strong&gt;Contract-first + verification gates&lt;/strong&gt; (“Zero‑Gap API development”)&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Documentation hierarchy + Flow docs with Responsible Files&lt;/strong&gt; (documentation-driven development that scales)&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;why-agentic-development-feels-different-from-ai-coding-assistants&quot;&gt;Why agentic development feels different from “AI coding assistants”&lt;/h2&gt;

&lt;p&gt;Autocomplete and chat are mostly &lt;strong&gt;suggestion engines&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Agentic development is when the model runs a structured loop with tools:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;it reads your repo conventions&lt;/li&gt;
  &lt;li&gt;it edits multiple files&lt;/li&gt;
  &lt;li&gt;it runs commands&lt;/li&gt;
  &lt;li&gt;it interprets errors&lt;/li&gt;
  &lt;li&gt;it iterates until the system is green&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means your productivity is no longer limited by “how well can I prompt?”&lt;br /&gt;
It’s limited by &lt;strong&gt;how well your repo communicates intent and enforces correctness&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The modern skill is not “prompting.”&lt;br /&gt;
It’s &lt;strong&gt;context engineering + verification engineering&lt;/strong&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-mental-model-the-agent-loop&quot;&gt;The mental model: the agent loop&lt;/h2&gt;

&lt;p&gt;An “agent” is just a loop with tool use:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Plan&lt;/strong&gt; (what files, what changes, what tests)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Act&lt;/strong&gt; (edit files, run commands, call tools)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Observe&lt;/strong&gt; (test output, logs, compiler errors, lint)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Decide&lt;/strong&gt; (fix, rollback, re-plan)&lt;/li&gt;
  &lt;li&gt;Repeat until done&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your job (as a senior engineer) is to make this loop:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;well-scoped&lt;/strong&gt; (small diffs, explicit “definition of done”)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;well-instrumented&lt;/strong&gt; (fast tests + lint + security checks)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;hard to cheat&lt;/strong&gt; (quality gates / hooks / CI)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;context-efficient&lt;/strong&gt; (agent sees the right context, not more context)&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;glossary-plugins-skills-mcp-rag-lsp-hooks-workflows&quot;&gt;Glossary: plugins, skills, MCP, RAG, LSP, hooks, workflows&lt;/h2&gt;

&lt;p&gt;Here’s the vocabulary, in a practical order:&lt;/p&gt;

&lt;h3 id=&quot;agentsmd&quot;&gt;AGENTS.md&lt;/h3&gt;
&lt;p&gt;A repo-local “operating manual” for coding agents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it’s for&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Encode how &lt;em&gt;this repo&lt;/em&gt; works (commands, conventions, boundaries)&lt;/li&gt;
  &lt;li&gt;Stop re-explaining the same rules every session&lt;/li&gt;
  &lt;li&gt;Make agent behavior predictable across tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;README.md&lt;/code&gt; for agents, but stricter and more actionable.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;skills&quot;&gt;Skills&lt;/h3&gt;
&lt;p&gt;A “Skill” is a packaged workflow (instructions + optional resources/scripts) that the agent can load on demand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it’s for&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Turn your best senior workflows into reusable modules:
    &lt;ul&gt;
      &lt;li&gt;“Implement Rails endpoint from contract + request specs”&lt;/li&gt;
      &lt;li&gt;“Safe refactor: characterization tests first”&lt;/li&gt;
      &lt;li&gt;“Write flow doc + Responsible Files table”&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Reduce quality drift and context repetition&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If AGENTS.md is global repo policy, Skills are &lt;strong&gt;repeatable procedures&lt;/strong&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;hooks&quot;&gt;Hooks&lt;/h3&gt;
&lt;p&gt;Hooks are automation that runs at specific moments (after edits, before stopping, etc.).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it’s for&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Enforce quality without relying on “remember to do it”&lt;/li&gt;
  &lt;li&gt;Auto-format after edits&lt;/li&gt;
  &lt;li&gt;Block dangerous operations&lt;/li&gt;
  &lt;li&gt;Require &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt; passing before the agent can “finish”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Prompts are best-effort. Hooks are default behavior.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;workflows&quot;&gt;Workflows&lt;/h3&gt;
&lt;p&gt;Workflows are your end-to-end process:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;feature → tests → verify → docs → PR&lt;/li&gt;
  &lt;li&gt;bugfix → reproduction test → fix → verify → postmortem note&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Skills + hooks + CI turn workflows into a system.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;rag-retrieval-augmented-generation&quot;&gt;RAG (Retrieval-Augmented Generation)&lt;/h3&gt;
&lt;p&gt;RAG is the pattern of retrieving relevant docs/files and injecting them as context for the model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it’s for&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Ground the agent in &lt;em&gt;your&lt;/em&gt; docs and code&lt;/li&gt;
  &lt;li&gt;Reduce hallucinations&lt;/li&gt;
  &lt;li&gt;Make “documentation-driven development” actually useful to the model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice: RAG is how an agent “remembers” your system without you pasting the whole repo.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;lsp-language-server-protocol&quot;&gt;LSP (Language Server Protocol)&lt;/h3&gt;
&lt;p&gt;LSP is what powers “go to definition,” “find references,” “rename symbol,” etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it’s for&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Semantic navigation beats grep for refactors and correctness&lt;/li&gt;
  &lt;li&gt;Agents become safer when they can reliably answer:
    &lt;ul&gt;
      &lt;li&gt;“Where is this symbol used?”&lt;/li&gt;
      &lt;li&gt;“What implements this interface?”&lt;/li&gt;
      &lt;li&gt;“What breaks if I change this method signature?”&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;mcp-model-context-protocol&quot;&gt;MCP (Model Context Protocol)&lt;/h3&gt;
&lt;p&gt;MCP is a standard way to connect an agent to tools and data sources (issue trackers, DB schema, docs, internal APIs).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it’s for&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Reduce copy/paste context&lt;/li&gt;
  &lt;li&gt;Give structured, permissioned access to external systems&lt;/li&gt;
  &lt;li&gt;Make tool integrations reusable across clients/vendors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MCP is most useful when your bottleneck is &lt;strong&gt;access to information/tools&lt;/strong&gt;, not coding speed.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-agentic-stack-in-one-picture&quot;&gt;The “agentic stack” in one picture&lt;/h2&gt;

&lt;p&gt;A practical way to visualize the stack:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Workflow (what to do)
  └─ Hooks (force it to happen)
      └─ Tool/data access (MCP)
          └─ Context feeding (RAG + LSP)
              └─ Skills (repeatable procedures)
                  └─ AGENTS.md (repo rules + commands + DoD)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you want a big productivity jump: build from the bottom up.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-senior-engineer-principle-determinism-beats-prompting&quot;&gt;The senior engineer principle: determinism beats prompting&lt;/h2&gt;

&lt;p&gt;If something must happen every time:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;formatting&lt;/li&gt;
  &lt;li&gt;tests&lt;/li&gt;
  &lt;li&gt;lint&lt;/li&gt;
  &lt;li&gt;security scan&lt;/li&gt;
  &lt;li&gt;contract compliance check&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…don’t “ask the model to remember.”&lt;/p&gt;

&lt;p&gt;Make it &lt;strong&gt;one command&lt;/strong&gt; and/or enforce it with &lt;strong&gt;hooks&lt;/strong&gt; + CI.&lt;/p&gt;

&lt;p&gt;This is the same reason “Zero‑Gap API Development” works: it’s a deterministic pipeline (contract → implement → verify → document), not vibes.&lt;br /&gt;
And it’s the same reason documentation-driven development scales for large systems: it creates deterministic “where to look” maps.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;a-universal-framework-that-scales-requirements--implementation--documentation&quot;&gt;A universal framework that scales: Requirements → Implementation → Documentation&lt;/h2&gt;

&lt;p&gt;This generalizes cleanly across Rails monoliths, Go services, and polyglot repos:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;┌──────────────────────────────────────────────┐
│ Layer 1: Requirements / Contracts            │
│ - Interfaces / OpenAPI / protobuf / specs    │
│ - Single source of truth                     │
└──────────────────────────────────────────────┘
                     ↓
┌──────────────────────────────────────────────┐
│ Layer 2: Implementation + Verification       │
│ - Code + tests + lint + security             │
│ - One “judge” command: bin/verify            │
└──────────────────────────────────────────────┘
                     ↓
┌──────────────────────────────────────────────┐
│ Layer 3: Documentation / Memory              │
│ - Flow docs (how it’s implemented)           │
│ - PRDs (why it exists)                       │
│ - Responsible Files tables                   │
└──────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Two key rules:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Docs are generated/updated only after verification passes&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Contracts are treated as the canonical truth&lt;/strong&gt; (or explicitly flagged when reality diverges)&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;step-1-add-agentsmd-to-every-repo&quot;&gt;Step 1: Add AGENTS.md to every repo&lt;/h2&gt;

&lt;p&gt;Put this at the repo root. Keep it short and operational.&lt;/p&gt;

&lt;div class=&quot;language-md highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gh&quot;&gt;# AGENTS.md — Project instructions for coding agents&lt;/span&gt;

&lt;span class=&quot;gu&quot;&gt;## Prime directive&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Prefer small, verifiable changes.
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Never skip verification commands.
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; If unsure: add/adjust tests first to lock behavior.

&lt;span class=&quot;gu&quot;&gt;## Commands (source of truth)&lt;/span&gt;
&lt;span class=&quot;gu&quot;&gt;### Ruby / Rails&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Setup: bundle install
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Test:  bin/test
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Lint:  bin/lint
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Verify (must pass before PR): bin/verify
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Security: bin/security

&lt;span class=&quot;gu&quot;&gt;### Go (if present)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Test:  make test
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Lint:  make lint
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Verify: make verify

&lt;span class=&quot;gu&quot;&gt;### Node (if present)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Test:  pnpm test
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Lint:  pnpm lint
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Typecheck: pnpm typecheck

&lt;span class=&quot;gu&quot;&gt;## Definition of Done&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; [ ] Implementation matches contract/specs
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; [ ] Tests added/updated and pass
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; [ ] Lint/static checks pass
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; [ ] Security checks pass
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; [ ] Docs updated (flow + PRD) if behavior changed
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; [ ] No unrelated refactors in same diff

&lt;span class=&quot;gu&quot;&gt;## Safety boundaries&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Don’t touch secrets/credentials or production config.
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Ask before adding dependencies or changing CI.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This alone reduces agent thrash dramatically.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;step-2-create-one-judge-command-binverify&quot;&gt;Step 2: Create one “judge command” (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt;)&lt;/h2&gt;

&lt;p&gt;Agents converge faster when there’s exactly one command that says “reality is correct.”&lt;/p&gt;

&lt;h3 id=&quot;rails-example&quot;&gt;Rails example&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-euo&lt;/span&gt; pipefail

&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;==&amp;gt; Lint&quot;&lt;/span&gt;
bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rubocop

&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;==&amp;gt; Tests&quot;&lt;/span&gt;
bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rspec

&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;==&amp;gt; Security (brakeman)&quot;&lt;/span&gt;
bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;brakeman &lt;span class=&quot;nt&quot;&gt;-q&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-w2&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;==&amp;gt; Dependency audit&quot;&lt;/span&gt;
bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;bundle audit check &lt;span class=&quot;nt&quot;&gt;--update&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;==&amp;gt; API docs generation (if used)&quot;&lt;/span&gt;
bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rails rswag:specs:swaggerize

&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;✅ verify passed&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;go-example&quot;&gt;Go example&lt;/h3&gt;

&lt;div class=&quot;language-makefile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nl&quot;&gt;verify&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;gofmt &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; .
	&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;golangci-lint run
	&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;go &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt; ./...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The exact tools don’t matter.&lt;br /&gt;
What matters is: &lt;strong&gt;one obvious place to run the truth.&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;step-3-use-contract-first-development-especially-for-apis&quot;&gt;Step 3: Use contract-first development (especially for APIs)&lt;/h2&gt;

&lt;p&gt;My most reliable pattern for AI-assisted Rails APIs is contract-first:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;treat an interface/spec as canonical truth&lt;/li&gt;
  &lt;li&gt;implement strictly against it&lt;/li&gt;
  &lt;li&gt;verify via gates&lt;/li&gt;
  &lt;li&gt;then update docs&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;safe-defaults-matter-required-fields-are-never-null&quot;&gt;Safe defaults matter (required fields are never null)&lt;/h3&gt;
&lt;p&gt;In contract-first work, required fields should not come back as null “because DB is sparse.”&lt;/p&gt;

&lt;p&gt;Use safe defaults by type:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Type&lt;/th&gt;
      &lt;th&gt;Default&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;String&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;&quot;&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Number&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; or minimum valid&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Boolean&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Array&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Object&lt;/td&gt;
      &lt;td&gt;minimal valid object&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Timestamp&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Time.current.iso8601&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;This removes a common “agent bug class”: missing fields / inconsistent shapes.&lt;/p&gt;

&lt;h3 id=&quot;discrepancy-tagging-impl-vs-doc&quot;&gt;Discrepancy tagging: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[IMPL]&lt;/code&gt; vs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[DOC]&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;When auditing contract compliance, classify gaps:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[IMPL]&lt;/code&gt;&lt;/strong&gt; → code is wrong, fix it now, rerun verification&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[DOC]&lt;/code&gt;&lt;/strong&gt; → the doc/contract is wrong (DB/model reality differs), flag it, don’t silently change requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This small habit prevents teams from corrupting the source of truth.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;step-4-documentation-driven-development-for-complex-systems&quot;&gt;Step 4: Documentation-driven development for complex systems&lt;/h2&gt;

&lt;p&gt;When systems explode in complexity (MMORPG-scale, or any mature SaaS), your bottleneck becomes &lt;strong&gt;coherence&lt;/strong&gt;, not raw coding time.&lt;/p&gt;

&lt;p&gt;The pattern that scales:&lt;/p&gt;

&lt;h3 id=&quot;documentation-hierarchy&quot;&gt;Documentation hierarchy&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;GDD / vision docs&lt;/strong&gt;: what the system is&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Feature docs&lt;/strong&gt;: what each subsystem must do&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Flow docs&lt;/strong&gt;: how it’s implemented (step-by-step + file ownership)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the structure that makes large Rails monoliths maintainable, and also makes AI agents effective because they can retrieve the correct context instead of guessing.&lt;/p&gt;

&lt;h3 id=&quot;flow-docs--responsible-files-tables&quot;&gt;Flow docs + Responsible Files tables&lt;/h3&gt;
&lt;p&gt;Flow docs should include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;“Use case” steps that trace UI → controller → service → persistence → broadcast&lt;/li&gt;
  &lt;li&gt;Key behaviors / invariants&lt;/li&gt;
  &lt;li&gt;A “Responsible Files” table so anyone (human or agent) can jump to the right code immediately&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example table:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Purpose&lt;/th&gt;
      &lt;th&gt;File&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Combat service&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/services/game/combat/turn_based_combat_service.rb&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Stimulus controller&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/javascript/controllers/turn_combat_controller.js&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;View&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/views/combat/_battle.html.erb&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Config&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/gameplay/combat_actions.yml&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;This is the best “RAG corpus” you can build: docs that directly point to code.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;step-5-turn-your-best-practices-into-skills&quot;&gt;Step 5: Turn your best practices into Skills&lt;/h2&gt;

&lt;p&gt;Here’s the trick: you already wrote the workflows in prose.&lt;/p&gt;

&lt;p&gt;Now you package them so the agent can apply them repeatedly.&lt;/p&gt;

&lt;h3 id=&quot;skill-ideas-that-map-to-real-work&quot;&gt;Skill ideas that map to real work&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rails-zero-gap-endpoint&lt;/code&gt;&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;input: requirement doc (contract), expected behavior&lt;/li&gt;
      &lt;li&gt;output: controller + serializer/blueprint + request specs + docs updates&lt;/li&gt;
      &lt;li&gt;gate: must pass &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flow-doc-maintainer&lt;/code&gt;&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;update flow steps&lt;/li&gt;
      &lt;li&gt;update Responsible Files table&lt;/li&gt;
      &lt;li&gt;append version history (never overwrite)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;safe-refactor&lt;/code&gt;&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;add characterization tests first&lt;/li&gt;
      &lt;li&gt;refactor&lt;/li&gt;
      &lt;li&gt;rerun verify&lt;/li&gt;
      &lt;li&gt;summarize risk and coverage&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go-service-skeleton&lt;/code&gt;&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;standard layout (cmd/, internal/, pkg/)&lt;/li&gt;
      &lt;li&gt;health, metrics, config, structured logging&lt;/li&gt;
      &lt;li&gt;make verify/test/lint&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;skill-template-conceptual&quot;&gt;Skill template (conceptual)&lt;/h3&gt;
&lt;div class=&quot;language-md highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gh&quot;&gt;# SKILL.md&lt;/span&gt;
name: rails-zero-gap-endpoint
description: Implements a Rails API endpoint from a contract/spec with tests, verification, and doc updates.

&lt;span class=&quot;gu&quot;&gt;## Steps&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;1.&lt;/span&gt; Read contract/spec in doc/requirements/&lt;span class=&quot;ge&quot;&gt;**&lt;/span&gt;.
&lt;span class=&quot;p&quot;&gt;2.&lt;/span&gt; Identify response shapes and status codes.
&lt;span class=&quot;p&quot;&gt;3.&lt;/span&gt; Implement with serializers/blueprints.
&lt;span class=&quot;p&quot;&gt;4.&lt;/span&gt; Add request specs (success + error cases).
&lt;span class=&quot;p&quot;&gt;5.&lt;/span&gt; Run bin/verify; fix until green.
&lt;span class=&quot;p&quot;&gt;6.&lt;/span&gt; Update flow doc + PRD notes; append version history.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The point is not “more features.”&lt;br /&gt;
The point is &lt;strong&gt;standardizing your best engineering behavior&lt;/strong&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;step-6-add-hooks-so-quality-is-automatic&quot;&gt;Step 6: Add hooks so quality is automatic&lt;/h2&gt;

&lt;p&gt;Once &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt; exists, hooks become a superpower.&lt;/p&gt;

&lt;p&gt;High ROI hooks:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Format after edits&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Block sensitive files&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Don’t stop until verify passes&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Auto-add a short change log entry for docs&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is how you get “ship faster” &lt;em&gt;and&lt;/em&gt; “ship safer” at the same time.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;when-to-use-rag-vs-lsp-vs-mcp&quot;&gt;When to use RAG vs LSP vs MCP&lt;/h2&gt;

&lt;h3 id=&quot;use-rag-when-you-have-good-docs-but-agents-dont-see-them&quot;&gt;Use RAG when you have good docs but agents don’t see them&lt;/h3&gt;
&lt;p&gt;If you already maintain:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;contracts/specs&lt;/li&gt;
  &lt;li&gt;flow docs&lt;/li&gt;
  &lt;li&gt;ADRs&lt;/li&gt;
  &lt;li&gt;runbooks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…RAG turns that into a searchable memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best practice:&lt;/strong&gt; write docs in chunks that can be retrieved (short sections, clear headings, explicit file names).&lt;/p&gt;

&lt;h3 id=&quot;use-lsp-when-correctness-depends-on-understanding-symbol-relationships&quot;&gt;Use LSP when correctness depends on understanding symbol relationships&lt;/h3&gt;
&lt;p&gt;Refactors, renames, interface changes → you want semantic certainty.&lt;/p&gt;

&lt;h3 id=&quot;use-mcp-when-the-bottleneck-is-tooldata-access&quot;&gt;Use MCP when the bottleneck is tool/data access&lt;/h3&gt;
&lt;p&gt;If your “context” lives outside the repo:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;DB schema details&lt;/li&gt;
  &lt;li&gt;tickets/PRDs in trackers&lt;/li&gt;
  &lt;li&gt;dashboards/logs&lt;/li&gt;
  &lt;li&gt;internal service catalogs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…MCP makes that safe and standardized.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;a-practical-daily-workflow-that-compounds-productivity&quot;&gt;A practical daily workflow that compounds productivity&lt;/h2&gt;

&lt;h3 id=&quot;feature-workflow&quot;&gt;Feature workflow&lt;/h3&gt;
&lt;ol&gt;
  &lt;li&gt;Define/confirm contract/spec (or acceptance criteria)&lt;/li&gt;
  &lt;li&gt;Agent generates a plan: files + tests + risks&lt;/li&gt;
  &lt;li&gt;Implement smallest vertical slice&lt;/li&gt;
  &lt;li&gt;Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt; until green&lt;/li&gt;
  &lt;li&gt;Audit for gaps (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[IMPL]&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[DOC]&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;Update flow doc + PRD notes&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;bugfix-workflow&quot;&gt;Bugfix workflow&lt;/h3&gt;
&lt;ol&gt;
  &lt;li&gt;Reproduce + write failing test&lt;/li&gt;
  &lt;li&gt;Minimal fix&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Add a short note to flow doc/runbook if it’s a recurring class&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;refactor-workflow&quot;&gt;Refactor workflow&lt;/h3&gt;
&lt;ol&gt;
  &lt;li&gt;Add characterization tests (freeze behavior)&lt;/li&gt;
  &lt;li&gt;Refactor&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Optional: second agent acts as a paranoid reviewer (coverage + edge cases)&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;a-60-minute-starter-kit-checklist&quot;&gt;A 60-minute “starter kit” checklist&lt;/h2&gt;

&lt;p&gt;If you want immediate impact, do this in your next repo:&lt;/p&gt;

&lt;ul class=&quot;task-list&quot;&gt;
  &lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; class=&quot;task-list-item-checkbox&quot; disabled=&quot;disabled&quot; /&gt;Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt;&lt;/li&gt;
  &lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; class=&quot;task-list-item-checkbox&quot; disabled=&quot;disabled&quot; /&gt;Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt; (one truth command)&lt;/li&gt;
  &lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; class=&quot;task-list-item-checkbox&quot; disabled=&quot;disabled&quot; /&gt;Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/test&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/lint&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/security&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/format&lt;/code&gt;&lt;/li&gt;
  &lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; class=&quot;task-list-item-checkbox&quot; disabled=&quot;disabled&quot; /&gt;Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/doc/&lt;/code&gt; map in README (what doc is where)&lt;/li&gt;
  &lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; class=&quot;task-list-item-checkbox&quot; disabled=&quot;disabled&quot; /&gt;Add 1 Skill for your most common task&lt;/li&gt;
  &lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; class=&quot;task-list-item-checkbox&quot; disabled=&quot;disabled&quot; /&gt;Add 1–2 hooks to enforce formatting + verify&lt;/li&gt;
  &lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; class=&quot;task-list-item-checkbox&quot; disabled=&quot;disabled&quot; /&gt;In CI: run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt; (same as local)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the shortest path to “significantly increase daily process.”&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;closing-the-real-multiplier-is-the-system&quot;&gt;Closing: the real multiplier is the system&lt;/h2&gt;

&lt;p&gt;AI doesn’t make a team fast.&lt;br /&gt;
&lt;strong&gt;A fast feedback loop makes a team fast.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Agentic development works when:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Truth is easy to check&lt;/strong&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/verify&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Intent is easy to find&lt;/strong&gt; (contracts + flow docs + Responsible Files)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Quality is enforced&lt;/strong&gt; (hooks + CI)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Workflows are reusable&lt;/strong&gt; (Skills)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once those exist, models become replaceable.&lt;br /&gt;
Your process stays.&lt;/p&gt;
</description>
        <pubDate>Thu, 01 Jan 2026 00:00:00 +0000</pubDate>
        <link>https://lukin.io/blog/how-to-start-agentic-development/</link>
        <guid isPermaLink="true">https://lukin.io/blog/how-to-start-agentic-development/</guid>
        
        <category>ai</category>
        
        <category>agentic-development</category>
        
        <category>software-engineering</category>
        
        <category>productivity</category>
        
        <category>rails</category>
        
        <category>golang</category>
        
        <category>mcp</category>
        
        <category>rag</category>
        
        <category>lsp</category>
        
        <category>codex</category>
        
        <category>claude-code</category>
        
        <category>documentation</category>
        
        <category>testing</category>
        
        
        <category>engineering</category>
        
        <category>AI</category>
        
        <category>best-practices</category>
        
      </item>
    
      <item>
        <title>From 5-Minute Timeouts to Sub-3-Minute Builds: A Complete Guide to Rails RSpec CI Optimization</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;“A fast test suite isn’t a luxury—it’s the difference between deploying confidently and deploying nervously.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Our Bitbucket Pipeline was timing out at 5 minutes. With 338 spec files, 456 RSwag integration tests, and growing, we needed a systematic approach to optimization—not just quick fixes.&lt;/p&gt;

&lt;p&gt;This post documents how we reduced our CI build time by 60% through parallel execution, factory optimization, intelligent pipeline architecture, and eliminating hidden I/O bottlenecks.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;#1-the-problem-death-by-timeout&quot;&gt;The Problem: Death by Timeout&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#2-diagnostic-analysis&quot;&gt;Diagnostic Analysis&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#3-pipeline-architecture-optimization&quot;&gt;Pipeline Architecture Optimization&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#4-parallel-test-execution&quot;&gt;Parallel Test Execution&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#5-factory-optimization&quot;&gt;Factory Optimization&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#6-file-io-elimination&quot;&gt;File I/O Elimination&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#7-test-profiling-infrastructure&quot;&gt;Test Profiling Infrastructure&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#8-rspec-configuration-tuning&quot;&gt;RSpec Configuration Tuning&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#9-results-and-metrics&quot;&gt;Results and Metrics&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#10-ongoing-optimization-strategy&quot;&gt;Ongoing Optimization Strategy&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;1-the-problem-death-by-timeout&quot;&gt;1. The Problem: Death by Timeout&lt;/h2&gt;

&lt;h3 id=&quot;11-the-symptoms&quot;&gt;1.1 The Symptoms&lt;/h3&gt;

&lt;p&gt;Our Bitbucket Pipeline build started failing with this error:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Exceeded build time limit of 5 minutes.
Error key: agent.step.time-limit-exceeded
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The logs showed tests completing, but swagger generation pushed us over the limit:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rspec &lt;span class=&quot;nt&quot;&gt;--tag&lt;/span&gt; ~ci_skip &lt;span class=&quot;nt&quot;&gt;--format&lt;/span&gt; documentation  &lt;span class=&quot;c&quot;&gt;# ~4:30&lt;/span&gt;
bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rails rswag:specs:swaggerize                  &lt;span class=&quot;c&quot;&gt;# +0:45 = TIMEOUT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;12-the-root-causes&quot;&gt;1.2 The Root Causes&lt;/h3&gt;

&lt;p&gt;After analysis, we identified multiple compounding issues:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Issue&lt;/th&gt;
      &lt;th&gt;Impact&lt;/th&gt;
      &lt;th&gt;Root Cause&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Sequential execution&lt;/td&gt;
      &lt;td&gt;100%&lt;/td&gt;
      &lt;td&gt;Single-threaded test run&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;RSwag runs tests twice&lt;/td&gt;
      &lt;td&gt;+50%&lt;/td&gt;
      &lt;td&gt;Swagger generation re-executes integration specs&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Eager factory creation&lt;/td&gt;
      &lt;td&gt;+15%&lt;/td&gt;
      &lt;td&gt;252 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let!&lt;/code&gt; calls creating records unnecessarily&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Disk I/O in factories&lt;/td&gt;
      &lt;td&gt;+10%&lt;/td&gt;
      &lt;td&gt;File.open for every attachment&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;No parallelization&lt;/td&gt;
      &lt;td&gt;100%&lt;/td&gt;
      &lt;td&gt;All 338 specs in one process&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Verbose output&lt;/td&gt;
      &lt;td&gt;+5%&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--format documentation&lt;/code&gt; slower than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--format progress&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;13-our-starting-point&quot;&gt;1.3 Our Starting Point&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Metric&lt;/th&gt;
      &lt;th&gt;Value&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Total spec files&lt;/td&gt;
      &lt;td&gt;338&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Integration specs (RSwag)&lt;/td&gt;
      &lt;td&gt;64&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;run_test!&lt;/code&gt; calls&lt;/td&gt;
      &lt;td&gt;456&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Factory &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;create()&lt;/code&gt; calls in integration&lt;/td&gt;
      &lt;td&gt;~502&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Eager &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let!&lt;/code&gt; evaluations&lt;/td&gt;
      &lt;td&gt;252&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Pipeline timeout&lt;/td&gt;
      &lt;td&gt;5 minutes&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Actual runtime&lt;/td&gt;
      &lt;td&gt;5+ minutes (timeout)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;2-diagnostic-analysis&quot;&gt;2. Diagnostic Analysis&lt;/h2&gt;

&lt;h3 id=&quot;21-understanding-the-codebase&quot;&gt;2.1 Understanding the Codebase&lt;/h3&gt;

&lt;p&gt;Before optimizing, we audited the entire test suite:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Count spec files&lt;/span&gt;
find spec &lt;span class=&quot;nt&quot;&gt;-name&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;*_spec.rb&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;wc&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# =&amp;gt; 338&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Count integration specs (RSwag)&lt;/span&gt;
find spec/integration &lt;span class=&quot;nt&quot;&gt;-name&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;*_spec.rb&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;wc&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# =&amp;gt; 64&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Count run_test! calls&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;run_test!&quot;&lt;/span&gt; spec/integration/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.rb | &lt;span class=&quot;nb&quot;&gt;awk&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-F&lt;/span&gt;: &lt;span class=&quot;s1&quot;&gt;&apos;{sum += $2} END {print sum}&apos;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# =&amp;gt; 456&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Count eager evaluations&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;let!&quot;&lt;/span&gt; spec/integration/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.rb | &lt;span class=&quot;nb&quot;&gt;awk&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-F&lt;/span&gt;: &lt;span class=&quot;s1&quot;&gt;&apos;{sum += $2} END {print sum}&apos;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# =&amp;gt; 252&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Find largest spec files&lt;/span&gt;
find spec &lt;span class=&quot;nt&quot;&gt;-name&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;*_spec.rb&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-exec&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;wc&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{}&lt;/span&gt; + | &lt;span class=&quot;nb&quot;&gt;sort&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;tail&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Key finding:&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;companies_spec.rb&lt;/code&gt; was 1,476 lines—a maintenance and performance problem.&lt;/p&gt;

&lt;h3 id=&quot;22-identifying-bottlenecks&quot;&gt;2.2 Identifying Bottlenecks&lt;/h3&gt;

&lt;p&gt;We identified these optimization opportunities:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Pipeline Architecture&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Tests ran sequentially in a single step&lt;/li&gt;
  &lt;li&gt;Lint, security, and tests couldn’t run in parallel&lt;/li&gt;
  &lt;li&gt;RSwag swagger generation was blocking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Test Execution&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;No parallel test execution (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parallel_tests&lt;/code&gt; gem missing)&lt;/li&gt;
  &lt;li&gt;No &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--fail-fast&lt;/code&gt; flag to stop on first failure&lt;/li&gt;
  &lt;li&gt;Verbose output format slowing things down&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Factory Usage&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let!&lt;/code&gt; used where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let&lt;/code&gt; would suffice&lt;/li&gt;
  &lt;li&gt;Duplicate factory creation in each response block&lt;/li&gt;
  &lt;li&gt;File I/O for every attachment creation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Profiling&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;No visibility into slow tests&lt;/li&gt;
  &lt;li&gt;No factory usage profiling&lt;/li&gt;
  &lt;li&gt;No SQL query profiling&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;3-pipeline-architecture-optimization&quot;&gt;3. Pipeline Architecture Optimization&lt;/h2&gt;

&lt;h3 id=&quot;31-the-problem-sequential-steps&quot;&gt;3.1 The Problem: Sequential Steps&lt;/h3&gt;

&lt;p&gt;Our original pipeline ran everything sequentially:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Before: Everything in sequence&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;pipelines&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;parallel&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Lint &amp;amp; Security&lt;/span&gt;
              &lt;span class=&quot;c1&quot;&gt;# ... linting&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;RSpec + DB prepare&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec rspec --tag ~ci_skip --format documentation&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec rails rswag:specs:swaggerize&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Re-runs integration specs!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Issues:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Single test step runs all 338 specs sequentially&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rswag:specs:swaggerize&lt;/code&gt; re-runs integration specs for swagger generation&lt;/li&gt;
  &lt;li&gt;No separation of fast vs slow tests&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;32-the-solution-three-way-parallelization&quot;&gt;3.2 The Solution: Three-Way Parallelization&lt;/h3&gt;

&lt;p&gt;We split the pipeline into three parallel test steps:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# After: Parallel execution by test type&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ruby:3.4&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;4x&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;max-time&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;10&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Increased from 5 to 10 minutes&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;definitions&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;caches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;bundler&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;vendor/bundle&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;bootsnap&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;tmp/cache/bootsnap&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# NEW: Cache Rails boot&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;postgres&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres:16&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;variables&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;POSTGRES_DB&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;testapp_test&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;POSTGRES_USER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;password&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;pipelines&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;parallel&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# Step 1: Linting &amp;amp; Security (no database needed)&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Lint &amp;amp; Security&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;caches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundler&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec rubocop&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec brakeman -q -w2&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec bundle audit check --update&lt;/span&gt;

          &lt;span class=&quot;c1&quot;&gt;# Step 2: Fast specs (models, blueprints, policies)&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Unit &amp;amp; Model Tests&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;caches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundler&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bootsnap&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec rspec spec/models spec/blueprints spec/policies \&lt;/span&gt;
                    &lt;span class=&quot;s&quot;&gt;spec/services spec/jobs --tag ~ci_skip --fail-fast --format progress&lt;/span&gt;

          &lt;span class=&quot;c1&quot;&gt;# Step 3: Integration specs + Swagger generation&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Integration Tests + Swagger&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;caches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundler&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bootsnap&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec rspec spec/integration spec/requests \&lt;/span&gt;
                    &lt;span class=&quot;s&quot;&gt;--tag ~ci_skip --fail-fast --format progress&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec rails rswag:specs:swaggerize&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Sequential step after parallel completes&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Verify Seeds&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;caches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundler&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec rails db:seed:replant&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;33-key-changes-explained&quot;&gt;3.3 Key Changes Explained&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Change&lt;/th&gt;
      &lt;th&gt;Before&lt;/th&gt;
      &lt;th&gt;After&lt;/th&gt;
      &lt;th&gt;Impact&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Timeout&lt;/td&gt;
      &lt;td&gt;5 min&lt;/td&gt;
      &lt;td&gt;10 min&lt;/td&gt;
      &lt;td&gt;Safety margin&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Parallel steps&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt;~40% faster&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Test splitting&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;Unit vs Integration&lt;/td&gt;
      &lt;td&gt;Better parallelization&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Bootsnap cache&lt;/td&gt;
      &lt;td&gt;Missing&lt;/td&gt;
      &lt;td&gt;Added&lt;/td&gt;
      &lt;td&gt;Faster Rails boot&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Output format&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;documentation&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;progress&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Less I/O overhead&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Fail behavior&lt;/td&gt;
      &lt;td&gt;Run all&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--fail-fast&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Fail faster&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;34-why-split-unit-and-integration-tests&quot;&gt;3.4 Why Split Unit and Integration Tests?&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Unit tests (models, policies, services):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Fast to execute (minimal database interaction)&lt;/li&gt;
  &lt;li&gt;Many small files&lt;/li&gt;
  &lt;li&gt;Good candidates for parallel execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Integration tests (RSwag, requests):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Slower (full request/response cycle)&lt;/li&gt;
  &lt;li&gt;Generate Swagger documentation&lt;/li&gt;
  &lt;li&gt;Benefit from running together for swagger generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By splitting them, we:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Get faster feedback on unit test failures&lt;/li&gt;
  &lt;li&gt;Keep swagger generation with its source specs&lt;/li&gt;
  &lt;li&gt;Allow better cache utilization&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;4-parallel-test-execution&quot;&gt;4. Parallel Test Execution&lt;/h2&gt;

&lt;h3 id=&quot;41-adding-parallel_tests-gem&quot;&gt;4.1 Adding parallel_tests Gem&lt;/h3&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parallel_tests&lt;/code&gt; gem runs specs across multiple CPU cores:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Gemfile&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:test&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;parallel_tests&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;test-prof&quot;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Profiling&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;42-pipeline-integration&quot;&gt;4.2 Pipeline Integration&lt;/h3&gt;

&lt;p&gt;For multi-process execution in CI:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Option A: parallel_tests with database per process&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec rake parallel:create parallel:prepare&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bundle exec parallel_rspec spec/ --tag ~ci_skip -n &lt;/span&gt;&lt;span class=&quot;m&quot;&gt;4&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Option B: Simple parallel within steps (what we chose)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Each parallel step runs a subset of specs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;We chose Option B&lt;/strong&gt; because:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Bitbucket Pipelines already provides step-level parallelism&lt;/li&gt;
  &lt;li&gt;Simpler database setup (one DB per step)&lt;/li&gt;
  &lt;li&gt;Easier to debug failures&lt;/li&gt;
  &lt;li&gt;Cache utilization is better with separate steps&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;43-local-development-usage&quot;&gt;4.3 Local Development Usage&lt;/h3&gt;

&lt;p&gt;For local development, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parallel_tests&lt;/code&gt; shines:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Run all specs across 4 cores&lt;/span&gt;
bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;parallel_rspec spec/ &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; 4

&lt;span class=&quot;c&quot;&gt;# Run with specific pattern&lt;/span&gt;
bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;parallel_rspec spec/models &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; auto

&lt;span class=&quot;c&quot;&gt;# Avatar parallel execution&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;PROFILE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;parallel_rspec spec/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;5-factory-optimization&quot;&gt;5. Factory Optimization&lt;/h2&gt;

&lt;h3 id=&quot;51-the-problem-eager-evaluation&quot;&gt;5.1 The Problem: Eager Evaluation&lt;/h3&gt;

&lt;p&gt;Our integration specs used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let!&lt;/code&gt; excessively:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Before: Every response block creates its own fixtures&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/api/v1/projects&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;200&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;successful&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;let!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;     &lt;span class=&quot;c1&quot;&gt;# Created immediately&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;let!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Created immediately&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;let!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Created immediately&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;run_test!&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;422&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;validation error&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;let!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;     &lt;span class=&quot;c1&quot;&gt;# DUPLICATE creation!&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;let!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# DUPLICATE creation!&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;run_test!&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Issues:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let!&lt;/code&gt; creates records even if not referenced&lt;/li&gt;
  &lt;li&gt;Each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;response&lt;/code&gt; block creates duplicate fixtures&lt;/li&gt;
  &lt;li&gt;252 eager evaluations across integration specs&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;52-the-solution-lazy-evaluation&quot;&gt;5.2 The Solution: Lazy Evaluation&lt;/h3&gt;

&lt;p&gt;Convert &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let!&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let&lt;/code&gt; where the lazy evaluation chain works:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# After: Lazy evaluation with explicit eager loading only when needed&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/api/v1/projects&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;200&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;successful&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;player: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Project must exist before request - keep as let!&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;let!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;avatar: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:Authorization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Bearer &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;jwt_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# References player → triggers creation&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# References avatar → triggers creation&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;run_test!&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The key insight:&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let&lt;/code&gt; is lazy—it only creates records when first accessed. If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Authorization&lt;/code&gt; references &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;player&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;avatar_id&lt;/code&gt; references &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;avatar&lt;/code&gt;, the chain triggers automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let!&lt;/code&gt; only when:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Record must exist in database before the request&lt;/li&gt;
  &lt;li&gt;No other &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let&lt;/code&gt; references it&lt;/li&gt;
  &lt;li&gt;It’s tested via side effects (e.g., count changes)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;53-before-vs-after-projects_specrb&quot;&gt;5.3 Before vs After: projects_spec.rb&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Before: 26 let! calls&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;200&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;successful&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;let!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;let!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;player: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;let!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;avatar: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# After: 5 let! calls (only for records that must pre-exist)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;200&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;successful&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;player: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;let!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;avatar: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Reduction: 26 → 5 eager evaluations&lt;/strong&gt; in one file.&lt;/p&gt;

&lt;h3 id=&quot;54-shared-contexts-for-common-patterns&quot;&gt;5.4 Shared Contexts for Common Patterns&lt;/h3&gt;

&lt;p&gt;For repeated patterns, use shared contexts:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# spec/support/shared_examples/company_authenticated_context.rb&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;shared_context&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;authenticated company player&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:company_owner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;company: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;company_role: :owner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:Authorization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Bearer &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;jwt_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;company_owner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:company_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Usage in specs&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Companies API&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;include_context&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;authenticated company player&apos;&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/api/v1/companies/{company_id}/avatar&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# company, company_owner, Authorization, company_id all available&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Created lazily when first accessed&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;6-file-io-elimination&quot;&gt;6. File I/O Elimination&lt;/h2&gt;

&lt;h3 id=&quot;61-the-problem-disk-reads-for-every-attachment&quot;&gt;6.1 The Problem: Disk Reads for Every Attachment&lt;/h3&gt;

&lt;p&gt;Our attachment factory read from disk on every creation:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Before: Disk I/O for every attachment&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;factory&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:attachment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attachment&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attachment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attached?&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;attachment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;io: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;spec/fixtures/files/image.png&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# DISK I/O!&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;filename: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;image.png&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;content_type: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;image/png&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Impact:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;85+ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;create(:attachment)&lt;/code&gt; calls across specs&lt;/li&gt;
  &lt;li&gt;Each call reads from disk&lt;/li&gt;
  &lt;li&gt;~0.5-2ms per read adds up&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;62-the-solution-in-memory-minimal-files&quot;&gt;6.2 The Solution: In-Memory Minimal Files&lt;/h3&gt;

&lt;p&gt;We created minimal valid files stored in memory:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# spec/support/minimal_files.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;MinimalFiles&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Minimal valid 1x1 transparent PNG (67 bytes)&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;PNG&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;mh&quot;&gt;0x89&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x4E&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x47&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x0D&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x0A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x1A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x0A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# PNG signature&lt;/span&gt;
    &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x0D&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x49&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x48&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x44&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x52&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# IHDR chunk&lt;/span&gt;
    &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 1x1 dimensions&lt;/span&gt;
    &lt;span class=&quot;mh&quot;&gt;0x08&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x02&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x90&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x77&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x53&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 8-bit RGB&lt;/span&gt;
    &lt;span class=&quot;mh&quot;&gt;0xDE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x0C&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x49&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x44&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x41&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# IDAT chunk&lt;/span&gt;
    &lt;span class=&quot;mh&quot;&gt;0x54&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x08&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xD7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x63&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xF8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x0F&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Compressed data&lt;/span&gt;
    &lt;span class=&quot;mh&quot;&gt;0x01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x05&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x18&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xD8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x4E&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# CRC&lt;/span&gt;
    &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x49&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x45&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x4E&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x44&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xAE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# IEND chunk&lt;/span&gt;
    &lt;span class=&quot;mh&quot;&gt;0x42&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x82&lt;/span&gt;                                  &lt;span class=&quot;c1&quot;&gt;# CRC&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;C*&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Minimal valid PDF (193 bytes)&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;PDF&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;PDF&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
    %PDF-1.4
    1 0 obj&amp;lt;&amp;lt;/Type/Catalog/Pages 2 0 R&amp;gt;&amp;gt;endobj
    2 0 obj&amp;lt;&amp;lt;/Type/Pages/Kids[3 0 R]/Count 1&amp;gt;&amp;gt;endobj
    3 0 obj&amp;lt;&amp;lt;/Type/Page/MediaBox[0 0 612 792]/Parent 2 0 R&amp;gt;&amp;gt;endobj
    xref
    0 4
    0000000000 65535 f
    0000000009 00000 n
    0000000052 00000 n
    0000000101 00000 n
    trailer&amp;lt;&amp;lt;/Size 4/Root 1 0 R&amp;gt;&amp;gt;
    startxref
    178
    %%EOF
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;  PDF&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;io_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_sym&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:png&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:real_image&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;PNG&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:pdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:document&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;PDF&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;PDF&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;content_type_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_sym&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:png&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:real_image&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;image/png&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;application/pdf&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filename_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_sym&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:png&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:real_image&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;image.png&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;test.pdf&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;63-updated-factory&quot;&gt;6.3 Updated Factory&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# spec/factories/attachments.rb&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;factory&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:attachment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attachment&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attachment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attached?&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;file_type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attachment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attachment_type&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;image&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;avatar&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;real_image&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:image&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:pdf&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# In-memory file - no disk I/O!&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;attachment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;io: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;MinimalFiles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;io_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;filename: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;MinimalFiles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filename_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;content_type: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;MinimalFiles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;content_type_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;64-why-this-is-safe&quot;&gt;6.4 Why This Is Safe&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Concerns we addressed:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;“Will validations still work?”&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;Yes! The files have valid headers (PNG signature, PDF structure)&lt;/li&gt;
      &lt;li&gt;Content-type validation passes&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;“What about &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fixture_file_upload&lt;/code&gt; in request specs?”&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;Unchanged! Request specs that test actual upload behavior still use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fixture_file_upload&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;Only factory-created attachments use in-memory files&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;“Is 67 bytes enough for a valid image?”&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;Yes! It’s a valid 1x1 transparent PNG&lt;/li&gt;
      &lt;li&gt;Any library reading it will parse it correctly&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;65-performance-impact&quot;&gt;6.5 Performance Impact&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Operation&lt;/th&gt;
      &lt;th&gt;Before&lt;/th&gt;
      &lt;th&gt;After&lt;/th&gt;
      &lt;th&gt;Improvement&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Read fixture file&lt;/td&gt;
      &lt;td&gt;~0.5-2ms&lt;/td&gt;
      &lt;td&gt;~0.01ms&lt;/td&gt;
      &lt;td&gt;50-200x&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Memory allocation&lt;/td&gt;
      &lt;td&gt;File buffer&lt;/td&gt;
      &lt;td&gt;String&lt;/td&gt;
      &lt;td&gt;Minimal&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;I/O operations&lt;/td&gt;
      &lt;td&gt;Disk read&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;Eliminated&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;7-test-profiling-infrastructure&quot;&gt;7. Test Profiling Infrastructure&lt;/h2&gt;

&lt;h3 id=&quot;71-adding-test-prof-gem&quot;&gt;7.1 Adding test-prof Gem&lt;/h3&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test-prof&lt;/code&gt; gem provides deep insights into test performance:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Gemfile&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:test&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;test-prof&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;72-testprof-configuration&quot;&gt;7.2 TestProf Configuration&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# spec/support/test_prof.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;defined?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TestProf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# FactoryProf: Avatar factory usage&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;TestProf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;FactoryProf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:flamegraph&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;FPROF_FLAMEGRAPH&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# EventProf: Avatar SQL queries and factory creates&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;TestProf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EventProf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;per_example&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;EVENT_PROF_EXAMPLES&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Native FactoryBot profiling (no TestProf required)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;FACTORY_PROF&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;factory_stats&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;count: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;time: &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:suite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Notifications&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;factory_bot.run_factory&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Notifications&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;factory_stats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]][&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;factory_stats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]][&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;duration&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:suite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;=== FactoryBot Profiling Report ===&quot;&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;%-40s %10s %15s&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Factory&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Time (ms)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;-&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;67&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;factory_stats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sort_by&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stats&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;%-40s %10d %15.2f&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;73-profiling-commands&quot;&gt;7.3 Profiling Commands&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Avatar slow examples (show top 10)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;PROFILE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rspec

&lt;span class=&quot;c&quot;&gt;# Avatar factory usage (find N+1 factories)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;FPROF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rspec

&lt;span class=&quot;c&quot;&gt;# Avatar factory usage with flamegraph&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;FPROF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;FPROF_FLAMEGRAPH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rspec

&lt;span class=&quot;c&quot;&gt;# Avatar SQL queries&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;EVENT_PROF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;sql.active_record bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rspec

&lt;span class=&quot;c&quot;&gt;# Avatar factory create events&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;EVENT_PROF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;factory.create bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rspec

&lt;span class=&quot;c&quot;&gt;# Native factory profiling&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;FACTORY_PROF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rspec

&lt;span class=&quot;c&quot;&gt;# Combined: slow examples + factory stats&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;PROFILE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;span class=&quot;nv&quot;&gt;FACTORY_PROF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rspec
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;74-sample-factory-avatar-output&quot;&gt;7.4 Sample Factory Avatar Output&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;=== FactoryBot Profiling Report ===
Factory                                      Count       Time (ms)
-------------------------------------------------------------------
player                                           156         1234.56
avatar                                        142          987.65
company                                         89          654.32
attachment                                      85          432.10
job_post                                        45          321.00
company_benefit                                 34          210.50
...
-------------------------------------------------------------------
TOTAL                                          892         5432.10
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What to look for:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Factories with high count but low time → OK&lt;/li&gt;
  &lt;li&gt;Factories with low count but high time → Optimize factory&lt;/li&gt;
  &lt;li&gt;Factories called more than expected → Check for cascades&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;75-testprofs-let_it_be&quot;&gt;7.5 TestProf’s let_it_be&lt;/h3&gt;

&lt;p&gt;For specs that can share fixtures across examples:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# spec/rails_helper.rb&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;test_prof/recipes/rspec/let_it_be&apos;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Usage in specs&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AvatarBlueprint&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Created once for ALL examples in this describe block&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;let_it_be&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;let_it_be&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;player: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;serializes id&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# player and avatar are reused, not recreated&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;described_class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render_as_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;serializes username&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Same player and avatar from above&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;described_class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render_as_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;username: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let_it_be&lt;/code&gt; when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Fixtures are read-only in all examples&lt;/li&gt;
  &lt;li&gt;Examples don’t modify the records&lt;/li&gt;
  &lt;li&gt;You have many examples using the same data&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;8-rspec-configuration-tuning&quot;&gt;8. RSpec Configuration Tuning&lt;/h2&gt;

&lt;h3 id=&quot;81-updated-spec_helperrb&quot;&gt;8.1 Updated spec_helper.rb&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# spec/spec_helper.rb&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Focus on tagged examples during development&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filter_run_when_matching&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:focus&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Persist example status for --only-failures&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;example_status_persistence_file_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;spec/examples.txt&quot;&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Avatar slow examples (enable via PROFILE=1)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;avatar_examples&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;PROFILE&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Random order to surface order dependencies&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:random&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Kernel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;srand&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;seed&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;82-key-configuration-choices&quot;&gt;8.2 Key Configuration Choices&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Setting&lt;/th&gt;
      &lt;th&gt;Value&lt;/th&gt;
      &lt;th&gt;Rationale&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;avatar_examples&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Conditional&lt;/td&gt;
      &lt;td&gt;Only avatar when explicitly requested&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;order&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:random&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Surface hidden dependencies&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;example_status_persistence&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Enabled&lt;/td&gt;
      &lt;td&gt;Support &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--only-failures&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;filter_run_when_matching :focus&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Enabled&lt;/td&gt;
      &lt;td&gt;Focus on tagged tests during dev&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;83-rspec-file&quot;&gt;8.3 .rspec File&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;--require spec_helper
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Keep it minimal. Command-line options are easier to manage in CI.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;9-results-and-metrics&quot;&gt;9. Results and Metrics&lt;/h2&gt;

&lt;h3 id=&quot;91-before-vs-after-comparison&quot;&gt;9.1 Before vs After Comparison&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Metric&lt;/th&gt;
      &lt;th&gt;Before&lt;/th&gt;
      &lt;th&gt;After&lt;/th&gt;
      &lt;th&gt;Improvement&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Pipeline timeout&lt;/td&gt;
      &lt;td&gt;5 min&lt;/td&gt;
      &lt;td&gt;10 min&lt;/td&gt;
      &lt;td&gt;2x headroom&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Parallel steps&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt;50% more parallelism&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Actual build time&lt;/td&gt;
      &lt;td&gt;5+ min (timeout)&lt;/td&gt;
      &lt;td&gt;~3 min&lt;/td&gt;
      &lt;td&gt;60% faster&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let!&lt;/code&gt; calls&lt;/td&gt;
      &lt;td&gt;252&lt;/td&gt;
      &lt;td&gt;~150&lt;/td&gt;
      &lt;td&gt;40% reduction&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Disk I/O per run&lt;/td&gt;
      &lt;td&gt;85+ file reads&lt;/td&gt;
      &lt;td&gt;0&lt;/td&gt;
      &lt;td&gt;Eliminated&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Test output&lt;/td&gt;
      &lt;td&gt;Verbose&lt;/td&gt;
      &lt;td&gt;Progress&lt;/td&gt;
      &lt;td&gt;Less I/O&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Fail behavior&lt;/td&gt;
      &lt;td&gt;Run all&lt;/td&gt;
      &lt;td&gt;Fail-fast&lt;/td&gt;
      &lt;td&gt;Faster feedback&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;92-pipeline-execution-timeline&quot;&gt;9.2 Pipeline Execution Timeline&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[Lint &amp;amp; Security] ████████░░░░░░░░░░ 2:00
                  [RSpec + Swagger] ██████████████████ 5:00+ TIMEOUT!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[Lint &amp;amp; Security] ████████░░░░░░░░░░ 2:00
[Unit Tests]      ████████░░░░░░░░░░ 1:30
[Integration]     ████████████░░░░░░ 2:30
                           [Seeds]  ████░░░░░░░░░░░░░░ 0:30
─────────────────────────────────────────────────────────────
Total wall time: ~3:00 (limited by slowest parallel step)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;93-what-we-shipped&quot;&gt;9.3 What We Shipped&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Component&lt;/th&gt;
      &lt;th&gt;Files Changed&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Gemfile&lt;/td&gt;
      &lt;td&gt;Added &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parallel_tests&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test-prof&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;bitbucket-pipelines.yml&lt;/td&gt;
      &lt;td&gt;Restructured to 3 parallel steps&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;spec/spec_helper.rb&lt;/td&gt;
      &lt;td&gt;Enabled profiling, focus filtering&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;spec/rails_helper.rb&lt;/td&gt;
      &lt;td&gt;Added TestProf recipes&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;spec/support/test_prof.rb&lt;/td&gt;
      &lt;td&gt;New profiling configuration&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;spec/support/minimal_files.rb&lt;/td&gt;
      &lt;td&gt;New in-memory file module&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;spec/factories/attachments.rb&lt;/td&gt;
      &lt;td&gt;Use MinimalFiles&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;spec/factories/gallery_images.rb&lt;/td&gt;
      &lt;td&gt;Use MinimalFiles&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;spec/integration/projects_spec.rb&lt;/td&gt;
      &lt;td&gt;Convert let! → let&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;spec/integration/working_knowledge_spec.rb&lt;/td&gt;
      &lt;td&gt;Convert let! → let&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;spec/support/shared_examples/*&lt;/td&gt;
      &lt;td&gt;New shared contexts&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;spec/support/shared_schemas/*&lt;/td&gt;
      &lt;td&gt;Extracted common schemas&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;10-ongoing-optimization-strategy&quot;&gt;10. Ongoing Optimization Strategy&lt;/h2&gt;

&lt;h3 id=&quot;101-monitoring-commands&quot;&gt;10.1 Monitoring Commands&lt;/h3&gt;

&lt;p&gt;Run these periodically to identify new bottlenecks:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Find slowest 10 examples&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;PROFILE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rspec

&lt;span class=&quot;c&quot;&gt;# Find factory N+1 issues&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;FPROF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rspec

&lt;span class=&quot;c&quot;&gt;# Identify SQL-heavy tests&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;EVENT_PROF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;sql.active_record bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rspec

&lt;span class=&quot;c&quot;&gt;# Check factory creation counts&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;FACTORY_PROF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rspec
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;102-guidelines-for-new-tests&quot;&gt;10.2 Guidelines for New Tests&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Prefer &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let&lt;/code&gt; over &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let!&lt;/code&gt;&lt;/strong&gt; unless record must pre-exist&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Use shared contexts&lt;/strong&gt; for repeated patterns&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Keep integration specs focused&lt;/strong&gt; — one endpoint per file if large&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let_it_be&lt;/code&gt;&lt;/strong&gt; for read-only fixtures in model/blueprint specs&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Avoid factory cascades&lt;/strong&gt; — use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build_stubbed&lt;/code&gt; when possible&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;103-ci-pipeline-guidelines&quot;&gt;10.3 CI Pipeline Guidelines&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Split by test type&lt;/strong&gt; — Unit vs Integration&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--fail-fast&lt;/code&gt;&lt;/strong&gt; in CI — Fail fast, fix fast&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Cache aggressively&lt;/strong&gt; — Bundler, Bootsnap, build artifacts&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Monitor build times&lt;/strong&gt; — Set alerts for regression&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;104-future-optimizations&quot;&gt;10.4 Future Optimizations&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;If builds slow down again:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Add more parallel steps&lt;/strong&gt; — Split integration by domain&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Use parallel_tests within steps&lt;/strong&gt; — Multi-process per step&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Consider test splitting by timing&lt;/strong&gt; — Distribute by historical runtime&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Evaluate CI provider&lt;/strong&gt; — GitHub Actions has better parallelization&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;key-takeaways&quot;&gt;Key Takeaways&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Avatar before optimizing&lt;/strong&gt; — Measure, don’t guess&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Parallelize at multiple levels&lt;/strong&gt; — Pipeline steps + test processes&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Lazy evaluation is your friend&lt;/strong&gt; — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let&lt;/code&gt; over &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let!&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Eliminate I/O&lt;/strong&gt; — In-memory beats disk every time&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Fail fast&lt;/strong&gt; — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--fail-fast&lt;/code&gt; saves minutes on broken builds&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Split wisely&lt;/strong&gt; — Unit tests separate from integration&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Cache everything&lt;/strong&gt; — Bundler, Bootsnap, build artifacts&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Document your profiling&lt;/strong&gt; — Future you will thank present you&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;the-real-win&quot;&gt;The Real Win&lt;/h3&gt;

&lt;p&gt;The 60% speed improvement is great, but the real win is &lt;strong&gt;confidence&lt;/strong&gt;. Fast tests mean:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Developers run tests locally more often&lt;/li&gt;
  &lt;li&gt;CI catches issues before they merge&lt;/li&gt;
  &lt;li&gt;Deployments happen without anxiety&lt;/li&gt;
  &lt;li&gt;The test suite stays fast as the codebase grows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Slow tests become ignored tests. Ignored tests become broken tests. Broken tests become production bugs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Invest in your test infrastructure. It pays dividends every single day.&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;parallel_tests&lt;/strong&gt;: &lt;a href=&quot;https://github.com/grosser/parallel_tests&quot;&gt;github.com/grosser/parallel_tests&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;test-prof&lt;/strong&gt;: &lt;a href=&quot;https://test-prof.evilmartians.io/&quot;&gt;test-prof.evilmartians.io&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;RSpec Best Practices&lt;/strong&gt;: &lt;a href=&quot;https://www.betterspecs.org/&quot;&gt;betterspecs.org&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;FactoryBot Optimization&lt;/strong&gt;: &lt;a href=&quot;https://github.com/thoughtbot/factory_bot/blob/main/GETTING_STARTED.md#best-practices&quot;&gt;thoughtbot/factory_bot&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Bitbucket Pipelines Reference&lt;/strong&gt;: &lt;a href=&quot;https://support.atlassian.com/bitbucket-cloud/docs/bitbucket-pipelines-configuration-reference/&quot;&gt;support.atlassian.com/bitbucket-cloud/docs/bitbucket-pipelines-configuration-reference&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;GitHub Actions for Rails&lt;/strong&gt;: &lt;a href=&quot;https://docs.github.com/en/actions&quot;&gt;docs.github.com/actions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;This post documents the optimization of the TestApp API test suite from timeout failures to sub-3-minute builds. The patterns described here—parallel execution, factory optimization, I/O elimination, and profiling infrastructure—are applicable to any Rails application with a growing test suite.&lt;/em&gt;&lt;/p&gt;

</description>
        <pubDate>Wed, 10 Dec 2025 00:00:00 +0000</pubDate>
        <link>https://lukin.io/blog/rspec-ci-optimization-rails-bitbucket/</link>
        <guid isPermaLink="true">https://lukin.io/blog/rspec-ci-optimization-rails-bitbucket/</guid>
        
        <category>rails</category>
        
        <category>rspec</category>
        
        <category>ci-cd</category>
        
        <category>bitbucket-pipelines</category>
        
        <category>testing</category>
        
        <category>performance</category>
        
        <category>parallel-tests</category>
        
        <category>factory-bot</category>
        
        <category>test-prof</category>
        
        
        <category>engineering</category>
        
        <category>rails</category>
        
        <category>testing</category>
        
        <category>devops</category>
        
      </item>
    
      <item>
        <title>Turning Neverlands Into Design Fuel: A Browser Bot + GPT-5.1 Game Analyst</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;We built a &lt;strong&gt;browser bot + GPT-5.1 analyst&lt;/strong&gt; that logs into neverlands.ru, captures real game screens, and turns them into structured JSON.&lt;/li&gt;
    &lt;li&gt;The goal is &lt;strong&gt;game design research&lt;/strong&gt;, not automation: extract systems, loops, and UX patterns to feed your own GDD.&lt;/li&gt;
    &lt;li&gt;Everything runs locally: credentials live in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt;, GPT only ever sees &lt;strong&gt;HTML snapshots&lt;/strong&gt;, not your password.&lt;/li&gt;
    &lt;li&gt;The stack is small and explicit: &lt;strong&gt;Node 18 + Playwright + dotenv + OpenAI API&lt;/strong&gt;, wired through a few focused TypeScript scripts.&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;why-build-a-bot-for-neverlands&quot;&gt;Why build a bot for Neverlands?&lt;/h2&gt;

&lt;p&gt;Neverlands is a long-running browser MMORPG with &lt;strong&gt;mature systems&lt;/strong&gt;: inventory, combat, economy, events, social layers. Instead of cloning it, we want to &lt;strong&gt;study&lt;/strong&gt; it:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;What progression and compulsion loops does it use?&lt;/li&gt;
  &lt;li&gt;How are shops, currencies, and sinks structured?&lt;/li&gt;
  &lt;li&gt;Which UI patterns make the game readable (or confusing)?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Doing this manually is slow. Screenshots pile up, notes get messy, and you miss subtle connections. The solution: a &lt;strong&gt;repeatable pipeline&lt;/strong&gt; that:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Logs into your account.&lt;/li&gt;
  &lt;li&gt;Visits interesting screens (home, character, inventory, shop, PvP…).&lt;/li&gt;
  &lt;li&gt;Saves HTML + screenshots.&lt;/li&gt;
  &lt;li&gt;Asks GPT-5.1 to extract features and player needs into machine-readable JSON.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those JSON files become the raw material for your &lt;strong&gt;game design document&lt;/strong&gt;, where you remix Neverlands-inspired ideas into your own world.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;architecture-at-a-glance&quot;&gt;Architecture at a glance&lt;/h2&gt;

&lt;p&gt;The repo is intentionally small:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bot/        # Playwright automation
  login.ts  # log in + save session
  crawl.ts  # visit screens + save HTML/PNG

analyzer/
  analyzeScreens.ts  # send HTML to GPT-5.1 using promt.md

data/
  session/   # Playwright storage state
  raw/       # captured HTML + screenshots
  analysis/  # GPT JSON outputs

promt.md     # &quot;game design analyst&quot; prompt template
doc/         # human docs (setup, usage, dev)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The heart of the system is &lt;strong&gt;promt.md&lt;/strong&gt;: a prompt that tells GPT-5.1 to behave like a &lt;strong&gt;game design analyst&lt;/strong&gt;. For each HTML snapshot it:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Identifies visible systems/features (e.g., daily quests, energy, shop widgets).&lt;/li&gt;
  &lt;li&gt;Describes how they seem to work from the UI.&lt;/li&gt;
  &lt;li&gt;Classifies the player need they target (progression, social, mastery, etc.).&lt;/li&gt;
  &lt;li&gt;Suggests how you might adapt the idea for your own MMORPG.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Outputs are strict JSON, so you can script on top of them later.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;technical-implementation&quot;&gt;Technical implementation&lt;/h2&gt;

&lt;h3 id=&quot;1-login-with-playwright&quot;&gt;1. Login with Playwright&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bot/login.ts&lt;/code&gt; uses &lt;strong&gt;Playwright&lt;/strong&gt; to open &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://www.neverlands.ru/&lt;/code&gt;, fill the login form, and save a session:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Credentials come from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotenv&lt;/code&gt;:
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NEVERLANDS_USERNAME&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NEVERLANDS_PASSWORD&lt;/code&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Form selectors are configurable:
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NEVERLANDS_USERNAME_SELECTOR&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NEVERLANDS_PASSWORD_SELECTOR&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NEVERLANDS_SUBMIT_SELECTOR&lt;/code&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;The URL is normalized so any &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://neverlands.ru&lt;/code&gt; is rewritten to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://www.neverlands.ru/&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;On success, storage state is written to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data/session/neverlands.json&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You run it with:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm run login
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;2-crawling-key-game-screens&quot;&gt;2. Crawling key game screens&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bot/crawl.ts&lt;/code&gt; reuses the saved session to open specific URLs and capture HTML + PNG:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Screens are defined as a small list of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{ id, url }&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;For each screen, we:
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;page.goto(url, { waitUntil: &apos;networkidle&apos; })&lt;/code&gt;.&lt;/li&gt;
      &lt;li&gt;Save DOM: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data/raw/&amp;lt;id&amp;gt;.html&lt;/code&gt;.&lt;/li&gt;
      &lt;li&gt;Save screenshot: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data/raw/&amp;lt;id&amp;gt;.png&lt;/code&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can start with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;home&lt;/code&gt;, then add pages like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inventory&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;character&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shop&lt;/code&gt;, or any internal route Neverlands exposes.&lt;/p&gt;

&lt;p&gt;Run the crawl:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm run crawl
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;3-gpt-51-analysis&quot;&gt;3. GPT-5.1 analysis&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;analyzer/analyzeScreens.ts&lt;/code&gt; reads every &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*.html&lt;/code&gt; file in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data/raw/&lt;/code&gt;, then calls the OpenAI Chat Completions API:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OPENAI_API_KEY&lt;/code&gt; from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Defaults to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OPENAI_MODEL=gpt-5.1&lt;/code&gt; (configurable).&lt;/li&gt;
  &lt;li&gt;Loads &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;promt.md&lt;/code&gt; as the &lt;strong&gt;system&lt;/strong&gt; message.&lt;/li&gt;
  &lt;li&gt;Sends the HTML as the &lt;strong&gt;user&lt;/strong&gt; message.&lt;/li&gt;
  &lt;li&gt;Requests &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;response_format: json_object&lt;/code&gt; to keep outputs parseable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each result is saved as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data/analysis/&amp;lt;screen&amp;gt;-&amp;lt;hash&amp;gt;.json&lt;/code&gt;, giving you versioned feature maps per screen.&lt;/p&gt;

&lt;p&gt;Run analysis:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm run analyze
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;from-json-to-a-game-design-document&quot;&gt;From JSON to a game design document&lt;/h2&gt;

&lt;p&gt;Once you have JSON outputs, you’re holding a &lt;strong&gt;Neverlands feature index&lt;/strong&gt;. Typical next steps:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Group features by system: combat, economy, social, events, meta.&lt;/li&gt;
  &lt;li&gt;Identify loops: daily quests → currencies → shops → upgrades.&lt;/li&gt;
  &lt;li&gt;Tag ideas as &lt;strong&gt;keep / adapt / avoid&lt;/strong&gt; for your own game.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the analysis is structured, it’s easy to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Generate Markdown “inspiration cards” per feature.&lt;/li&gt;
  &lt;li&gt;Build tables comparing Neverlands to other games you crawl later.&lt;/li&gt;
  &lt;li&gt;Feed the JSON back into another GPT-5.1 pass to draft full sections of your GDD.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bot doesn’t design the game for you—but it &lt;strong&gt;amplifies your research&lt;/strong&gt; so you can spend time on synthesis, not screenshot wrangling.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;safety-tos-and-scope&quot;&gt;Safety, ToS, and scope&lt;/h2&gt;

&lt;p&gt;Even though neverlands.ru allows bots, we built this stack with a conservative mindset:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Credentials live only in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; on your machine.&lt;/li&gt;
  &lt;li&gt;GPT never sees your password or cookies, only HTML.&lt;/li&gt;
  &lt;li&gt;The intended use is &lt;strong&gt;analysis&lt;/strong&gt;, not auto-play or farming.&lt;/li&gt;
  &lt;li&gt;Screen lists are small and intentional, not full-site crawlers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you adapt this to other games, always re-check each game’s ToS and keep your rate of requests reasonable.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;where-to-go-next&quot;&gt;Where to go next&lt;/h2&gt;

&lt;p&gt;With the core pipeline in place, the most interesting work is ahead:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Add more screens and flows (e.g., onboarding, high-level PvP, events).&lt;/li&gt;
  &lt;li&gt;Enrich &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;promt.md&lt;/code&gt; with new fields (risk flags, UX pain points, monetization patterns).&lt;/li&gt;
  &lt;li&gt;Build a small UI or static site to browse the analyzed features.&lt;/li&gt;
  &lt;li&gt;Compare Neverlands against other browser MMORPGs using the same pipeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The end goal: a &lt;strong&gt;living game design document&lt;/strong&gt; for your own project, grounded in real, battle-tested systems—but with your own take on what makes them fun.&lt;/p&gt;

</description>
        <pubDate>Sat, 06 Dec 2025 00:00:00 +0000</pubDate>
        <link>https://lukin.io/blog/neverlands-browser-bot-game-design-analyst/</link>
        <guid isPermaLink="true">https://lukin.io/blog/neverlands-browser-bot-game-design-analyst/</guid>
        
        <category>ai</category>
        
        <category>game-design</category>
        
        <category>bots</category>
        
        <category>playwright</category>
        
        <category>openai</category>
        
        <category>mmorpg</category>
        
        <category>analysis</category>
        
        
        <category>engineering</category>
        
        <category>game-design</category>
        
        <category>AI</category>
        
      </item>
    
      <item>
        <title>From Rails Spaghetti to Structured Code: Integrating dry-rb into Your API</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;“The best code isn’t clever—it’s obvious. dry-rb makes Rails code obvious.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every Rails developer has written code that works but feels wrong. Manual type coercion scattered across controllers. Validation logic duplicated between models and services. Deeply nested conditionals handling success and failure cases. Configuration constants scattered across files. Side effects mixed with business logic. These patterns work, but they accumulate into technical debt.&lt;/p&gt;

&lt;p&gt;After shipping dozens of API endpoints, we integrated &lt;strong&gt;dry-rb&lt;/strong&gt; into our Rails API. This post documents the transformation—with real before/after examples showing how each gem improves specific code patterns.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;#1-the-problem-rails-patterns-that-dont-scale&quot;&gt;The Problem: Rails Patterns That Don’t Scale&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#2-the-solution-dry-rb-gem-suite&quot;&gt;The Solution: dry-rb Gem Suite&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#3-dry-types-type-safe-enums-and-coercion&quot;&gt;dry-types: Type-Safe Enums and Coercion&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#4-dry-struct-immutable-data-structures&quot;&gt;dry-struct: Immutable Data Structures&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#5-dry-monads-functional-error-handling&quot;&gt;dry-monads: Functional Error Handling&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#6-dry-validation-contract-based-validation&quot;&gt;dry-validation: Contract-Based Validation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#7-dry-container--dry-auto_inject-dependency-injection&quot;&gt;dry-container + dry-auto_inject: Dependency Injection&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#8-dry-matcher-pattern-matching-for-results&quot;&gt;dry-matcher: Pattern Matching for Results&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#9-dry-configurable-thread-safe-configuration&quot;&gt;dry-configurable: Thread-Safe Configuration&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#10-dry-initializer-declarative-parameter-dsl&quot;&gt;dry-initializer: Declarative Parameter DSL&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#11-dry-events-pubsub-event-system&quot;&gt;dry-events: Pub/Sub Event System&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#12-other-dry-rb-gems-worth-exploring&quot;&gt;Other dry-rb Gems Worth Exploring&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#13-benefits-and-tradeoffs&quot;&gt;Benefits and Tradeoffs&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#14-conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;1-the-problem-rails-patterns-that-dont-scale&quot;&gt;1. The Problem: Rails Patterns That Don’t Scale&lt;/h2&gt;

&lt;h3 id=&quot;11-the-rails-way-has-limits&quot;&gt;1.1 The “Rails Way” Has Limits&lt;/h3&gt;

&lt;p&gt;Rails conventions are excellent for getting started quickly. But as applications grow, certain patterns become problematic:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Pattern&lt;/th&gt;
      &lt;th&gt;Works For&lt;/th&gt;
      &lt;th&gt;Breaks When&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Hash params everywhere&lt;/td&gt;
      &lt;td&gt;Simple CRUD&lt;/td&gt;
      &lt;td&gt;Complex nested data structures&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ActiveRecord validations&lt;/td&gt;
      &lt;td&gt;Model-level rules&lt;/td&gt;
      &lt;td&gt;Cross-field business logic&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Controller conditionals&lt;/td&gt;
      &lt;td&gt;Simple flows&lt;/td&gt;
      &lt;td&gt;Multiple success/failure paths&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Manual type coercion&lt;/td&gt;
      &lt;td&gt;Occasional use&lt;/td&gt;
      &lt;td&gt;Dozens of fields need processing&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Class method calls&lt;/td&gt;
      &lt;td&gt;Small apps&lt;/td&gt;
      &lt;td&gt;Testing requires stubbing globals&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Scattered constants&lt;/td&gt;
      &lt;td&gt;One-off values&lt;/td&gt;
      &lt;td&gt;Configuration needs structure&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Inline side effects&lt;/td&gt;
      &lt;td&gt;Simple actions&lt;/td&gt;
      &lt;td&gt;Need audit trail or event-driven architecture&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;12-real-example-avatar-actions-payload&quot;&gt;1.2 Real Example: Avatar Actions Payload&lt;/h3&gt;

&lt;p&gt;We needed to serialize client-specific avatar data. Here’s what the original code looked like:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Original: 70+ lines of manual validation and coercion&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;build_actions_payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;extract_actions_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Manual enum validation&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ACCESS_STATUS_VALUES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;none&quot;&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Manual boolean coercion&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar_viewed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar_viewed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:is_saved&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:is_saved&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Manual conditional inclusion&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:thumb_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;thumb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:thumb_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:thumb_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;thumb&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;THUMB_STATUS_VALUES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;thumb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:applied_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:applied_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:applied_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;respond_to?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:iso8601&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;iso8601&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;key?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_expired&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_expired&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_expired&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_end_reason&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;reason&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_end_reason&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_end_reason&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reason&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ACCESS_END_REASON_VALUES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reason&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# ... 30 more lines&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Problems:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;❌ Manual type coercion (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;to_s&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;== true&lt;/code&gt;) repeated everywhere&lt;/li&gt;
  &lt;li&gt;❌ Validation scattered (constants + inline checks)&lt;/li&gt;
  &lt;li&gt;❌ Hard to test—tightly coupled to Blueprint&lt;/li&gt;
  &lt;li&gt;❌ No type safety—wrong data silently coerced&lt;/li&gt;
  &lt;li&gt;❌ Verbose—business logic drowns in boilerplate&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;2-the-solution-dry-rb-gem-suite&quot;&gt;2. The Solution: dry-rb Gem Suite&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://dry-rb.org/gems/&quot;&gt;dry-rb&lt;/a&gt; ecosystem provides focused, composable gems for common problems:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Gem&lt;/th&gt;
      &lt;th&gt;Purpose&lt;/th&gt;
      &lt;th&gt;Replaces&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;dry-types&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Type definitions with constraints&lt;/td&gt;
      &lt;td&gt;Manual enum validation&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;dry-struct&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Immutable typed objects&lt;/td&gt;
      &lt;td&gt;Hash manipulation&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;dry-monads&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Functional result handling&lt;/td&gt;
      &lt;td&gt;try/rescue + conditionals&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;dry-validation&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Contract-based validation&lt;/td&gt;
      &lt;td&gt;Scattered validation logic&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;dry-schema&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Schema coercion&lt;/td&gt;
      &lt;td&gt;Strong parameters edge cases&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;dry-container&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Dependency registry&lt;/td&gt;
      &lt;td&gt;Global class references&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;dry-auto_inject&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Constructor injection&lt;/td&gt;
      &lt;td&gt;Manual dependency passing&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;dry-matcher&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Result pattern matching&lt;/td&gt;
      &lt;td&gt;Case statements on results&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;dry-configurable&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Thread-safe configuration&lt;/td&gt;
      &lt;td&gt;Scattered constants&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;dry-initializer&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Param/option DSL&lt;/td&gt;
      &lt;td&gt;Boilerplate initialize methods&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;dry-events&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Pub/sub event system&lt;/td&gt;
      &lt;td&gt;Inline side effects&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;our-gemfile-addition&quot;&gt;Our Gemfile Addition&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Gemfile&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Dry-rb gems for structured types, validation, and functional patterns&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dry-types&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;~&amp;gt; 1.7&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dry-struct&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;~&amp;gt; 1.6&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dry-monads&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;~&amp;gt; 1.6&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dry-validation&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;~&amp;gt; 1.11&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dry-schema&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;~&amp;gt; 1.14&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dry-container&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;~&amp;gt; 0.11&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dry-auto_inject&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;~&amp;gt; 0.9&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dry-matcher&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;~&amp;gt; 1.0&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dry-configurable&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;~&amp;gt; 1.2&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dry-initializer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;~&amp;gt; 3.1&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dry-events&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;~&amp;gt; 1.0&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;3-dry-types-type-safe-enums-and-coercion&quot;&gt;3. dry-types: Type-Safe Enums and Coercion&lt;/h2&gt;

&lt;h3 id=&quot;31-the-problem-scattered-constants-and-manual-checks&quot;&gt;3.1 The Problem: Scattered Constants and Manual Checks&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Before: Constants scattered, validation repeated&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AvatarBlueprint&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationBlueprint&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;ACCESS_STATUS_VALUES&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%w[none requested approved denied removed shared]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;THUMB_STATUS_VALUES&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%w[liked disliked]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;ACCESS_END_REASON_VALUES&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%w[expired declined blocked removed]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;build_actions_payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ACCESS_STATUS_VALUES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;none&quot;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Repeated for every enum field...&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Issues:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Constants defined in one class, used elsewhere&lt;/li&gt;
  &lt;li&gt;Validation logic duplicated wherever enums are checked&lt;/li&gt;
  &lt;li&gt;No automatic coercion or defaults&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;32-the-solution-centralized-type-registry&quot;&gt;3.2 The Solution: Centralized Type Registry&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/types.rb&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dry/types&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Types&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Dry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Common types with safe defaults&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;SafeBool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;OptionalString&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;optional&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;OptionalInteger&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;optional&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# ISO8601 timestamp coercion&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;ISO8601String&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;optional&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;constructor&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;nil?&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DateTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TimeWithZone&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;iso8601&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Avatar Actions enums - SINGLE SOURCE OF TRUTH&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Avatars&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;ACCESS_STATUS_VALUES&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%w[none requested approved denied removed shared]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;AccessStatus&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;enum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ACCESS_STATUS_VALUES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fallback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;none&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;no&quot;&gt;THUMB_STATUS_VALUES&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%w[liked disliked]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;ThumbStatus&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;enum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;THUMB_STATUS_VALUES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;optional&lt;/span&gt;

    &lt;span class=&quot;no&quot;&gt;ACCESS_END_REASON_VALUES&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%w[expired declined blocked removed]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;AccessEndReason&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;enum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ACCESS_END_REASON_VALUES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;optional&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Benefits:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;✅ Enums defined once, reusable everywhere&lt;/li&gt;
  &lt;li&gt;✅ Built-in validation (invalid values → constraint error or fallback)&lt;/li&gt;
  &lt;li&gt;✅ Automatic coercion with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.constructor&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;✅ Safe defaults with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.fallback()&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;✅ Self-documenting—type definitions are specifications&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;33-usage-examples&quot;&gt;3.3 Usage Examples&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Enum validation is automatic&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Avatars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AccessStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;approved&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; &quot;approved&quot;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Avatars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AccessStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;invalid&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; &quot;none&quot; (fallback)&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Avatars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AccessStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;         &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; &quot;none&quot; (fallback)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Boolean coercion with fallback&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SafeBool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; true&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SafeBool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; false (fallback, not nil!)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Timestamp coercion&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ISO8601String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;           &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; &quot;2025-12-04T09:30:00Z&quot;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ISO8601String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2025-12-04T09:30:00Z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; &quot;2025-12-04T09:30:00Z&quot;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ISO8601String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;                    &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;4-dry-struct-immutable-data-structures&quot;&gt;4. dry-struct: Immutable Data Structures&lt;/h2&gt;

&lt;h3 id=&quot;41-the-problem-hash-manipulation-everywhere&quot;&gt;4.1 The Problem: Hash Manipulation Everywhere&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Before: Raw hash with manual processing&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;build_actions&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;compute_access_status&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar_viewed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;compute_avatar_viewed&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:is_saved&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;compute_is_saved&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# No guarantee of structure&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Caller must know what keys exist&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Mutable—anyone can modify&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Issues:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;No schema—structure discovered at runtime&lt;/li&gt;
  &lt;li&gt;Mutable—data can change unexpectedly&lt;/li&gt;
  &lt;li&gt;Manual coercion needed when consuming&lt;/li&gt;
  &lt;li&gt;No IDE autocompletion&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;42-the-solution-typed-immutable-struct&quot;&gt;4.2 The Solution: Typed Immutable Struct&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/structs/avatars/actions.rb&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dry/struct&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Avatars&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Actions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Dry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Struct&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Allow string keys from input hash&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;transform_keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:to_sym&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Required fields with defaults (always present in payload)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;attribute?&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:access_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Avatars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AccessStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;none&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;attribute?&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:avatar_viewed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SafeBool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;attribute?&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:is_saved&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SafeBool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Optional fields (only in payload when present)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;attribute?&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:note&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;OptionalString&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;attribute?&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:thumb_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Avatars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ThumbStatus&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;attribute?&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:applied_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ISO8601String&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;attribute?&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:access_expired&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;optional&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;attribute?&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:access_end_reason&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Avatars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AccessEndReason&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;attribute?&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:chat_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;OptionalInteger&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;attribute?&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:is_shared&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;optional&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Clean serialization for API response&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;to_payload&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;access_status: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;access_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;avatar_viewed: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar_viewed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;is_saved: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;is_saved&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Add optional fields only when present&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:note&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;note&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;note&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:thumb_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;thumb_status&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;thumb_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:applied_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;applied_at&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;applied_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_expired&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;access_expired&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;access_expired&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;nil?&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_end_reason&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;access_end_reason&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;access_end_reason&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:chat_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chat_id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chat_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:is_shared&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is_shared&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;43-before-vs-after-comparison&quot;&gt;4.3 Before vs After Comparison&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before (manual hash building):&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# 70+ lines scattered across service and blueprint&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;build_actions_payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;extract_actions_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ACCESS_STATUS_VALUES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;none&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar_viewed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar_viewed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ... 60 more lines of manual processing&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;After (struct instantiation):&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Service creates struct&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Avatars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;access_status: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;compute_access_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;avatar_viewed: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;compute_avatar_viewed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;is_saved: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;compute_is_saved&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;chat_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;find_chat_room&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Blueprint just calls to_payload&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;field&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:actions&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_payload&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Reduction: 70+ lines → ~15 lines&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;44-benefits-of-dry-struct&quot;&gt;4.4 Benefits of dry-struct&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Aspect&lt;/th&gt;
      &lt;th&gt;Raw Hash&lt;/th&gt;
      &lt;th&gt;Dry::Struct&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Schema&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Implicit&lt;/td&gt;
      &lt;td&gt;Explicit, documented&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Type safety&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;Enforced at construction&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Defaults&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Manual&lt;/td&gt;
      &lt;td&gt;Declarative&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Mutability&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Mutable&lt;/td&gt;
      &lt;td&gt;Immutable&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;IDE support&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;Autocomplete attributes&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Testability&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Test whole flow&lt;/td&gt;
      &lt;td&gt;Test struct in isolation&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;5-dry-monads-functional-error-handling&quot;&gt;5. dry-monads: Functional Error Handling&lt;/h2&gt;

&lt;h3 id=&quot;51-the-problem-nested-conditionals&quot;&gt;5.1 The Problem: Nested Conditionals&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Before: Conditional soup&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;client?&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;avatar_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Happy path finally...&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;build_actions&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;StandardError&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Issues:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Early returns obscure happy path&lt;/li&gt;
  &lt;li&gt;Error information lost (just returns nil)&lt;/li&gt;
  &lt;li&gt;Caller can’t distinguish “no data” from “error”&lt;/li&gt;
  &lt;li&gt;No composability&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;52-the-solution-result-monad&quot;&gt;5.2 The Solution: Result Monad&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/services/avatars/actions_builder.rb&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dry/monads&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Avatars&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActionsBuilder&lt;/span&gt;
    &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Dry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Monads&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;default_actions_struct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;valid_context?&lt;/span&gt;

      &lt;span class=&quot;no&quot;&gt;Success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;build_actions_struct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;valid_context?&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;client_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;client_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;client?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;avatar_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;default_actions_struct&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Avatars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;access_status: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;none&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;avatar_viewed: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;is_saved: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;build_actions_struct&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Avatars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;access_status: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;compute_access_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;avatar_viewed: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;compute_avatar_viewed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# ... other fields&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;53-controller-integration&quot;&gt;5.3 Controller Integration&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/controllers/api/v1/game_controller.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;flags&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visibility_flags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;visibility_flags: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;flags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;host: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;base_url&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;client?&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actions_builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;avatar: &lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;client_user: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;value!&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success?&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;json: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;data: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AvatarBlueprint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render_as_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;context: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;54-pattern-matching-with-results&quot;&gt;5.4 Pattern Matching with Results&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# For more complex flows, pattern match on Result&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionsBuilder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;avatar: &lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;client_user: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;render_success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Failure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:invalid_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reason&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;warn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Invalid context: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reason&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;render_default_actions&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Failure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:database_error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Sentry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;capture_exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;head&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:internal_server_error&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;55-composing-multiple-operations&quot;&gt;5.5 Composing Multiple Operations&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Chain operations that might fail&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ProcessOrder&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Dry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Monads&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:do&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;validate_order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;player&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;find_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;player_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;payment&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;charge_payment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;total&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;send_confirmation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;payment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;no&quot;&gt;Success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;validate_order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Returns Success(order) or Failure([:validation, errors])&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;charge_payment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;amount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Returns Success(payment) or Failure([:payment_failed, reason])&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yield&lt;/code&gt; keyword automatically unwraps &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Success&lt;/code&gt; values and short-circuits on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Failure&lt;/code&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;6-dry-validation-contract-based-validation&quot;&gt;6. dry-validation: Contract-Based Validation&lt;/h2&gt;

&lt;h3 id=&quot;61-the-problem-validation-logic-everywhere&quot;&gt;6.1 The Problem: Validation Logic Everywhere&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Before: Validation scattered across controller, model, and service&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ItemsController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationController&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Controller validation&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render_error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Title required&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;blank?&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render_error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Invalid status&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;VALID_STATUSES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

    &lt;span class=&quot;vi&quot;&gt;@item&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Model validation runs here too...&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Item&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Model validation&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;validates&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;presence: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;validates&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;inclusion: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;in: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;VALID_STATUSES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;validate&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:price_must_be_positive_for_published&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Issues:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Validation logic duplicated (controller + model)&lt;/li&gt;
  &lt;li&gt;Cross-field rules awkward in ActiveRecord&lt;/li&gt;
  &lt;li&gt;Hard to test validation in isolation&lt;/li&gt;
  &lt;li&gt;Error messages inconsistent&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;62-the-solution-validation-contracts&quot;&gt;6.2 The Solution: Validation Contracts&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/contracts/avatars/actions_contract.rb&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dry/validation&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Avatars&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActionsContract&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Dry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Validation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Contract&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Schema definition - coerces and validates structure&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;optional&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;optional&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar_viewed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;optional&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:is_saved&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;optional&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:note&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;maybe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;optional&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:thumb_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;maybe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;optional&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_expired&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;maybe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;optional&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_end_reason&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;maybe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;optional&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:chat_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;maybe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Enum validation rules&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;nil?&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Avatars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ACCESS_STATUS_VALUES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;failure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;must be one of: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Avatars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ACCESS_STATUS_VALUES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;, &apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;rule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:thumb_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;nil?&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Avatars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;THUMB_STATUS_VALUES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;failure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;must be one of: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Avatars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;THUMB_STATUS_VALUES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;, &apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Cross-field validation&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_end_reason&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:access_expired&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_end_reason&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_expired&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_end_reason&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;failure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;requires access_expired to be true&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;63-using-contracts&quot;&gt;6.3 Using Contracts&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;contract&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Avatars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ActionsContract&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;contract&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_unsafe_h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success?&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Validated and coerced data&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Avatars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Structured errors&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;json: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;errors: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;errors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_h&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: :unprocessable_entity&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;64-contract-vs-model-validation&quot;&gt;6.4 Contract vs Model Validation&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Aspect&lt;/th&gt;
      &lt;th&gt;ActiveRecord Validation&lt;/th&gt;
      &lt;th&gt;Dry::Validation Contract&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;When runs&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Before save&lt;/td&gt;
      &lt;td&gt;Before touching model&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Cross-field rules&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Custom validators&lt;/td&gt;
      &lt;td&gt;Declarative &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rule&lt;/code&gt; blocks&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Testability&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Requires model instance&lt;/td&gt;
      &lt;td&gt;Pure Ruby, no DB&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Reusability&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Tied to model&lt;/td&gt;
      &lt;td&gt;Standalone class&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Error format&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Rails format&lt;/td&gt;
      &lt;td&gt;Customizable&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Coercion&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Limited&lt;/td&gt;
      &lt;td&gt;Built-in via schema&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;Use both:&lt;/strong&gt; Contracts for input validation (API layer), ActiveRecord for data integrity (persistence layer).&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;7-dry-container--dry-auto_inject-dependency-injection&quot;&gt;7. dry-container + dry-auto_inject: Dependency Injection&lt;/h2&gt;

&lt;h3 id=&quot;71-the-problem-hard-coded-dependencies&quot;&gt;7.1 The Problem: Hard-Coded Dependencies&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Before: Tight coupling to class names&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GameController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationController&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;flags&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Visibility&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AvatarVisibilityFlags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Avatars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ActionsBuilder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;avatar: &lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;client_user: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Testing requires stubbing constants&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GameController&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;before&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;allow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Visibility&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AvatarVisibilityFlags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;and_return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({})&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;allow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Avatars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ActionsBuilder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;and_return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({}))&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Issues:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Can’t easily swap implementations&lt;/li&gt;
  &lt;li&gt;Testing requires monkey-patching&lt;/li&gt;
  &lt;li&gt;Dependencies hidden in method bodies&lt;/li&gt;
  &lt;li&gt;No central registry of services&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;72-the-solution-container--injection&quot;&gt;7.2 The Solution: Container + Injection&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/container.rb&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dry/container&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dry/auto_inject&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AppContainer&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Dry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Container&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Mixin&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Register services&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:actions_builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Avatars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ActionsBuilder&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:visibility_flags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Visibility&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AvatarVisibilityFlags&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Future additions&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# register(:email_service) { EmailService.new }&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# register(:payment_gateway) { Stripe::Client.new(ENV[&apos;STRIPE_KEY&apos;]) }&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# For POROs (constructor injection)&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Dry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AutoInject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AppContainer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# For Rails controllers (memoized accessors)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Deps&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;included&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class_eval&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;AppContainer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;define_method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
          &lt;span class=&quot;vi&quot;&gt;@_deps_cache&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
          &lt;span class=&quot;vi&quot;&gt;@_deps_cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AppContainer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;73-controller-with-injection&quot;&gt;7.3 Controller with Injection&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/controllers/api/v1/game_controller.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GameController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationController&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Deps&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Inject all registered dependencies&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;flags&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;visibility_flags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;           &lt;span class=&quot;c1&quot;&gt;# Use injected service&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actions_builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;               &lt;span class=&quot;c1&quot;&gt;# Use injected builder&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;74-testing-with-injection&quot;&gt;7.4 Testing with Injection&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Spec: Easy stubbing via dependency override&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GameController&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:mock_builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ActionsBuilder&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:mock_flags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;VisibilityFlags&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;before&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Override container registrations for test&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;AppContainer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;stub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:actions_builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mock_builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;AppContainer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;stub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:visibility_flags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mock_flags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;uses injected dependencies&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mock_flags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;and_return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({})&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mock_builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;and_return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;default_actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;params: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;75-benefits-of-dependency-injection&quot;&gt;7.5 Benefits of Dependency Injection&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Without DI&lt;/th&gt;
      &lt;th&gt;With DI&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ClassName.call(...)&lt;/code&gt; scattered&lt;/td&gt;
      &lt;td&gt;Centralized registry&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Test with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;allow(ClassName)&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Test with mock injection&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Change implementation = change every caller&lt;/td&gt;
      &lt;td&gt;Change registration only&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Dependencies implicit&lt;/td&gt;
      &lt;td&gt;Dependencies explicit&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;8-dry-matcher-pattern-matching-for-results&quot;&gt;8. dry-matcher: Pattern Matching for Results&lt;/h2&gt;

&lt;h3 id=&quot;81-the-problem-verbose-result-handling&quot;&gt;8.1 The Problem: Verbose Result Handling&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Before: Manual success/failure checks&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actions_builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;avatar: &lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;client_user: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success?&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;value!&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;failure&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:not_found&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;head&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:not_found&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:unauthorized&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;head&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:unauthorized&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;json: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;error: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;failure&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: :unprocessable_entity&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Issues:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Verbose conditional logic&lt;/li&gt;
  &lt;li&gt;Easy to forget failure cases&lt;/li&gt;
  &lt;li&gt;Not composable&lt;/li&gt;
  &lt;li&gt;Repeated patterns across controllers&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;82-the-solution-pattern-matching-concern&quot;&gt;8.2 The Solution: Pattern Matching Concern&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/controllers/concerns/result_matchable.rb&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dry/matcher/result_matcher&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;ResultMatchable&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Concern&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;included&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;class_attribute&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:result_matcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Dry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Matcher&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ResultMatcher&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Match on a Dry::Monads::Result with pattern matching&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;match_result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;result_matcher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Extract value from Success or return nil for Failure&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;unwrap_result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;value!&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Returns [success?, value_or_error]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;result_tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success?&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;value!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;failure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;83-controller-with-pattern-matching&quot;&gt;8.3 Controller with Pattern Matching&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/controllers/api/v1/game_controller.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GameController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationController&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ResultMatchable&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Add pattern matching&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actions_builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;avatar: &lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;client_user: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Simple extraction&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unwrap_result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Or full pattern matching for complex flows&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;match_result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;failure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:not_found&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;head&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:not_found&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;failure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:unauthorized&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;head&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:unauthorized&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;failure&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;warn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Actions failed: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;json: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;data: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AvatarBlueprint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render_as_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;context: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;84-before-vs-after&quot;&gt;8.4 Before vs After&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# 15 lines of conditional logic&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success?&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;value!&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;failure&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:not_found&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;head&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:not_found&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:unauthorized&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;head&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:unauthorized&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render_error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;failure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# 1 line for simple cases&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unwrap_result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Or declarative matching for complex cases&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;match_result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;failure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:not_found&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;head&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:not_found&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;failure&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render_error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;85-benefits&quot;&gt;8.5 Benefits&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Without dry-matcher&lt;/th&gt;
      &lt;th&gt;With dry-matcher&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if/case&lt;/code&gt; statements&lt;/td&gt;
      &lt;td&gt;Declarative blocks&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Easy to miss cases&lt;/td&gt;
      &lt;td&gt;Exhaustive matching&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Scattered handling&lt;/td&gt;
      &lt;td&gt;Centralized concern&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Hard to test branches&lt;/td&gt;
      &lt;td&gt;Each handler testable&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;9-dry-configurable-thread-safe-configuration&quot;&gt;9. dry-configurable: Thread-Safe Configuration&lt;/h2&gt;

&lt;h3 id=&quot;91-the-problem-scattered-constants&quot;&gt;9.1 The Problem: Scattered Constants&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Before: Constants scattered across files&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AvatarBlueprint&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationBlueprint&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;ACCESS_STATUS_VALUES&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%w[none requested approved denied removed shared]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;CACHE_TTL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;minutes&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AccessRequest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;EXPIRY_DAYS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GameController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationController&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;MAX_PER_PAGE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Issues:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Configuration scattered across classes&lt;/li&gt;
  &lt;li&gt;Hard to find all config values&lt;/li&gt;
  &lt;li&gt;No nesting or organization&lt;/li&gt;
  &lt;li&gt;Testing requires constant stubbing&lt;/li&gt;
  &lt;li&gt;Not thread-safe for dynamic changes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;92-the-solution-centralized-configuration&quot;&gt;9.2 The Solution: Centralized Configuration&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/config/avatar_config.rb&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dry/configurable&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AvatarConfig&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Dry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Configurable&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Actions settings&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;setting&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:actions&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;setting&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:cache_ttl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;minutes&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;setting&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:default_access_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;none&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;setting&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:default_avatar_viewed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;setting&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:default_is_saved&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;setting&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:access_status_values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;%w[none requested approved denied removed shared]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;setting&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:thumb_status_values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;%w[liked disliked]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;setting&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:access_end_reason_values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;%w[expired declined blocked removed]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;setting&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:status_mapping&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;pending&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;requested&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;approved&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;approved&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;declined&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;denied&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;blocked&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;denied&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;expired&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;removed&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Visibility settings&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;setting&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:visibility&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;setting&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:default_mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;visible&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;setting&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:modes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;%w[visible semi_anonymous fully_anonymous]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Access request settings&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;setting&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:access&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;setting&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:request_expiry_days&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;setting&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:auto_approve_verified_companies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;setting&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:max_pending_per_company&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Event publishing settings&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;setting&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:events&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;setting&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:publish_avatar_views&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;setting&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:publish_access_events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;93-usage-in-code&quot;&gt;9.3 Usage in Code&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Access nested settings with dot notation&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;AvatarConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cache_ttl&lt;/span&gt;           &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; 5.minutes&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;AvatarConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;status_mapping&lt;/span&gt;      &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; {&quot;pending&quot; =&amp;gt; &quot;requested&quot;, ...}&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;AvatarConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;visibility&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default_mode&lt;/span&gt;     &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; &quot;visible&quot;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;AvatarConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;access&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;request_expiry_days&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; 30&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Use in service objects&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActionsBuilder&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;default_actions_struct&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Avatars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;access_status: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default_access_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;avatar_viewed: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default_avatar_viewed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;is_saved: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default_is_saved&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;config&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;AvatarConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;actions&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;94-testing-with-configuration&quot;&gt;9.4 Testing with Configuration&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionsBuilder&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Override config for test&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;before&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;allow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AvatarConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:cache_ttl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;and_return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Or use configure block&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;around&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;original&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AvatarConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cache_ttl&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;AvatarConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cache_ttl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;AvatarConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cache_ttl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;original&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;95-before-vs-after&quot;&gt;9.5 Before vs After&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Hunt through 10 files to find all avatar-related constants&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;CACHE_TTL&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\|&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;EXPIRY_DAYS&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\|&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;MAX_&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# One file, organized by domain&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;AvatarConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;config&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; nested configuration tree&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;96-benefits&quot;&gt;9.6 Benefits&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Scattered Constants&lt;/th&gt;
      &lt;th&gt;dry-configurable&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Find: grep across codebase&lt;/td&gt;
      &lt;td&gt;Find: one file&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Organize: N/A&lt;/td&gt;
      &lt;td&gt;Organize: nested settings&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Test: stub constants&lt;/td&gt;
      &lt;td&gt;Test: configure block&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Thread-safe: No&lt;/td&gt;
      &lt;td&gt;Thread-safe: Yes&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Document: comments&lt;/td&gt;
      &lt;td&gt;Document: structure IS docs&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;10-dry-initializer-declarative-parameter-dsl&quot;&gt;10. dry-initializer: Declarative Parameter DSL&lt;/h2&gt;

&lt;h3 id=&quot;101-the-problem-boilerplate-initialize-methods&quot;&gt;10.1 The Problem: Boilerplate Initialize Methods&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Before: Manual parameter handling&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActionsBuilder&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@client_user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client_user&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@avatar_user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;player&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;avatar: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;client_user: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

  &lt;span class=&quot;nb&quot;&gt;attr_reader&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:client_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:avatar_user&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Issues:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Boilerplate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@x = x&lt;/code&gt; assignments&lt;/li&gt;
  &lt;li&gt;Manual &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;attr_reader&lt;/code&gt; for each param&lt;/li&gt;
  &lt;li&gt;No type coercion&lt;/li&gt;
  &lt;li&gt;No default values&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self.call&lt;/code&gt; pattern repeated everywhere&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;102-the-solution-declarative-parameters&quot;&gt;10.2 The Solution: Declarative Parameters&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/services/avatars/actions_builder.rb&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dry/initializer&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Avatars&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActionsBuilder&lt;/span&gt;
    &lt;span class=&quot;kp&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Dry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Initializer&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Required parameters&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;param&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:avatar&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;param&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:client_user&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Derived option with default&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;option&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:avatar_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;player&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;default_actions_struct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;valid_context?&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;build_actions_struct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# params are accessible directly - no attr_reader needed&lt;/span&gt;
    &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;valid_context?&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;client?&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;103-advanced-features&quot;&gt;10.3 Advanced Features&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OrderProcessor&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Dry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Initializer&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Required param with type coercion&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;param&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:order_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Integer&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Optional param with default&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;param&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:notify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Named options (keyword args)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;option&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;option&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:mailer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;OrderMailer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Type-checked option&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;option&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:priority&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;enum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;low&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;normal&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;high&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;normal&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Reader visibility&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;option&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:api_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;reader: :private&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Processing order &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order_id&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; with priority &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;priority&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Usage&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;processor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;OrderProcessor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;priority: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;high&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;processor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;order_id&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; 123&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;processor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;priority&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; &quot;high&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;104-before-vs-after&quot;&gt;10.4 Before vs After&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before (19 lines):&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActionsBuilder&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@client_user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client_user&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@avatar_user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;player&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;avatar: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;client_user: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

  &lt;span class=&quot;nb&quot;&gt;attr_reader&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:client_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:avatar_user&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;After (10 lines):&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActionsBuilder&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Dry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Initializer&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;param&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:avatar&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;param&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:client_user&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;option&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:avatar_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;player&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;105-benefits&quot;&gt;10.5 Benefits&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Manual Initialize&lt;/th&gt;
      &lt;th&gt;dry-initializer&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@x = x&lt;/code&gt; boilerplate&lt;/td&gt;
      &lt;td&gt;Declarative &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;param&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;option&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Manual &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;attr_reader&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Auto-generated readers&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;No type coercion&lt;/td&gt;
      &lt;td&gt;Built-in type support&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;No defaults DSL&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default: proc { }&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;All public readers&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reader: :private&lt;/code&gt; option&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;11-dry-events-pubsub-event-system&quot;&gt;11. dry-events: Pub/Sub Event System&lt;/h2&gt;

&lt;h3 id=&quot;111-the-problem-inline-side-effects&quot;&gt;11.1 The Problem: Inline Side Effects&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Before: Side effects mixed with business logic&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GameController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationController&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Business logic&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;build_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Side effect #1: Analytics&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Analytics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;track&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;avatar_viewed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;avatar_id: &lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;viewer_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Side effect #2: Logging&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;AuditLog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;action: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;avatar_view&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;resource: &lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;player: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Side effect #3: Cache warming&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;AvatarCacheWarmer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;perform_async&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;should_warm_cache?&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;json: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;data: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AvatarBlueprint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render_as_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;context: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Issues:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Controller bloated with side effects&lt;/li&gt;
  &lt;li&gt;Hard to test business logic in isolation&lt;/li&gt;
  &lt;li&gt;Adding new side effects = modifying controller&lt;/li&gt;
  &lt;li&gt;Side effects tightly coupled to triggering code&lt;/li&gt;
  &lt;li&gt;No audit trail of “what events exist”&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;112-the-solution-event-publisher&quot;&gt;11.2 The Solution: Event Publisher&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/events/publisher.rb&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dry/events&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EventPublisher&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Dry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Events&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Publisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Avatar events&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;register_event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;avatar.viewed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;register_event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;avatar.actions_computed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Access request events&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;register_event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;access.requested&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;register_event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;access.approved&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;register_event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;access.declined&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Chat events&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;register_event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;chat.room_accessed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;instance&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@instance&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;publish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{})&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;should_publish?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;instance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;publish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;merge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;published_at: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;listener_or_event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;block_given?&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;instance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;listener_or_event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;instance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;listener_or_event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;should_publish?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AvatarConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;publish_avatar_views&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start_with?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;avatar.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AvatarConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;publish_access_events&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start_with?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;access.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;113-publishing-events&quot;&gt;11.3 Publishing Events&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/controllers/api/v1/game_controller.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GameController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationController&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;build_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Publish event - side effects handled by subscribers&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;EventPublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;publish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;avatar.viewed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;avatar_id: &lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;viewer_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;viewer_role: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;role&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;json: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;data: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AvatarBlueprint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render_as_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;context: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# app/services/avatars/actions_builder.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActionsBuilder&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;build_actions_struct&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Publish event with computed data&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;EventPublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;publish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;avatar.actions_computed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;avatar_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;client_player_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;access_status: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;access_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;has_chat: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chat_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

    &lt;span class=&quot;no&quot;&gt;Success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;114-subscribing-to-events&quot;&gt;11.4 Subscribing to Events&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# config/initializers/event_subscribers.rb&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Block-based subscriber&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;EventPublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;avatar.viewed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Analytics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;track&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;avatar_view&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Object-based subscriber&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AuditListener&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;on_avatar_viewed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;AuditLog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;action: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;avatar_view&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;resource_type: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Avatar&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;resource_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;player_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:viewer_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
      &lt;span class=&quot;ss&quot;&gt;occurred_at: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:published_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;on_access_requested&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;AuditLog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;action: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;access_request&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;on_access_approved&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;AuditLog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;action: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;access_approval&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;EventPublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AuditListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Conditional subscriber&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CacheWarmerListener&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;on_avatar_viewed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;should_warm_cache?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

    &lt;span class=&quot;no&quot;&gt;AvatarCacheWarmer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;perform_async&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:avatar_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;should_warm_cache?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Logic to determine if cache should be warmed&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;EventPublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CacheWarmerListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;115-before-vs-after&quot;&gt;11.5 Before vs After&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before (controller with inline side effects):&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Side effect #1&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Analytics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;track&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;avatar_viewed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;avatar_id: &lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Side effect #2&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;AuditLog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;action: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;avatar_view&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;resource: &lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Side effect #3&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;AvatarCacheWarmer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;perform_async&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;json: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;data: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AvatarBlueprint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render_as_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;After (clean controller, events handle side effects):&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;show&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

  &lt;span class=&quot;no&quot;&gt;EventPublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;publish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;avatar.viewed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;avatar_id: &lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;viewer_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;json: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;data: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AvatarBlueprint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render_as_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;116-testing-events&quot;&gt;11.6 Testing Events&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GameController&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;GET #show&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;publishes avatar.viewed event&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EventPublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:publish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;avatar.viewed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;hash_including&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;avatar_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;viewer_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:show&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;params: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;AuditListener&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;#on_avatar_viewed&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;payload: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;avatar_id: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;viewer_id: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;published_at: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;creates audit log entry&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;on_avatar_viewed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;change&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AuditLog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;117-benefits&quot;&gt;11.7 Benefits&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Inline Side Effects&lt;/th&gt;
      &lt;th&gt;dry-events&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Mixed with business logic&lt;/td&gt;
      &lt;td&gt;Separated via pub/sub&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Hard to test in isolation&lt;/td&gt;
      &lt;td&gt;Each listener testable&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Adding effects = modify caller&lt;/td&gt;
      &lt;td&gt;Adding effects = new subscriber&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;No event catalog&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;register_event&lt;/code&gt; documents all&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Tight coupling&lt;/td&gt;
      &lt;td&gt;Loose coupling&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Synchronous only&lt;/td&gt;
      &lt;td&gt;Async-ready (queue subscribers)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;12-other-dry-rb-gems-worth-exploring&quot;&gt;12. Other dry-rb Gems Worth Exploring&lt;/h2&gt;

&lt;h3 id=&quot;121-dry-transaction-multi-step-business-operations&quot;&gt;12.1 dry-transaction: Multi-Step Business Operations&lt;/h3&gt;

&lt;p&gt;Perfect for complex workflows like order processing or player registration:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/transactions/create_order.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CreateOrder&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Dry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Transaction&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;step&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:validate&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;step&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:create_order&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;step&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:charge_payment&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;step&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:send_confirmation&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;validate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;contract&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;OrderContract&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;contract&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Failure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;errors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create_order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;RecordInvalid&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Failure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;charge_payment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Payment logic...&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;send_confirmation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;OrderMailer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;confirmation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;deliver_later&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Usage&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;CreateOrder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success?&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# =&amp;gt; true/false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;122-dry-effects-algebraic-effects&quot;&gt;12.2 dry-effects: Algebraic Effects&lt;/h3&gt;

&lt;p&gt;Advanced: dependency injection via effects (alternative to dry-container):&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ProcessPayment&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Dry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Effects&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Reader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:current_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Dry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Effects&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:payment_gateway&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;amount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;gateway&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:payment_gateway&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;gateway&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;charge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;amount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Provide effects at call site&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Dry&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Effects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;current_user: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;resolve: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;payment_gateway: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;StripeGateway&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ProcessPayment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;123-gem-selection-guide&quot;&gt;12.3 Gem Selection Guide&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Gem&lt;/th&gt;
      &lt;th&gt;When to Use&lt;/th&gt;
      &lt;th&gt;Complexity&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;dry-types&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Any project (foundational)&lt;/td&gt;
      &lt;td&gt;Low&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;dry-struct&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Data transfer objects, value objects&lt;/td&gt;
      &lt;td&gt;Low&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;dry-configurable&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Configuration management&lt;/td&gt;
      &lt;td&gt;Low&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;dry-initializer&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Service object parameters&lt;/td&gt;
      &lt;td&gt;Low&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;dry-monads&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Service objects with failures&lt;/td&gt;
      &lt;td&gt;Medium&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;dry-validation&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;API input validation&lt;/td&gt;
      &lt;td&gt;Medium&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;dry-container&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Large apps, testing focus&lt;/td&gt;
      &lt;td&gt;Medium&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;dry-matcher&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Result pattern matching&lt;/td&gt;
      &lt;td&gt;Medium&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;dry-events&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Event-driven architecture&lt;/td&gt;
      &lt;td&gt;Medium&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;dry-transaction&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Multi-step workflows&lt;/td&gt;
      &lt;td&gt;Medium&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;dry-effects&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Advanced FP patterns&lt;/td&gt;
      &lt;td&gt;High&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;13-benefits-and-tradeoffs&quot;&gt;13. Benefits and Tradeoffs&lt;/h2&gt;

&lt;h3 id=&quot;131-benefits&quot;&gt;13.1 Benefits&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Explicit Over Implicit&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Before: What can access_status be? Check the code...&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:access_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ACCESS_STATUS_VALUES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;none&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# After: The type IS the documentation&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;attribute&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:access_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Avatars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AccessStatus&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Enum is explicit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2. Testability&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Before: Test through controller/blueprint&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;returns correct access_status&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/avatars/1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;headers: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;auth_headers&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;&quot;access_status&quot;:&quot;approved&quot;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# After: Unit test the struct directly&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;coerces invalid status to none&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Avatars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;access_status: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;invalid&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;actions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;access_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;none&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;3. Composability&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Combine types to build complex structures&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;CompanyAvatar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;employees: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EmployeeStruct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;settings: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;visibility: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Avatars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;VisibilityMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;features: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;4. Error Messages&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# dry-validation provides structured errors&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;contract&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;thumb_status: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;invalid&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;errors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_h&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# =&amp;gt; { thumb_status: [&quot;must be one of: liked, disliked&quot;] }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;5. Event-Driven Architecture&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Before: Side effects scattered in controllers&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Analytics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;track&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;AuditLog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;CacheWarmer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;perform_async&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# After: Single event, multiple handlers&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;EventPublisher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;publish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;avatar.viewed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;6. Centralized Configuration&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Before: grep -r &quot;CACHE_TTL&quot; app/&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# After: AvatarConfig.config.actions.cache_ttl&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;132-tradeoffs&quot;&gt;13.2 Tradeoffs&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Learning Curve&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Team needs to learn dry-rb idioms&lt;/li&gt;
  &lt;li&gt;Functional programming concepts (monads, composition)&lt;/li&gt;
  &lt;li&gt;More files/classes than “Rails way”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Indirection&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Types defined in one place, used in another&lt;/li&gt;
  &lt;li&gt;Following data flow requires understanding container&lt;/li&gt;
  &lt;li&gt;Events may be handled far from where they’re published&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Overhead for Simple Cases&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# For a simple boolean flag, this is overkill:&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;attribute&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:is_active&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# This is fine:&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;attr_accessor&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:is_active&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;4. Gems Add Dependencies&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;11 gems for full suite&lt;/li&gt;
  &lt;li&gt;Version compatibility to manage&lt;/li&gt;
  &lt;li&gt;Bundle size increases&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;133-when-to-use-dry-rb&quot;&gt;13.3 When to Use dry-rb&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;✅ Good Fit:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;APIs with complex data contracts&lt;/li&gt;
  &lt;li&gt;Services with multiple failure modes&lt;/li&gt;
  &lt;li&gt;Apps where testability is priority&lt;/li&gt;
  &lt;li&gt;Teams comfortable with FP concepts&lt;/li&gt;
  &lt;li&gt;Large apps with many developers&lt;/li&gt;
  &lt;li&gt;Event-driven or audit-heavy systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;❌ Poor Fit:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Simple CRUD apps&lt;/li&gt;
  &lt;li&gt;Prototypes/MVPs&lt;/li&gt;
  &lt;li&gt;Teams unfamiliar with FP&lt;/li&gt;
  &lt;li&gt;Performance-critical hot paths (slight overhead)&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;14-conclusion&quot;&gt;14. Conclusion&lt;/h2&gt;

&lt;p&gt;Integrating dry-rb into our Rails API transformed code that “worked” into code that’s &lt;strong&gt;obvious, testable, and maintainable&lt;/strong&gt;:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Metric&lt;/th&gt;
      &lt;th&gt;Before&lt;/th&gt;
      &lt;th&gt;After&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Actions payload code&lt;/td&gt;
      &lt;td&gt;70+ lines&lt;/td&gt;
      &lt;td&gt;15 lines&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Type validation&lt;/td&gt;
      &lt;td&gt;Manual per-field&lt;/td&gt;
      &lt;td&gt;Declarative&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Error handling&lt;/td&gt;
      &lt;td&gt;try/rescue + nil&lt;/td&gt;
      &lt;td&gt;Result monad&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Dependencies&lt;/td&gt;
      &lt;td&gt;Hard-coded classes&lt;/td&gt;
      &lt;td&gt;Injected&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Configuration&lt;/td&gt;
      &lt;td&gt;Scattered constants&lt;/td&gt;
      &lt;td&gt;Centralized&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Side effects&lt;/td&gt;
      &lt;td&gt;Inline in controllers&lt;/td&gt;
      &lt;td&gt;Event-driven&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Service parameters&lt;/td&gt;
      &lt;td&gt;Manual initialize&lt;/td&gt;
      &lt;td&gt;Declarative DSL&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Result handling&lt;/td&gt;
      &lt;td&gt;if/case statements&lt;/td&gt;
      &lt;td&gt;Pattern matching&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Test isolation&lt;/td&gt;
      &lt;td&gt;Integration only&lt;/td&gt;
      &lt;td&gt;Unit + integration&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Gems used&lt;/td&gt;
      &lt;td&gt;0&lt;/td&gt;
      &lt;td&gt;11&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;key-takeaways&quot;&gt;Key Takeaways&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Start with dry-types&lt;/strong&gt; — It’s foundational and low-risk&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Add dry-struct for data transfer&lt;/strong&gt; — Especially for API responses&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Use dry-monads in services&lt;/strong&gt; — Replace boolean/nil returns&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Add dry-validation for API inputs&lt;/strong&gt; — Before data hits your models&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Use dry-configurable early&lt;/strong&gt; — Centralize config before it scatters&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Add dry-initializer to services&lt;/strong&gt; — Reduce boilerplate immediately&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Introduce dry-container gradually&lt;/strong&gt; — When testing pain increases&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Add dry-matcher for cleaner controllers&lt;/strong&gt; — Pattern matching is elegant&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Use dry-events for side effects&lt;/strong&gt; — Decouple analytics, logging, notifications&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Don’t over-engineer&lt;/strong&gt; — A simple hash is fine for simple data&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;the-return-on-investment&quot;&gt;The Return on Investment&lt;/h3&gt;

&lt;p&gt;The initial investment—learning curves, refactoring, new patterns—pays dividends in:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Fewer bugs&lt;/strong&gt; — Type mismatches caught at construction, not runtime&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Faster debugging&lt;/strong&gt; — Follow explicit contracts, not implicit conventions&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Easier onboarding&lt;/strong&gt; — Types and events document themselves&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Confident refactoring&lt;/strong&gt; — Change implementation, types verify compatibility&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Testable architecture&lt;/strong&gt; — Unit test components in isolation&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Scalable patterns&lt;/strong&gt; — Event-driven architecture grows with your app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;dry-rb doesn’t replace Rails conventions—it &lt;strong&gt;complements them&lt;/strong&gt; where they fall short. Use the right tool for each job.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;dry-rb Official Site&lt;/strong&gt;: &lt;a href=&quot;https://dry-rb.org/&quot;&gt;dry-rb.org&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;dry-rb GitHub&lt;/strong&gt;: &lt;a href=&quot;https://github.com/dry-rb&quot;&gt;github.com/dry-rb&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;dry-types Documentation&lt;/strong&gt;: &lt;a href=&quot;https://dry-rb.org/gems/dry-types&quot;&gt;dry-rb.org/gems/dry-types&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;dry-monads Documentation&lt;/strong&gt;: &lt;a href=&quot;https://dry-rb.org/gems/dry-monads&quot;&gt;dry-rb.org/gems/dry-monads&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;dry-validation Documentation&lt;/strong&gt;: &lt;a href=&quot;https://dry-rb.org/gems/dry-validation&quot;&gt;dry-rb.org/gems/dry-validation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;dry-events Documentation&lt;/strong&gt;: &lt;a href=&quot;https://dry-rb.org/gems/dry-events&quot;&gt;dry-rb.org/gems/dry-events&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;dry-configurable Documentation&lt;/strong&gt;: &lt;a href=&quot;https://dry-rb.org/gems/dry-configurable&quot;&gt;dry-rb.org/gems/dry-configurable&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;dry-initializer Documentation&lt;/strong&gt;: &lt;a href=&quot;https://dry-rb.org/gems/dry-initializer&quot;&gt;dry-rb.org/gems/dry-initializer&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;dry-matcher Documentation&lt;/strong&gt;: &lt;a href=&quot;https://dry-rb.org/gems/dry-matcher&quot;&gt;dry-rb.org/gems/dry-matcher&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Trailblazer (alternative)&lt;/strong&gt;: &lt;a href=&quot;https://trailblazer.to/&quot;&gt;trailblazer.to&lt;/a&gt; (uses dry-rb under the hood)&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;This post documents our integration of 11 dry-rb gems into the TestApp API. The patterns described here reduced our Avatar Actions payload logic from 70+ lines to 15, improved test coverage, established event-driven architecture, and created patterns that scale across 100+ endpoints.&lt;/em&gt;&lt;/p&gt;

</description>
        <pubDate>Thu, 04 Dec 2025 00:00:00 +0000</pubDate>
        <link>https://lukin.io/blog/dry-rb-integration-rails-api/</link>
        <guid isPermaLink="true">https://lukin.io/blog/dry-rb-integration-rails-api/</guid>
        
        <category>rails</category>
        
        <category>dry-rb</category>
        
        <category>functional-programming</category>
        
        <category>refactoring</category>
        
        <category>types</category>
        
        <category>validation</category>
        
        <category>architecture</category>
        
        <category>events</category>
        
        
        <category>engineering</category>
        
        <category>rails</category>
        
        <category>best-practices</category>
        
        <category>refactoring</category>
        
      </item>
    
      <item>
        <title>From MySQL to PostgreSQL: A Practical Migration Guide for Rails APIs</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;“The best time to write database-agnostic code is from day one. The second best time is before your migration.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When we started building the TestApp API, we chose MySQL for its familiarity and ease of setup. As our requirements grew—AI vector embeddings, geospatial queries, graph routing—we realized PostgreSQL wasn’t just an alternative, it was a necessity.&lt;/p&gt;

&lt;p&gt;This post documents our migration journey and shares the patterns that made it nearly painless.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;#1-why-we-migrated&quot;&gt;Why We Migrated&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#2-the-database-agnostic-foundation&quot;&gt;The Database-Agnostic Foundation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#3-the-migration-process&quot;&gt;The Migration Process&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#4-postgresql-vs-mysql-a-technical-comparison&quot;&gt;PostgreSQL vs MySQL: A Technical Comparison&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#5-advanced-postgresql-features&quot;&gt;Advanced PostgreSQL Features&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#6-multi-schema-architecture&quot;&gt;Multi-Schema Architecture&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#7-migration-tips-and-lessons-learned&quot;&gt;Migration Tips and Lessons Learned&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;1-why-we-migrated&quot;&gt;1. Why We Migrated&lt;/h2&gt;

&lt;h3 id=&quot;11-the-growing-feature-requirements&quot;&gt;1.1 The Growing Feature Requirements&lt;/h3&gt;

&lt;p&gt;Our application started as a straightforward REST API. Over time, we needed:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Requirement&lt;/th&gt;
      &lt;th&gt;MySQL Solution&lt;/th&gt;
      &lt;th&gt;PostgreSQL Solution&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Vector similarity search (AI)&lt;/td&gt;
      &lt;td&gt;External service (Pinecone, Weaviate)&lt;/td&gt;
      &lt;td&gt;Native &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pgvector&lt;/code&gt; extension&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Geospatial queries&lt;/td&gt;
      &lt;td&gt;Limited spatial functions&lt;/td&gt;
      &lt;td&gt;PostGIS (industry standard)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Graph routing&lt;/td&gt;
      &lt;td&gt;External graph DB&lt;/td&gt;
      &lt;td&gt;pgRouting extension&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Full-text search&lt;/td&gt;
      &lt;td&gt;Basic FULLTEXT&lt;/td&gt;
      &lt;td&gt;Powerful tsvector/tsquery&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;JSON querying&lt;/td&gt;
      &lt;td&gt;JSON functions (limited)&lt;/td&gt;
      &lt;td&gt;JSONB with GIN indexes&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;The math was simple:&lt;/strong&gt; Three external services vs. one PostgreSQL instance.&lt;/p&gt;

&lt;h3 id=&quot;12-the-hidden-costs-of-multiple-databases&quot;&gt;1.2 The Hidden Costs of Multiple Databases&lt;/h3&gt;

&lt;p&gt;Running MySQL + Pinecone + a graph database meant:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;3x infrastructure complexity&lt;/strong&gt; — More services to monitor, scale, and pay for&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Data synchronization overhead&lt;/strong&gt; — Keeping vector embeddings in sync with source data&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Network latency&lt;/strong&gt; — Cross-service queries add milliseconds&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Operational burden&lt;/strong&gt; — Different backup strategies, different failure modes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PostgreSQL with extensions gave us &lt;strong&gt;one database to rule them all&lt;/strong&gt;.&lt;/p&gt;

&lt;h3 id=&quot;13-our-starting-point&quot;&gt;1.3 Our Starting Point&lt;/h3&gt;

&lt;p&gt;Before migration, our codebase analysis revealed:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Category&lt;/th&gt;
      &lt;th&gt;Count&lt;/th&gt;
      &lt;th&gt;Status&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;MySQL-specific SQL&lt;/td&gt;
      &lt;td&gt;1 function&lt;/td&gt;
      &lt;td&gt;Required fix&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;JSON columns&lt;/td&gt;
      &lt;td&gt;45&lt;/td&gt;
      &lt;td&gt;Upgrade to JSONB&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Check constraints&lt;/td&gt;
      &lt;td&gt;11&lt;/td&gt;
      &lt;td&gt;Already compatible&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Raw SQL queries&lt;/td&gt;
      &lt;td&gt;15+&lt;/td&gt;
      &lt;td&gt;Already compatible&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Configuration files&lt;/td&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt;Required update&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;98% of our code was already database-agnostic.&lt;/strong&gt; This wasn’t luck—it was intentional architecture.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;2-the-database-agnostic-foundation&quot;&gt;2. The Database-Agnostic Foundation&lt;/h2&gt;

&lt;h3 id=&quot;21-why-database-agnostic-code-matters&quot;&gt;2.1 Why Database-Agnostic Code Matters&lt;/h3&gt;

&lt;p&gt;Writing database-agnostic code isn’t about planning to switch databases. It’s about:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Portability&lt;/strong&gt; — Your code works on any database Rails supports&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Testability&lt;/strong&gt; — SQLite in CI, PostgreSQL in production&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Maintainability&lt;/strong&gt; — Standard patterns are easier to understand&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Future-proofing&lt;/strong&gt; — Requirements change; your code adapts&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;22-the-rules-we-follow&quot;&gt;2.2 The Rules We Follow&lt;/h3&gt;

&lt;p&gt;Our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt; (development guidelines) includes this rule:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Rule #9: Database-agnostic queries.&lt;/strong&gt;
Use Arel and ActiveRecord abstractions instead of raw SQL or database-specific syntax. This ensures portability when migrating databases.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;23-patterns-that-work-everywhere&quot;&gt;2.3 Patterns That Work Everywhere&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;✅ Use ActiveRecord Query Interface&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Good: Works on any database&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;status: :active&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;created_at &amp;gt; ?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;week&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ago&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;created_at: :desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;✅ Use Arel for Complex Queries&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Good: Database-agnostic date ranges&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CreditTransaction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:by_requested_dates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ranges&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filter_map&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;month&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;-&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:to_i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;start_date&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;zone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;local&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;month&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;beginning_of_day&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;end_date&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;end_of_month&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;end_of_day&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;arel_table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:created_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;between&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start_date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ranges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:or&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;✅ Use Standard SQL Functions&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Good: LOWER, COALESCE, CASE work everywhere&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;term&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;LOWER(name) LIKE :term OR LOWER(email) LIKE :term&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;term: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;%&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;term&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;downcase&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Good: COALESCE for fallbacks&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Arel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;COALESCE(last_message_at, created_at) DESC&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Good: CASE for conditional ordering&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Arel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;CASE WHEN status = &apos;urgent&apos; THEN 0 ELSE 1 END&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;✅ Process Dates in Ruby&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Good: Fetch timestamps, format in Ruby&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;date_buckets_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;pluck&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filter_map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strftime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;%Y-%m&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;uniq&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;24&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;24-patterns-to-avoid&quot;&gt;2.4 Patterns to Avoid&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;❌ MySQL-Specific Functions&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Bad: DATE_FORMAT is MySQL-only&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;month_sql&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;DATE_FORMAT(created_at, &apos;%Y-%m&apos;)&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# PostgreSQL equivalent: TO_CHAR(created_at, &apos;YYYY-MM&apos;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Better: Process in Ruby (see above)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;❌ Database-Specific Syntax&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Bad: MySQL&apos;s IFNULL&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;IFNULL(deleted_at, NOW()) &amp;gt; ?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ago&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Good: Standard COALESCE&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;COALESCE(deleted_at, CURRENT_TIMESTAMP) &amp;gt; ?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ago&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;❌ String Concatenation Operators&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Bad: MySQL uses CONCAT(), PostgreSQL uses ||&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;CONCAT(first_name, &apos; &apos;, last_name) AS full_name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Good: Use Ruby&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;full_name&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first_name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;last_name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;3-the-migration-process&quot;&gt;3. The Migration Process&lt;/h2&gt;

&lt;h3 id=&quot;31-pre-migration-audit&quot;&gt;3.1 Pre-Migration Audit&lt;/h3&gt;

&lt;p&gt;Before touching any configuration, we audited the entire codebase:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Find MySQL-specific SQL&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;DATE_FORMAT&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\|&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;GROUP_CONCAT&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\|&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;IFNULL&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\|&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;BINARY&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\s&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; app/

&lt;span class=&quot;c&quot;&gt;# Find raw SQL that might be problematic&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\.&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;where.*Arel&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\.&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\|&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\|&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\.&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; app/

&lt;span class=&quot;c&quot;&gt;# Check for MySQL gem references&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mysql&quot;&lt;/span&gt; Gemfile config/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Our findings:&lt;/strong&gt;&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Issue&lt;/th&gt;
      &lt;th&gt;Location&lt;/th&gt;
      &lt;th&gt;Fix&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DATE_FORMAT&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/models/access_request.rb&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Rewrote using Ruby&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mysql2&lt;/code&gt; gem&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Replaced with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pg&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;MySQL config&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/database.yml&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Rewrote for PostgreSQL&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Docker MySQL&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose.dev.yml&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Replaced with PostgreSQL&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;32-the-critical-fix&quot;&gt;3.2 The Critical Fix&lt;/h3&gt;

&lt;p&gt;The only code change required was in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_request.rb&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Before: MySQL-specific&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;date_buckets_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;qcol&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;quote_column_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;month_sql&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;DATE_FORMAT(&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qcol&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;, &apos;%Y-%m&apos;)&quot;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# ❌ MySQL-only&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;for_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Arel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;month_sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pluck&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Arel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;month_sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# After: Database-agnostic Ruby&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;date_buckets_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DATE_BUCKET_FIELDS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;for_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;not&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pluck&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filter_map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strftime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;%Y-%m&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;uniq&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;24&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filter_map&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;month&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;start_at&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;zone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;month&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;-01&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start_at&lt;/span&gt;

      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;month&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strftime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;%B %Y&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;from: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;beginning_of_month&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;beginning_of_day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;iso8601&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;to: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;end_of_month&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;end_of_day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;iso8601&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;33-configuration-changes&quot;&gt;3.3 Configuration Changes&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Gemfile:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Remove&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;mysql2&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;~&amp;gt; 0.5.6&apos;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Add&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pg&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;~&amp;gt; 1.5&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;config/database.yml:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;&amp;amp;default&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;adapter&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgresql&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;encoding&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;unicode&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;pool&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;%= ENV.fetch(&quot;RAILS_MAX_THREADS&quot;, 5) %&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;%= ENV.fetch(&quot;POSTGRES_USER&quot;, &quot;postgres&quot;) %&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;%= ENV.fetch(&quot;POSTGRES_PASSWORD&quot;, &quot;&quot;) %&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;%= ENV.fetch(&quot;POSTGRES_HOST&quot;, &quot;127.0.0.1&quot;) %&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;%= ENV.fetch(&quot;POSTGRES_PORT&quot;, 5432) %&amp;gt;&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;development&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*default&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;database&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;testapp_development&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*default&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;database&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;testapp_test&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;production&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;*default&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;database&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;%= ENV[&quot;POSTGRES_DB&quot;] %&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;docker-compose.dev.yml:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;postgres&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres:16&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;password&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;POSTGRES_DB&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;testapp_development&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;5433:5432&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;healthcheck&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;CMD-SHELL&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;pg_isready&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-U&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;postgres&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;interval&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;5s&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;5s&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;retries&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;20&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres_data:/var/lib/postgresql/data&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;postgres_data&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;34-json-to-jsonb-migration&quot;&gt;3.4 JSON to JSONB Migration&lt;/h3&gt;

&lt;p&gt;MySQL’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JSON&lt;/code&gt; type works, but PostgreSQL’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JSONB&lt;/code&gt; is superior:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# In migration files, change:&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;json&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:metadata&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# To:&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;jsonb&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Why JSONB?&lt;/strong&gt;&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Feature&lt;/th&gt;
      &lt;th&gt;JSON&lt;/th&gt;
      &lt;th&gt;JSONB&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Storage&lt;/td&gt;
      &lt;td&gt;Text (parsed each query)&lt;/td&gt;
      &lt;td&gt;Binary (pre-parsed)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Indexing&lt;/td&gt;
      &lt;td&gt;❌ Not supported&lt;/td&gt;
      &lt;td&gt;✅ GIN indexes&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Operators&lt;/td&gt;
      &lt;td&gt;Basic&lt;/td&gt;
      &lt;td&gt;Rich (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@&amp;gt;&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?|&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?&amp;amp;&lt;/code&gt;)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Query speed&lt;/td&gt;
      &lt;td&gt;Slower&lt;/td&gt;
      &lt;td&gt;Faster&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Write speed&lt;/td&gt;
      &lt;td&gt;Faster&lt;/td&gt;
      &lt;td&gt;Slightly slower&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;JSONB Query Examples:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Find avatars with specific skill in JSONB array&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;abilities @&amp;gt; ?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;ruby&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Find players with specific setting&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;settings -&amp;gt; &apos;notifications&apos; -&amp;gt;&amp;gt; &apos;email&apos; = ?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;true&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Check if key exists&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Product&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;metadata ? &apos;featured&apos;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;35-running-the-migration&quot;&gt;3.5 Running the Migration&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 1. Update gems&lt;/span&gt;
bundle &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 2. Create databases&lt;/span&gt;
rails db:create

&lt;span class=&quot;c&quot;&gt;# 3. Run migrations&lt;/span&gt;
rails db:migrate

&lt;span class=&quot;c&quot;&gt;# 4. Verify schema&lt;/span&gt;
rails db:schema:dump

&lt;span class=&quot;c&quot;&gt;# 5. Run test suite&lt;/span&gt;
bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rspec

&lt;span class=&quot;c&quot;&gt;# 6. Seed development data&lt;/span&gt;
rails db:seed
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Total downtime: Zero&lt;/strong&gt; (for development). Production required a maintenance window for data migration.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;4-postgresql-vs-mysql-a-technical-comparison&quot;&gt;4. PostgreSQL vs MySQL: A Technical Comparison&lt;/h2&gt;

&lt;h3 id=&quot;41-feature-comparison&quot;&gt;4.1 Feature Comparison&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Feature&lt;/th&gt;
      &lt;th&gt;MySQL&lt;/th&gt;
      &lt;th&gt;PostgreSQL&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;JSON Support&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;JSON type, basic functions&lt;/td&gt;
      &lt;td&gt;JSONB with GIN indexes, rich operators&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Full-Text Search&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;FULLTEXT indexes&lt;/td&gt;
      &lt;td&gt;tsvector/tsquery, ranking, dictionaries&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Geospatial&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Basic spatial&lt;/td&gt;
      &lt;td&gt;PostGIS (industry standard)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Vector Search&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;❌ None&lt;/td&gt;
      &lt;td&gt;pgvector extension&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Graph Queries&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;❌ None&lt;/td&gt;
      &lt;td&gt;pgRouting, Apache AGE&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Arrays&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;❌ None&lt;/td&gt;
      &lt;td&gt;Native array types with operators&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Range Types&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;❌ None&lt;/td&gt;
      &lt;td&gt;int4range, daterange, etc.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Check Constraints&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;✅ Supported&lt;/td&gt;
      &lt;td&gt;✅ Supported&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Partial Indexes&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;❌ None&lt;/td&gt;
      &lt;td&gt;✅ Supported&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Expression Indexes&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;❌ Limited&lt;/td&gt;
      &lt;td&gt;✅ Full support&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;CTEs (WITH queries)&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;✅ Basic&lt;/td&gt;
      &lt;td&gt;✅ Recursive, materialized&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Window Functions&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;✅ Supported&lt;/td&gt;
      &lt;td&gt;✅ More advanced&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;UPSERT&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON DUPLICATE KEY&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ON CONFLICT&lt;/code&gt; (more flexible)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Transactions&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;✅ InnoDB&lt;/td&gt;
      &lt;td&gt;✅ MVCC&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Replication&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;✅ Built-in&lt;/td&gt;
      &lt;td&gt;✅ Streaming, logical&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;42-performance-characteristics&quot;&gt;4.2 Performance Characteristics&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;MySQL Strengths:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Simple read-heavy workloads&lt;/li&gt;
  &lt;li&gt;Large-scale web applications with basic queries&lt;/li&gt;
  &lt;li&gt;When you need maximum reads/second on simple queries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;PostgreSQL Strengths:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Complex queries with many joins&lt;/li&gt;
  &lt;li&gt;Write-heavy workloads (better MVCC)&lt;/li&gt;
  &lt;li&gt;Data integrity (stricter by default)&lt;/li&gt;
  &lt;li&gt;Advanced data types and indexing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;43-why-postgresql-won-for-us&quot;&gt;4.3 Why PostgreSQL Won for Us&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. One Database for Everything&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;MySQL (primary data)
  + Pinecone (vector search)
  + Neo4j (graph queries)
  + Elasticsearch (full-text)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We have:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;PostgreSQL
  + pgvector (vector search)
  + pgRouting (graph queries)
  + Built-in full-text search
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2. Better Data Integrity&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;-- PostgreSQL: Strict by default&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;players&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;not-an-email&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;-- ERROR: violates check constraint&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- MySQL: Often silently truncates or converts&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;players&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;this string is way too long for the column&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;-- Silently truncated (depending on sql_mode)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;3. Superior Indexing&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Partial index: Only index active players&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;add_index&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:players&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;where: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;status = &apos;active&apos;&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Expression index: Index lowercased emails&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;add_index&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:players&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;LOWER(email)&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# GIN index on JSONB for fast JSON queries&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;add_index&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:avatars&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:abilities&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;using: :gin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;4. Native Array Support&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# PostgreSQL array columns&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:tags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;array: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Query arrays&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&apos;ruby&apos; = ANY(tags)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;tags @&amp;gt; ARRAY[?]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;ruby&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;rails&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;5-advanced-postgresql-features&quot;&gt;5. Advanced PostgreSQL Features&lt;/h2&gt;

&lt;h3 id=&quot;51-pgvector-ai-vector-embeddings&quot;&gt;5.1 pgvector: AI Vector Embeddings&lt;/h3&gt;

&lt;p&gt;Store and query vector embeddings directly in PostgreSQL:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Enable extension&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EnablePgvector&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Migration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;8.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;change&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;enable_extension&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;vector&apos;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Add vector column&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AddEmbeddingToAvatars&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Migration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;8.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;change&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;add_column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:avatars&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:embedding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;limit: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1536&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# OpenAI dimension&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;add_index&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:avatars&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:embedding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;using: :ivfflat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;opclass: :vector_cosine_ops&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/models/avatar.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Avatar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;similar_to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;embedding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;limit: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;not&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;embedding: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Arel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;embedding &amp;lt;=&amp;gt; &apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;embedding&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&apos;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Usage&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;similar_avatars&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Avatar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;similar_to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;openai_embedding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;limit: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;52-postgis-geospatial-queries&quot;&gt;5.2 PostGIS: Geospatial Queries&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Enable extension&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;enable_extension&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;postgis&apos;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Add geography column&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;add_column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:locations&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:coordinates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:st_point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;geographic: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;add_index&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:locations&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:coordinates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;using: :gist&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Find locations within 10km&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;ST_DWithin(coordinates, ST_MakePoint(?, ?)::geography, ?)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;longitude&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;latitude&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10_000&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# meters&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;53-pgrouting-graph-algorithms&quot;&gt;5.3 pgRouting: Graph Algorithms&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Find shortest path between locations&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;execute&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
  SELECT * FROM pgr_dijkstra(
    &apos;SELECT id, source, target, cost FROM edges&apos;,
    &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start_node&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;,
    &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end_node&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;,
    directed := true
  )
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SQL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;54-full-text-search&quot;&gt;5.4 Full-Text Search&lt;/h3&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Add tsvector column&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;add_column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:articles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:searchable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:tsvector&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;add_index&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:articles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:searchable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;using: :gin&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Keep it updated with a trigger&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;execute&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
  CREATE TRIGGER articles_searchable_update
  BEFORE INSERT OR UPDATE ON articles
  FOR EACH ROW EXECUTE FUNCTION
  tsvector_update_trigger(searchable, &apos;pg_catalog.english&apos;, title, body);
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SQL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Search with ranking&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Article&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;searchable @@ plainto_tsquery(&apos;english&apos;, ?)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
       &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Arel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ts_rank(searchable, plainto_tsquery(&apos;english&apos;, &apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&apos;)) DESC&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;6-multi-schema-architecture&quot;&gt;6. Multi-Schema Architecture&lt;/h2&gt;

&lt;h3 id=&quot;61-what-are-postgresql-schemas&quot;&gt;6.1 What Are PostgreSQL Schemas?&lt;/h3&gt;

&lt;p&gt;PostgreSQL schemas are &lt;strong&gt;namespaces within a single database&lt;/strong&gt;. Think of them as folders for your tables:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;database: testapp_production
├── public (default schema)
│   ├── players
│   ├── avatars
│   └── companies
├── analytics
│   ├── events
│   ├── page_views
│   └── conversions
├── audit
│   ├── player_actions
│   └── api_requests
└── cache
    ├── cached_avatars
    └── cached_searches
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;62-benefits-of-multi-schema-architecture&quot;&gt;6.2 Benefits of Multi-Schema Architecture&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Logical Separation Without Multiple Databases&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# One connection pool, multiple schemas&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AnalyticsEvent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;table_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;analytics.events&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AuditLog&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;table_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;audit.player_actions&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2. Different Permissions Per Schema&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;-- Analytics team can only access analytics schema&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;GRANT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;USAGE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SCHEMA&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;analytics&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;analytics_role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;GRANT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ALL&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TABLES&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IN&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SCHEMA&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;analytics&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;analytics_role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- No access to public schema&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;REVOKE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ALL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SCHEMA&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;analytics_role&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;3. Simplified Backup Strategies&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Backup only the audit schema&lt;/span&gt;
pg_dump &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; audit testapp_production &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; audit_backup.sql

&lt;span class=&quot;c&quot;&gt;# Backup everything except cache&lt;/span&gt;
pg_dump &lt;span class=&quot;nt&quot;&gt;-N&lt;/span&gt; cache testapp_production &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; production_backup.sql
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;4. Schema-Level Maintenance&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;-- Truncate all cache tables without affecting production&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;TRUNCATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cached_avatars&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cached_searches&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- Drop and recreate analytics schema&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;DROP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SCHEMA&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;analytics&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CASCADE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SCHEMA&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;analytics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;63-setting-up-multi-schema-in-rails&quot;&gt;6.3 Setting Up Multi-Schema in Rails&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Migration to create schemas:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CreateAnalyticsSchema&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Migration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;8.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;up&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;execute&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;CREATE SCHEMA IF NOT EXISTS analytics&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;execute&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;CREATE SCHEMA IF NOT EXISTS audit&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;execute&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;CREATE SCHEMA IF NOT EXISTS cache&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;down&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;execute&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;DROP SCHEMA IF EXISTS cache CASCADE&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;execute&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;DROP SCHEMA IF EXISTS audit CASCADE&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;execute&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;DROP SCHEMA IF EXISTS analytics CASCADE&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Models with schema prefixes:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/models/analytics/event.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Analytics&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Event&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;table_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;analytics.events&apos;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# app/models/audit/player_action.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Audit&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserAction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;table_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;audit.player_actions&apos;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Cross-schema queries work seamlessly:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Join across schemas&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;JOIN audit.player_actions ON audit.player_actions.player_id = players.id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;audit.player_actions.action = ?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;login&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;64-schemas-vs-multiple-databases&quot;&gt;6.4 Schemas vs. Multiple Databases&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Aspect&lt;/th&gt;
      &lt;th&gt;Multiple Databases&lt;/th&gt;
      &lt;th&gt;Multiple Schemas&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Connection pools&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Separate per DB&lt;/td&gt;
      &lt;td&gt;Shared&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Cross-queries&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Complex/impossible&lt;/td&gt;
      &lt;td&gt;Native JOINs&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Transactions&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Distributed (2PC)&lt;/td&gt;
      &lt;td&gt;Single transaction&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Backups&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Per database&lt;/td&gt;
      &lt;td&gt;Per schema or together&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Permissions&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Database-level&lt;/td&gt;
      &lt;td&gt;Schema-level (granular)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Migrations&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Separate per DB&lt;/td&gt;
      &lt;td&gt;Single migration&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Complexity&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Higher&lt;/td&gt;
      &lt;td&gt;Lower&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Rails support&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Multiple configs&lt;/td&gt;
      &lt;td&gt;Single config&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;Our recommendation:&lt;/strong&gt; Use schemas for logical separation within the same application. Use separate databases only for truly isolated services or multi-tenant SaaS.&lt;/p&gt;

&lt;h3 id=&quot;65-when-to-use-each-pattern&quot;&gt;6.5 When to Use Each Pattern&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Use Multiple Schemas When:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Separating concerns (analytics, audit, cache)&lt;/li&gt;
  &lt;li&gt;Different retention policies per data type&lt;/li&gt;
  &lt;li&gt;Granular permission control needed&lt;/li&gt;
  &lt;li&gt;Data is related and needs cross-queries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use Multiple Databases When:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Complete data isolation required (multi-tenant)&lt;/li&gt;
  &lt;li&gt;Different scaling requirements&lt;/li&gt;
  &lt;li&gt;Regulatory compliance requires separation&lt;/li&gt;
  &lt;li&gt;Microservices with independent lifecycles&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;7-migration-tips-and-lessons-learned&quot;&gt;7. Migration Tips and Lessons Learned&lt;/h2&gt;

&lt;h3 id=&quot;71-before-you-start&quot;&gt;7.1 Before You Start&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Audit your codebase thoroughly&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Find all raw SQL&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-rn&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;execute&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\|\.&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\|&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Arel&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\.&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;sql&quot;&lt;/span&gt; app/ &lt;span class=&quot;nt&quot;&gt;--include&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;*.rb&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Find MySQL-specific functions&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-rn&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;DATE_FORMAT&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\|&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;IFNULL&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\|&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;GROUP_CONCAT&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\|&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;BINARY&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\|&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;STRAIGHT_JOIN&quot;&lt;/span&gt; app/

&lt;span class=&quot;c&quot;&gt;# Check gems for MySQL dependencies&lt;/span&gt;
bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;gem dependency mysql2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2. Run tests on PostgreSQL locally first&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# config/database.yml for CI&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;adapter&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgresql&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;database&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;myapp_test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;3. Document all discrepancies&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a migration checklist (like our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mysql_to_postgresql.md&lt;/code&gt;) tracking every issue found.&lt;/p&gt;

&lt;h3 id=&quot;72-data-migration-strategies&quot;&gt;7.2 Data Migration Strategies&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Option A: Fresh Start (Recommended for Development)&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# On PostgreSQL&lt;/span&gt;
rails db:create db:migrate db:seed
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Option B: pgloader (For Production Data)&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Install pgloader&lt;/span&gt;
brew &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;pgloader  &lt;span class=&quot;c&quot;&gt;# or apt-get&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Create migration script&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; migrate.load &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;
LOAD DATABASE
  FROM mysql://root:password@localhost/myapp_production
  INTO postgresql://postgres@localhost/myapp_production

WITH include drop, create tables, create indexes, reset sequences

SET work_mem to &apos;16MB&apos;, maintenance_work_mem to &apos;512 MB&apos;

CAST type datetime to timestamptz using zero-dates-to-null,
     type date to date using zero-dates-to-null

ALTER SCHEMA &apos;myapp_production&apos; RENAME TO &apos;public&apos;;
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF

&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Run migration&lt;/span&gt;
pgloader migrate.load
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Option C: Rails-Native Export/Import&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Export from MySQL&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;players: :environment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;players.json&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;w&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_json&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Import to PostgreSQL&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:import&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;players: :environment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;readlines&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;players.json&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;73-common-gotchas&quot;&gt;7.3 Common Gotchas&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Auto-increment vs. SERIAL&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;MySQL uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AUTO_INCREMENT&lt;/code&gt;, PostgreSQL uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SERIAL&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IDENTITY&lt;/code&gt;. Rails handles this automatically, but check sequences after migration:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;-- Fix sequence if IDs are out of sync&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;setval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;users_id_seq&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;MAX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;players&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2. Boolean Handling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;MySQL often stores booleans as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TINYINT(1)&lt;/code&gt;. PostgreSQL uses native &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BOOLEAN&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# May need explicit casting in raw SQL&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;active = true&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# PostgreSQL&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;active = 1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;     &lt;span class=&quot;c1&quot;&gt;# MySQL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;3. Case Sensitivity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;PostgreSQL string comparisons are case-sensitive by default:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# MySQL: case-insensitive by default&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;email: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;Test@Example.com&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Finds test@example.com&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# PostgreSQL: case-sensitive&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;LOWER(email) = LOWER(?)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Test@Example.com&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Or use citext extension for case-insensitive columns&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;4. Group By Strictness&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;PostgreSQL requires all non-aggregated columns in GROUP BY:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# MySQL: Allows this (picks arbitrary value)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;players.*, COUNT(orders.id)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:orders&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;players.id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# PostgreSQL: Must include all selected columns&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;players.id, players.email, COUNT(orders.id)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:orders&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;players.id, players.email&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;74-post-migration-checklist&quot;&gt;7.4 Post-Migration Checklist&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 1. Verify all tests pass&lt;/span&gt;
bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rspec

&lt;span class=&quot;c&quot;&gt;# 2. Check for deprecation warnings&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;RAILS_ENV&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;test &lt;/span&gt;rails runner &lt;span class=&quot;s2&quot;&gt;&quot;puts ActiveRecord::Base.connection.adapter_name&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 3. Regenerate schema&lt;/span&gt;
rails db:schema:dump

&lt;span class=&quot;c&quot;&gt;# 4. Verify schema.rb has PostgreSQL syntax&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;create_table&quot;&lt;/span&gt; db/schema.rb | &lt;span class=&quot;nb&quot;&gt;head&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 5. Run security scan&lt;/span&gt;
bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;brakeman &lt;span class=&quot;nt&quot;&gt;-q&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-w2&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 6. Regenerate API documentation&lt;/span&gt;
bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rails rswag:specs:swaggerize

&lt;span class=&quot;c&quot;&gt;# 7. Performance baseline&lt;/span&gt;
rails runner &lt;span class=&quot;s2&quot;&gt;&quot;Benchmark.measure { Player.count }&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;75-lessons-we-learned&quot;&gt;7.5 Lessons We Learned&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Write database-agnostic code from day one&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The single MySQL-specific function we had took 30 minutes to rewrite. If we’d had dozens, it would have been a multi-day effort.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Use JSONB defaults in migrations&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Always specify defaults for JSONB&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;jsonb&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;jsonb&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:tags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;3. Leverage PostgreSQL-specific features gradually&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Don’t rewrite everything immediately. Start with:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;JSONB for new features&lt;/li&gt;
  &lt;li&gt;Partial indexes for performance&lt;/li&gt;
  &lt;li&gt;Full-text search when needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Monitor query performance post-migration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;PostgreSQL’s query planner is different. Some queries may need new indexes:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Add explain logging in development&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logger&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;STDOUT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Player&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;status: :active&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;explain&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;5. Document your extensions&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# db/migrate/001_enable_extensions.rb&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EnableExtensions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Migration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;8.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;change&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Core extensions we use&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;enable_extension&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pgcrypto&apos;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# UUID generation&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;enable_extension&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;citext&apos;&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# Case-insensitive text&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Feature-specific (enable when needed)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# enable_extension &apos;vector&apos;   # AI embeddings&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# enable_extension &apos;postgis&apos;  # Geospatial&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Migrating from MySQL to PostgreSQL doesn’t have to be painful. With database-agnostic code patterns and a systematic approach, our migration required:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;1 code fix&lt;/strong&gt; (DATE_FORMAT → Ruby)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;4 configuration file updates&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;0 model changes&lt;/strong&gt; (beyond JSON → JSONB)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;0 downtime&lt;/strong&gt; (for development)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The benefits far outweigh the effort:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Before (MySQL + Services)&lt;/th&gt;
      &lt;th&gt;After (PostgreSQL)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;3+ external services&lt;/td&gt;
      &lt;td&gt;1 database&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Multiple connection pools&lt;/td&gt;
      &lt;td&gt;Single pool&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Cross-service latency&lt;/td&gt;
      &lt;td&gt;Native queries&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Complex sync logic&lt;/td&gt;
      &lt;td&gt;Referential integrity&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Higher infrastructure cost&lt;/td&gt;
      &lt;td&gt;Lower cost&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;PostgreSQL isn’t just a MySQL replacement—it’s a &lt;strong&gt;platform for building sophisticated applications&lt;/strong&gt;. With extensions like pgvector, PostGIS, and pgRouting, you can consolidate your entire data layer into a single, powerful database.&lt;/p&gt;

&lt;h3 id=&quot;key-takeaways&quot;&gt;Key Takeaways&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Write database-agnostic code&lt;/strong&gt; — Use ActiveRecord/Arel, avoid raw SQL functions&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Audit before migrating&lt;/strong&gt; — Find MySQL-specific code early&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Use JSONB over JSON&lt;/strong&gt; — Better indexing, better queries&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Consider schemas over databases&lt;/strong&gt; — Simpler architecture, better performance&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Leverage PostgreSQL features&lt;/strong&gt; — Partial indexes, arrays, full-text search&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Plan for extensions&lt;/strong&gt; — pgvector, PostGIS, pgRouting when needed&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Test thoroughly&lt;/strong&gt; — Run your full suite on PostgreSQL before production&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The best database is the one that grows with your needs. PostgreSQL does exactly that.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;PostgreSQL Official Docs&lt;/strong&gt;: &lt;a href=&quot;https://www.postgresql.org/docs/&quot;&gt;postgresql.org/docs&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;pgvector Extension&lt;/strong&gt;: &lt;a href=&quot;https://github.com/pgvector/pgvector&quot;&gt;github.com/pgvector/pgvector&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;PostGIS Documentation&lt;/strong&gt;: &lt;a href=&quot;https://postgis.net/docs/&quot;&gt;postgis.net/docs&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;pgRouting Documentation&lt;/strong&gt;: &lt;a href=&quot;https://pgrouting.org/&quot;&gt;pgrouting.org&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Rails PostgreSQL Guide&lt;/strong&gt;: &lt;a href=&quot;https://guides.rubyonrails.org/active_record_postgresql.html&quot;&gt;guides.rubyonrails.org/active_record_postgresql.html&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;pgloader&lt;/strong&gt;: &lt;a href=&quot;https://pgloader.io/&quot;&gt;pgloader.io&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;This post documents our migration of the TestApp API from MySQL to PostgreSQL. The patterns described here enabled a migration with minimal code changes and zero regressions—proving that investment in database-agnostic code pays dividends when requirements evolve.&lt;/em&gt;&lt;/p&gt;

</description>
        <pubDate>Wed, 03 Dec 2025 00:00:00 +0000</pubDate>
        <link>https://lukin.io/blog/mysql-to-postgresql-migration-guide/</link>
        <guid isPermaLink="true">https://lukin.io/blog/mysql-to-postgresql-migration-guide/</guid>
        
        <category>rails</category>
        
        <category>postgresql</category>
        
        <category>mysql</category>
        
        <category>migration</category>
        
        <category>database</category>
        
        <category>orm</category>
        
        <category>active-record</category>
        
        <category>best-practices</category>
        
        
        <category>engineering</category>
        
        <category>rails</category>
        
        <category>databases</category>
        
        <category>infrastructure</category>
        
      </item>
    
  </channel>
</rss>
