<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Snippets of Code]]></title><description><![CDATA[A "programming language" learning journal]]></description><link>https://snippetsofcode.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!oXJr!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsnippetsofcode.substack.com%2Fimg%2Fsubstack.png</url><title>Snippets of Code</title><link>https://snippetsofcode.substack.com</link></image><generator>Substack</generator><lastBuildDate>Sat, 22 Aug 2026 06:38:01 GMT</lastBuildDate><atom:link href="https://snippetsofcode.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Rafael George]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[snippetsofcode@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[snippetsofcode@substack.com]]></itunes:email><itunes:name><![CDATA[Rafael George]]></itunes:name></itunes:owner><itunes:author><![CDATA[Rafael George]]></itunes:author><googleplay:owner><![CDATA[snippetsofcode@substack.com]]></googleplay:owner><googleplay:email><![CDATA[snippetsofcode@substack.com]]></googleplay:email><googleplay:author><![CDATA[Rafael George]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Putting Snippets of Code on hiatus (and what I learned doing it)]]></title><description><![CDATA[I started this newsletter to share technical solutions - code snippets, debugging wins, elegant patterns.]]></description><link>https://snippetsofcode.substack.com/p/putting-snippets-of-code-on-hiatus</link><guid isPermaLink="false">https://snippetsofcode.substack.com/p/putting-snippets-of-code-on-hiatus</guid><dc:creator><![CDATA[Rafael George]]></dc:creator><pubDate>Wed, 31 Dec 2025 18:42:42 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1593505681742-8cbb6f44de25?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>I started this newsletter to share technical solutions - code snippets, debugging wins, elegant patterns.</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1593505681742-8cbb6f44de25?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1593505681742-8cbb6f44de25?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 424w, https://images.unsplash.com/photo-1593505681742-8cbb6f44de25?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 848w, https://images.unsplash.com/photo-1593505681742-8cbb6f44de25?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1272w, https://images.unsplash.com/photo-1593505681742-8cbb6f44de25?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1593505681742-8cbb6f44de25?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" width="3000" height="1688" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1593505681742-8cbb6f44de25?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1688,&quot;width&quot;:3000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;red and white no smoking sign&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="red and white no smoking sign" title="red and white no smoking sign" srcset="https://images.unsplash.com/photo-1593505681742-8cbb6f44de25?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 424w, https://images.unsplash.com/photo-1593505681742-8cbb6f44de25?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 848w, https://images.unsplash.com/photo-1593505681742-8cbb6f44de25?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1272w, https://images.unsplash.com/photo-1593505681742-8cbb6f44de25?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>But here&#8217;s what happened: I stopped caring about <em>how</em> I solved technical problems and became obsessed with <em>why</em> those problems existed in the first place.</p><p>The interesting question isn&#8217;t &#8220;how do I implement feature X&#8221; - it&#8217;s &#8220;why are we building this when nobody asked for it?&#8221; The breakthrough isn&#8217;t a clever algorithm - it&#8217;s recognizing the organizational dysfunction that makes simple features take months.</p><p>So I&#8217;m putting Snippets of Code on hiatus. Not because I failed to maintain it, but because maintaining it would mean ignoring what I actually care about now.</p><p><strong>If you want to follow where this leads:</strong></p><p>Join me at Snippets Press, where I&#8217;m documenting the messy journey from developer to business owner - building a sustainable technical business while challenging the conventional wisdom that got us all burned out in the first place.</p><p>Real metrics. Real failures. Real businesses. No hustle porn.</p><p><a href="https://snippetsoftext.substack.com/subscribe">https://snippetsoftext.substack.com/subscribe</a></p><p>Thanks for reading, Rafa</p>]]></content:encoded></item><item><title><![CDATA[How Service-to-Service Authentication Nearly Broke Our API]]></title><description><![CDATA[We needed to validate admin permissions across services.]]></description><link>https://snippetsofcode.substack.com/p/how-service-to-service-authentication</link><guid isPermaLink="false">https://snippetsofcode.substack.com/p/how-service-to-service-authentication</guid><dc:creator><![CDATA[Rafael George]]></dc:creator><pubDate>Thu, 04 Dec 2025 15:01:05 GMT</pubDate><content:encoded><![CDATA[<blockquote><p>Simple requirement: authentication service checks if users can perform admin operations, but permission data lives in a legacy system.</p></blockquote><p>You know that moment when a &#8220;simple&#8221; feature reveals fundamental architecture problems? This was mine.</p><p>In this issue, I&#8217;ll take you through building a secure service-to-service authentication endpoint that solved a critical dependency without duplicating permission logic or compromising security. No theory dumps&#8212;just real code, real problems, and real solutions.</p><p><strong>What you&#8217;ll learn:</strong></p><ul><li><p>How to design secure service-to-service API authentication</p></li><li><p>Why permission logic duplication creates maintenance nightmares</p></li><li><p>Strategies for handling legacy system dependencies</p></li><li><p>Practical approaches to audit logging for compliance</p></li></ul><h2>The Problem</h2><p>Here&#8217;s what we were dealing with&#8212;a feature blocked by architectural constraints:</p><pre><code><code># authentication-service needs to check permissions
# but permissions live in legacy-backend
# and we can&#8217;t duplicate the permission logic

def can_user_manage_security?(user_id, organization_id)
  # ??? How do we check this without direct database access?
  # ??? How do we authenticate service-to-service calls securely?
  # ??? How do we avoid duplicating complex permission logic?
end
</code></code></pre><p><strong>Warning Signs:</strong></p><ul><li><p>Critical feature blocked by service boundary constraints</p></li><li><p>Temptation to duplicate permission logic across services</p></li><li><p>No existing pattern for secure service-to-service calls</p></li><li><p>Legacy permission system too complex to safely extract</p></li><li><p>Compliance requirements for permission check auditing</p></li></ul><h2>From Blocked Feature to Secure API</h2><h3>Rejecting the Easy (Wrong) Solutions</h3><p>The first instinct: just duplicate the permission logic in authentication-service.</p><p><strong>Before (Tempting but Wrong):</strong></p><pre><code><code># In authentication-service (BAD APPROACH)
class PermissionChecker
  def check_admin_access(user_id, organization_id)
    # Copy permission logic from legacy-backend
    user = User.find(user_id)
    org = Organization.find(organization_id)
    
    # Now we have duplicate permission logic to maintain
    # Changes in legacy-backend won&#8217;t sync here
    # Security bugs get fixed in one place but not the other
  end
end
</code></code></pre><p><strong>Why This Fails:</strong></p><ul><li><p>Permission logic becomes inconsistent across services</p></li><li><p>Security updates require coordinating multiple deployments</p></li><li><p>Business rule changes require updating two codebases</p></li><li><p>Testing becomes exponentially more complex</p></li></ul><p><strong>Impact:</strong></p><ul><li><p>Rejected this approach immediately despite deadline pressure</p></li><li><p>Chose to build proper service boundary instead</p></li></ul><h3>Building Secure Service Authentication</h3><p>The second realization: we need service-to-service authentication that doesn&#8217;t rely on user credentials.</p><p><strong>Before (User-Based Auth):</strong></p><pre><code><code># Using user credentials for service calls (WRONG)
response = HTTParty.get(
  &#8220;#{legacy_backend_url}/api/permissions/check&#8221;,
  headers: { 
    &#8216;Authorization&#8217; =&gt; &#8220;Bearer #{user_session_token}&#8221; 
  }
)
# Problems:
# - User token can expire mid-operation
# - Requires managing user sessions in service context
# - Security audit trail shows user, not service
</code></code></pre><p><strong>After (Service Authentication):</strong></p><pre><code><code># Service-to-service with pre-shared key
class LegacyBackendClient
  def check_permissions(user_id:, permission_type:, org_id: nil)
    response = HTTParty.get(
      &#8220;#{base_url}/api/admin/permissions/check_access&#8221;,
      query: {
        user_id: user_id,
        permission_type: permission_type,
        organization_id: org_id
      },
      headers: {
        &#8216;X-Service-API-Key&#8217; =&gt; service_api_key,
        &#8216;Content-Type&#8217; =&gt; &#8216;application/json&#8217;
      }
    )
    
    handle_response(response)
  end
  
  private
  
  def service_api_key
    Settings.legacy_backend.service_api_key
  end
end
</code></code></pre><p><strong>Impact:</strong></p><ul><li><p>Service identity separate from user identity</p></li><li><p>No token expiration issues</p></li><li><p>Clear audit trail showing service-to-service calls</p></li><li><p>Failed authentication distinguishable from failed authorization</p></li></ul><h3>Designing the Permission Check Endpoint</h3><p>The breakthrough: create a minimal API that validates permissions without exposing business logic.</p><pre><code><code># In legacy-backend
module Api
  module Admin
    module Permissions
      class CheckAccessController &lt; BaseController
        before_action :authenticate_service!
        
        def check_access
          validator = PermissionCheckValidator.new(params)
          
          unless validator.valid?
            return render json: { 
              error: &#8216;Invalid parameters&#8217;,
              details: validator.errors 
            }, status: :bad_request
          end
          
          result = PermissionChecker.check(
            user_id: params[:user_id],
            permission_type: params[:permission_type],
            organization_id: resolved_organization_id
          )
          
          audit_permission_check(result)
          
          render json: format_response(result)
        end
        
        private
        
        def authenticate_service!
          provided_key = request.headers[&#8217;X-Service-API-Key&#8217;]
          expected_key = Settings.auth_service.service_api_key
          
          unless ActiveSupport::SecurityUtils.secure_compare(
            provided_key.to_s, 
            expected_key.to_s
          )
            render json: { error: &#8216;Unauthorized&#8217; }, 
                   status: :unauthorized
          end
        end
        
        def resolved_organization_id
          params[:organization_id] || current_user_default_org_id
        end
        
        def audit_permission_check(result)
          AuditLog.create!(
            event_type: &#8216;PERMISSION_CHECK_REQUEST&#8217;,
            service: &#8216;authentication-service&#8217;,
            user_id: params[:user_id],
            permission_type: params[:permission_type],
            result: result.access_level,
            timestamp: Time.current
          )
        end
      end
    end
  end
end
</code></code></pre><p><strong>Key Design Decisions:</strong></p><ol><li><p><strong>Service authentication before any processing</strong></p></li><li><p><strong>Parameter validation with specific error messages</strong></p></li><li><p><strong>Default organization handling</strong> (uses user&#8217;s current org when not specified)</p></li><li><p><strong>Audit logging for compliance</strong> (every check gets logged)</p></li><li><p><strong>Secure comparison</strong> to prevent timing attacks</p></li></ol><p><strong>Impact:</strong></p><ul><li><p>Authentication-service can now validate permissions securely</p></li><li><p>No permission logic duplication</p></li><li><p>Complete audit trail for compliance</p></li><li><p>Clear error messages for debugging</p></li></ul><h3>Handling the YAML Configuration Hell</h3><p>The unexpected problem: documentation generation breaking production.</p><p><strong>The Issue:</strong></p><pre><code><code># swagger/v1/swagger.yaml
mfa_methods:
&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD
  - kind: authenticator
    prompt: 
=======
  - kind: sms
    prompt: &#8220;***-***-1234&#8221;
&gt;&gt;&gt;&gt;&gt;&gt;&gt; feature/mfa-recovery
</code></code></pre><p><strong>What Happened:</strong></p><ul><li><p>Merge conflict markers left in YAML</p></li><li><p>Swagger docs endpoint returning 500 errors</p></li><li><p>Production API documentation inaccessible</p></li><li><p>Developers couldn&#8217;t see endpoint specifications</p></li></ul><p><strong>The Fix:</strong></p><pre><code><code># Validate YAML before committing
ruby -e &#8220;require &#8216;yaml&#8217;; YAML.load_file(&#8217;swagger/v1/swagger.yaml&#8217;); puts &#8216;YAML OK&#8217;&#8221;

# Add to pre-commit hook
#!/bin/bash
for file in swagger/**/*.yaml; do
  ruby -e &#8220;require &#8216;yaml&#8217;; YAML.load_file(&#8217;$file&#8217;)&#8221; || exit 1
done
</code></code></pre><p><strong>Impact:</strong></p><ul><li><p>Caught YAML errors before deployment</p></li><li><p>Documentation stayed accessible</p></li><li><p>Added validation to CI pipeline</p></li></ul><blockquote><p>The breakthrough came when I stopped trying to solve the permission problem and started solving the service boundary problem.</p></blockquote><p>The real issue wasn&#8217;t &#8220;how do we check permissions&#8221;&#8212;it was &#8220;how do we maintain a clean boundary between services while enabling necessary cross-service operations?&#8221;</p><p>Answer: secure, audited API endpoints that expose capability without exposing implementation.</p><blockquote><p>After implementing service-to-service authentication:</p></blockquote><ul><li><p>Permission check latency: 45ms average (acceptable for our use case)</p></li><li><p>Zero permission logic duplication</p></li><li><p>100% audit coverage for compliance</p></li><li><p>Feature unblocked after 2 days instead of projected 2 weeks</p></li><li><p>Zero security incidents in 6 months of production use</p></li></ul><h2>The Final Result</h2><pre><code><code># Clean service boundary with proper authentication
class SecurityOperationsService
  def can_manage_mfa?(user_id:, organization_id:)
    result = legacy_backend_client.check_permissions(
      user_id: user_id,
      permission_type: &#8216;mfa_management&#8217;,
      org_id: organization_id
    )
    
    result.access_level.in?([&#8217;read&#8217;, &#8216;write&#8217;])
  end
  
  def can_modify_users?(user_id:, organization_id:)
    result = legacy_backend_client.check_permissions(
      user_id: user_id,
      permission_type: &#8216;user_management&#8217;,
      org_id: organization_id
    )
    
    result.access_level == &#8216;write&#8217;
  end
end

# Response format
{
  &#8220;access_level&#8221;: &#8220;write&#8221;,
  &#8220;user_id&#8221;: &#8220;550e8400-e29b-41d4-a716-446655440000&#8221;,
  &#8220;permission_type&#8221;: &#8220;mfa_management&#8221;,
  &#8220;organization_id&#8221;: &#8220;660e8400-e29b-41d4-a716-446655440000&#8221;,
  &#8220;timestamp&#8221;: &#8220;2025-01-15T10:30:00Z&#8221;
}
</code></code></pre><p><strong>Key Improvements:</strong></p><ul><li><p>Secure service-to-service authentication</p></li><li><p>No permission logic duplication</p></li><li><p>Complete audit trail</p></li><li><p>Clean service boundaries maintained</p></li></ul><h2>Monday Morning Action Items</h2><ol><li><p><strong>Quick Wins (5-Minute Changes)</strong></p><ul><li><p>Identify services making cross-service calls with user credentials</p></li><li><p>Check if you have service-to-service authentication</p></li><li><p>Audit YAML configuration files for merge conflicts</p></li></ul></li><li><p><strong>Next Steps</strong></p><ul><li><p>Implement service authentication for one critical cross-service call</p></li><li><p>Add audit logging to permission checks</p></li><li><p>Create YAML validation in CI pipeline</p></li><li><p>Document service authentication patterns for team</p></li></ul></li></ol><h2>Your Turn!</h2><h3>The Service Authentication Audit</h3><p>Analyze your microservices architecture:</p><pre><code><code># Questions to ask
- How many services need to check permissions owned by other services?
- Are you using user credentials for service-to-service calls?
- Do you have duplicated permission logic across services?
- Can you audit all permission checks for compliance?
- What happens if service API keys leak?
</code></code></pre><p><strong>Discussion Prompts:</strong></p><ul><li><p>How do you currently handle cross-service authentication?</p></li><li><p>What permission logic is duplicated across your services?</p></li><li><p>How do you audit service-to-service operations?</p></li></ul><h2>What&#8217;s Next?</h2><p>Next week: &#8220;The Microservices Fallacy: When Service Boundaries Create More Problems Than They Solve&#8221; - Why splitting every feature into its own service often makes systems harder to maintain, not easier.</p><div><hr></div><p><strong>Useful Resources:</strong></p><ul><li><p><a href="https://tools.ietf.org/html/rfc7523">OAuth 2.0 for Service Accounts</a> - Standard approaches to service authentication</p></li><li><p><a href="https://cheatsheetseries.owasp.org/">Secure Coding Guidelines</a> - OWASP guidance on API security</p></li><li><p><a href="https://yaml.org/spec/">YAML Specification</a> - Understanding YAML parsing edge cases</p></li></ul><div><hr></div><p>Found this useful? Share it with a developer wrestling with microservices boundaries. Reply with your service authentication patterns&#8212;I&#8217;m curious what&#8217;s working in production.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://snippetsofcode.substack.com/p/how-service-to-service-authentication?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://snippetsofcode.substack.com/p/how-service-to-service-authentication?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><p>Happy coding!</p><div><hr></div><p><strong>Tips and Notes:</strong></p><ul><li><p>Note: Always use constant-time comparison for API keys to prevent timing attacks</p></li><li><p>Pro Tip: Service API keys should be separate from user credentials and rotatable without downtime</p></li><li><p>Remember: Audit logging isn&#8217;t just for compliance&#8212;it&#8217;s your debugging lifeline when things go wrong</p></li></ul><p></p>]]></content:encoded></item><item><title><![CDATA[When Microservices Can’t Talk to Each Other]]></title><description><![CDATA[A critical feature was blocked for three weeks.]]></description><link>https://snippetsofcode.substack.com/p/when-microservices-cant-talk-to-each</link><guid isPermaLink="false">https://snippetsofcode.substack.com/p/when-microservices-cant-talk-to-each</guid><dc:creator><![CDATA[Rafael George]]></dc:creator><pubDate>Thu, 27 Nov 2025 22:07:40 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1762119594508-b9be29cd513d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>A critical feature was blocked for three weeks. Not because of technical complexity&#8212;because one service needed permission data that lived in another service, and we had no safe way to bridge that gap.</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1762119594508-b9be29cd513d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1762119594508-b9be29cd513d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 424w, https://images.unsplash.com/photo-1762119594508-b9be29cd513d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 848w, https://images.unsplash.com/photo-1762119594508-b9be29cd513d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 1272w, https://images.unsplash.com/photo-1762119594508-b9be29cd513d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1762119594508-b9be29cd513d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000" width="3000" height="4328" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1762119594508-b9be29cd513d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:4328,&quot;width&quot;:3000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Impressionist street scene with horse-drawn carriages and buildings.&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Impressionist street scene with horse-drawn carriages and buildings." title="Impressionist street scene with horse-drawn carriages and buildings." srcset="https://images.unsplash.com/photo-1762119594508-b9be29cd513d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 424w, https://images.unsplash.com/photo-1762119594508-b9be29cd513d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 848w, https://images.unsplash.com/photo-1762119594508-b9be29cd513d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 1272w, https://images.unsplash.com/photo-1762119594508-b9be29cd513d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this issue, I&#8217;ll take you through two approaches to solving cross-service data dependencies: synchronous API calls versus event-driven state replication. No architectural theory&#8212;just real trade-offs, actual implementations, and lessons learned.</p><p><strong>What you&#8217;ll learn:</strong></p><ul><li><p>How to design secure cross-service communication patterns</p></li><li><p>Why permission logic duplication creates security nightmares</p></li><li><p>Event modeling as an alternative to synchronous service calls</p></li><li><p>Trade-offs between consistency models in distributed systems</p></li></ul><h2>The Problem</h2><p>Here&#8217;s what we were dealing with&#8212;a feature blocked by service boundaries:</p><pre><code><code># payment-service needs to check if users can access billing data
# but authorization data lives in user-service
# and we have architectural constraints

class BillingController &lt; ApplicationController
  def show
    # How do we verify user has permission to view billing?
    # user-service owns permission data
    # payment-service can&#8217;t directly access user-service database
    # duplicating permission logic would create security issues
    
    # Option 1: Synchronous API call to user-service?
    # Option 2: Replicate permission state via events?
    # Option 3: Share database? (please no)
  end
end
</code></code></pre><p><strong>Warning Signs:</strong></p><ul><li><p>Critical features blocked by service boundary constraints</p></li><li><p>Temptation to share databases across services</p></li><li><p>No established pattern for cross-service authorization</p></li><li><p>Complex permission rules that change frequently</p></li><li><p>Security compliance requirements for permission auditing</p></li></ul><h2>From Service Isolation to Communication</h2><h3>Understanding Why Sharing Data Is Hard</h3><p>The naive approach: just query the other service&#8217;s database directly.</p><p><strong>Before (Database Coupling - WRONG):</strong></p><pre><code><code># In payment-service (BAD APPROACH)
class PermissionChecker
  def can_access_billing?(user_id)
    # Directly accessing user-service database
    ActiveRecord::Base.establish_connection(
      adapter: &#8216;postgresql&#8217;,
      host: &#8216;user-service-db.internal&#8217;,
      database: &#8216;users_production&#8217;
    )
    
    # Now we&#8217;re tightly coupled to user-service internals
    # Schema changes break us
    # Can&#8217;t evolve services independently
    # Violates microservices principles
  end
end
</code></code></pre><p><strong>Why This Fails:</strong></p><ul><li><p>Services can&#8217;t evolve database schemas independently</p></li><li><p>No clear ownership boundaries</p></li><li><p>Security becomes impossible to audit</p></li><li><p>Violates microservices isolation principles</p></li><li><p>Impossible to scale services separately</p></li></ul><p><strong>Impact:</strong></p><ul><li><p>Rejected immediately despite deadline pressure</p></li><li><p>Started exploring proper service communication patterns</p></li></ul><h3>Solution A - Synchronous API Authentication</h3><p>The first approach: create a secure API endpoint that validates permissions.</p><p><strong>The Implementation:</strong></p><pre><code><code># In user-service: expose permission check API
module Api
  module V1
    class PermissionsController &lt; BaseController
      before_action :authenticate_service!
      
      def check
        validator = PermissionValidator.new(
          user_id: params[:user_id],
          resource: params[:resource],
          action: params[:action],
          context: params[:context]
        )
        
        unless validator.valid?
          return render json: {
            error: &#8216;Invalid request&#8217;,
            details: validator.errors
          }, status: :bad_request
        end
        
        result = PermissionService.check(
          user_id: params[:user_id],
          resource: params[:resource],
          action: params[:action],
          context: params[:context] || default_context
        )
        
        audit_check(result)
        render json: format_response(result)
      end
      
      private
      
      def authenticate_service!
        provided = request.headers[&#8217;X-Service-Token&#8217;]
        expected = Settings.service_tokens[request.headers[&#8217;X-Service-Name&#8217;]]
        
        unless secure_compare(provided, expected)
          render json: { error: &#8216;Unauthorized service&#8217; }, 
                 status: :unauthorized
        end
      end
      
      def default_context
        User.find(params[:user_id]).default_organization_id
      end
      
      def audit_check(result)
        PermissionAuditLog.create!(
          requesting_service: request.headers[&#8217;X-Service-Name&#8217;],
          user_id: params[:user_id],
          resource: params[:resource],
          action: params[:action],
          decision: result.granted?,
          timestamp: Time.current
        )
      end
    end
  end
end

# In payment-service: call the API
class PermissionClient
  def can_access?(user_id:, resource:, action:)
    response = HTTP
      .headers(
        &#8216;X-Service-Token&#8217; =&gt; service_token,
        &#8216;X-Service-Name&#8217; =&gt; &#8216;payment-service&#8217;
      )
      .get(
        &#8220;#{user_service_url}/api/v1/permissions/check&#8221;,
        params: {
          user_id: user_id,
          resource: resource,
          action: action
        }
      )
      
    handle_response(response)
  end
  
  private
  
  def service_token
    Settings.user_service.token
  end
end
</code></code></pre><p><strong>Pros:</strong></p><ul><li><p>&#9989; Simple to understand and implement</p></li><li><p>&#9989; Real-time permission checks (always current)</p></li><li><p>&#9989; No data duplication</p></li><li><p>&#9989; Clear audit trail</p></li><li><p>&#9989; Single source of truth for permissions</p></li></ul><p><strong>Cons:</strong></p><ul><li><p>&#10060; Network latency on every check (45ms average)</p></li><li><p>&#10060; Creates runtime dependency (user-service down = payments broken)</p></li><li><p>&#10060; Doesn&#8217;t scale well for high-throughput scenarios</p></li><li><p>&#10060; Cascading failures possible</p></li><li><p>&#10060; No offline capability</p></li></ul><p><strong>When This Works:</strong></p><ul><li><p>Low-frequency permission checks</p></li><li><p>Strong consistency requirements</p></li><li><p>Simple deployment topology</p></li><li><p>Acceptable latency budget</p></li><li><p>Services in same data center</p></li></ul><h3>Step 3:Step 3:Step 3:Step 3:Solution B - Event-Driven State Replication</h3><p>The alternative approach: replicate permission state via events.</p><p><strong>The Event Modeling Approach:</strong></p><pre><code><code># Define domain events
module Events
  class PermissionGranted &lt; Event
    attributes :user_id, :resource, :action, :granted_by, :context
  end
  
  class PermissionRevoked &lt; Event
    attributes :user_id, :resource, :action, :revoked_by, :context
  end
  
  class UserRoleChanged &lt; Event
    attributes :user_id, :old_role, :new_role, :changed_by
  end
end

# In user-service: publish permission changes
class GrantPermissionCommand
  def execute(user_id:, resource:, action:, granted_by:)
    permission = Permission.create!(
      user_id: user_id,
      resource: resource,
      action: action,
      granted_by: granted_by
    )
    
    # Publish event to message bus
    EventBus.publish(
      Events::PermissionGranted.new(
        user_id: user_id,
        resource: resource,
        action: action,
        granted_by: granted_by,
        context: current_context
      )
    )
    
    permission
  end
end

# In payment-service: maintain local permission cache
class PermissionProjection
  include EventSubscriber
  
  subscribes_to Events::PermissionGranted,
                 Events::PermissionRevoked,
                 Events::UserRoleChanged
  
  def on_permission_granted(event)
    LocalPermission.upsert(
      user_id: event.user_id,
      resource: event.resource,
      action: event.action,
      granted_at: event.occurred_at
    )
  end
  
  def on_permission_revoked(event)
    LocalPermission.where(
      user_id: event.user_id,
      resource: event.resource,
      action: event.action
    ).delete_all
  end
  
  def on_user_role_changed(event)
    # Rebuild permissions for user based on new role
    RebuildUserPermissionsJob.perform_later(event.user_id)
  end
end

# Local permission checking (no network call)
class LocalPermissionChecker
  def can_access?(user_id:, resource:, action:)
    LocalPermission.exists?(
      user_id: user_id,
      resource: resource,
      action: action
    )
  end
end
</code></code></pre><p><strong>The Event Modeling Diagram:</strong></p><pre><code><code>[User-Service State]
      |
      | Permission Changed
      &#8595;
[PermissionGranted Event] &#8594; Message Bus
      |
      &#8595;
[Payment-Service Projection]
      |
      &#8595;
[Local Permission Cache]
</code></code></pre><p><strong>Pros:</strong></p><ul><li><p>&#9989; Zero latency permission checks (local data)</p></li><li><p>&#9989; Services independent (user-service down = payments still work)</p></li><li><p>&#9989; Scales horizontally without synchronization</p></li><li><p>&#9989; Offline capability built-in</p></li><li><p>&#9989; Natural audit log through event stream</p></li></ul><p><strong>Cons:</strong></p><ul><li><p>&#10060; Eventual consistency (permission changes delayed)</p></li><li><p>&#10060; More complex implementation</p></li><li><p>&#10060; Data duplication across services</p></li><li><p>&#10060; Event replay needed for new services</p></li><li><p>&#10060; Potential for inconsistent state if events lost</p></li></ul><p><strong>When This Works:</strong></p><ul><li><p>High-frequency permission checks</p></li><li><p>Can tolerate eventual consistency</p></li><li><p>Need offline capability</p></li><li><p>High-availability requirements</p></li><li><p>Services deployed across regions</p></li></ul><blockquote><p>There&#8217;s no universally correct answer. The choice depends on your consistency requirements and failure modes.</p></blockquote><p><strong>Ask these questions:</strong></p><ul><li><p>How often do permissions change versus how often are they checked?</p></li><li><p>What happens if permission data is temporarily stale?</p></li><li><p>What&#8217;s the cost of a network call versus maintaining replicated state?</p></li><li><p>Which failure mode is worse: stale permissions or unavailable checks?</p></li></ul><p>For our billing scenario, we chose the synchronous API because:</p><ul><li><p>Permission checks were infrequent (once per session)</p></li><li><p>Strong consistency was required (security-critical)</p></li><li><p>Network latency was acceptable</p></li><li><p>Simple deployment topology</p></li></ul><p>For a different service (content delivery with millions of requests), we chose event-driven because:</p><ul><li><p>Permission checks happened millions of times per day</p></li><li><p>Eventual consistency was acceptable</p></li><li><p>Network latency was unacceptable</p></li><li><p>Services deployed globally</p></li></ul><h3>Real Numbers From Both Implementations</h3><p><strong>Synchronous API Approach:</strong></p><ul><li><p>Permission check latency: 45ms p50, 120ms p99</p></li><li><p>Availability: 99.9% (dependent on user-service)</p></li><li><p>Implementation time: 2 days</p></li><li><p>Complexity: Low</p></li><li><p>Zero data replication overhead</p></li></ul><p><strong>Event-Driven Approach:</strong></p><ul><li><p>Permission check latency: &lt;1ms (local lookup)</p></li><li><p>Availability: 99.99% (independent of user-service)</p></li><li><p>Implementation time: 1 week</p></li><li><p>Complexity: High</p></li><li><p>Eventual consistency window: 100ms average</p></li><li><p>Storage overhead: 50MB per service replica</p></li></ul><h2>How to Decide?</h2><pre><code><code>def choose_approach(requirements)
  if requirements.strong_consistency_required? &amp;&amp;
     requirements.low_check_frequency? &amp;&amp;
     requirements.acceptable_latency?(50.milliseconds)
    :synchronous_api
  elsif requirements.high_throughput? &amp;&amp;
        requirements.eventual_consistency_acceptable? &amp;&amp;
        requirements.independent_service_deployments?
    :event_driven_replication
  else
    # Hybrid: cache with TTL + fallback to API
    :cached_with_api_fallback
  end
end
</code></code></pre><blockquote><p>In practice, we often use both:</p></blockquote><pre><code><code>class HybridPermissionChecker
  def can_access?(user_id:, resource:, action:)
    # Try local cache first
    cached = permission_cache.get(cache_key(user_id, resource, action))
    return cached if cached.present?
    
    # Cache miss: call API
    result = api_client.check_permission(
      user_id: user_id,
      resource: resource,
      action: action
    )
    
    # Cache result with TTL
    permission_cache.set(
      cache_key(user_id, resource, action),
      result,
      expires_in: 5.minutes
    )
    
    result
  end
  
  private
  
  def cache_key(user_id, resource, action)
    &#8220;permission:#{user_id}:#{resource}:#{action}&#8221;
  end
end
</code></code></pre><p>This gives you:</p><ul><li><p>Fast lookups (cached)</p></li><li><p>Strong consistency (cache TTL)</p></li><li><p>Graceful degradation (cache during outages)</p></li></ul><h2>Monday Morning Action Items</h2><ol><li><p><strong>Quick Wins (5-Minute Changes)</strong></p><ul><li><p>Map your cross-service data dependencies</p></li><li><p>Identify services sharing databases</p></li><li><p>Calculate permission check frequency in your services</p></li></ul></li><li><p><strong>Next Steps</strong></p><ul><li><p>Implement one synchronous service-to-service API</p></li><li><p>Prototype event-driven replication for high-frequency checks</p></li><li><p>Measure actual latency and consistency requirements</p></li><li><p>Document your consistency model for team</p></li></ul></li></ol><blockquote><p>Analyze your microservices:</p></blockquote><pre><code><code># Questions to answer
- Which services need data owned by other services?
- Are you sharing databases across services?
- What&#8217;s your permission check frequency?
- Can you tolerate eventual consistency?
- What&#8217;s your actual latency budget?
- Which failure mode is worse for your domain?
</code></code></pre><p><strong>Discussion Prompts:</strong></p><ul><li><p>How do you currently handle cross-service authorization?</p></li><li><p>Have you measured the cost of network latency versus data replication?</p></li><li><p>What&#8217;s your strategy for handling inconsistent distributed state?</p></li></ul><div><hr></div><p><strong>Useful Resources:</strong></p><ul><li><p><a href="https://www.oreilly.com/library/view/building-event-driven-microservices/9781492057888/">Building Event-Driven Microservices</a> - Comprehensive guide to event-driven architecture</p></li><li><p><a href="https://martinfowler.com/articles/microservices.html">Martin Fowler on Microservices</a> - Foundational microservices patterns</p></li><li><p><a href="https://eventmodeling.org/">Event Modeling</a> - Visual approach to designing event-driven systems</p></li></ul><div><hr></div><p>Found this useful? Share it with someone wrestling with microservices communication. Reply with your approach to cross-service data access&#8212;I&#8217;m curious what patterns are working in production.</p><p>Happy coding!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://snippetsofcode.substack.com/p/when-microservices-cant-talk-to-each/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://snippetsofcode.substack.com/p/when-microservices-cant-talk-to-each/comments"><span>Leave a comment</span></a></p><div><hr></div><p><strong>Tips and Notes:</strong></p><ul><li><p>Note: There&#8217;s no universal &#8220;right&#8221; approach&#8212;consistency requirements determine architecture</p></li><li><p>Pro Tip: Start with synchronous APIs, migrate to events when latency becomes a problem</p></li><li><p>Remember: Event-driven systems trade complexity for performance&#8212;make sure the trade-off is worth it</p></li></ul>]]></content:encoded></item><item><title><![CDATA[Why 100% Test Coverage Is Killing Your Productivity]]></title><description><![CDATA[&#8220;We need 100% test coverage.&#8221;]]></description><link>https://snippetsofcode.substack.com/p/why-100-test-coverage-is-killing</link><guid isPermaLink="false">https://snippetsofcode.substack.com/p/why-100-test-coverage-is-killing</guid><dc:creator><![CDATA[Rafael George]]></dc:creator><pubDate>Thu, 20 Nov 2025 23:40:33 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1762112800040-b0d01696cb92?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1762112800040-b0d01696cb92?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1762112800040-b0d01696cb92?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 424w, https://images.unsplash.com/photo-1762112800040-b0d01696cb92?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 848w, https://images.unsplash.com/photo-1762112800040-b0d01696cb92?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 1272w, https://images.unsplash.com/photo-1762112800040-b0d01696cb92?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1762112800040-b0d01696cb92?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000" width="3000" height="4576" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1762112800040-b0d01696cb92?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:4576,&quot;width&quot;:3000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Traditional japanese street scene with river and trees.&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Traditional japanese street scene with river and trees." title="Traditional japanese street scene with river and trees." srcset="https://images.unsplash.com/photo-1762112800040-b0d01696cb92?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 424w, https://images.unsplash.com/photo-1762112800040-b0d01696cb92?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 848w, https://images.unsplash.com/photo-1762112800040-b0d01696cb92?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 1272w, https://images.unsplash.com/photo-1762112800040-b0d01696cb92?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I heard this in a code review last month. A junior developer had spent three days writing tests for ActiveRecord validations&#8212;code that Rails already guarantees works. The PR sat there with 47 test files for 12 lines of business logic.</p><p>You know that moment when you realize your team is optimizing for the wrong metric? This was mine.</p><p>In this issue, I&#8217;ll take you through the testing strategy that cut our test suite runtime by 60% while actually improving code quality. No testing dogma&#8212;just practical approaches to testing what matters.</p><p><strong>What you&#8217;ll learn:</strong></p><ul><li><p>Why test coverage percentage is a misleading metric</p></li><li><p>The code-to-test ratio that signals problems</p></li><li><p>How to identify what actually needs testing</p></li><li><p>Practical strategies for sustainable test suites</p></li></ul><h2>The Problem</h2><p>Here&#8217;s what we were dealing with&#8212;a testing culture gone wrong:</p><pre><code><code># Actual test from our codebase
RSpec.describe User do
  it &#8220;has a name&#8221; do
    user = User.new(name: &#8220;Test&#8221;)
    expect(user.name).to eq(&#8221;Test&#8221;)
  end
  
  it &#8220;validates presence of email&#8221; do
    user = User.new(email: nil)
    expect(user).to_not be_valid
  end
  
  # ... 45 more tests like this for basic Rails functionality
end
</code></code></pre><p><strong>Warning Signs:</strong></p><ul><li><p>Test suite taking 20+ minutes to run</p></li><li><p>Developers avoiding running tests locally</p></li><li><p>Breaking changes passing CI because tests were brittle</p></li><li><p>Team morale dropping as test maintenance became a burden</p></li><li><p>Code-to-test ratio exceeding 1:3 (one line of code, three lines of tests)</p></li></ul><h2>The Journey: From Coverage Theater to Meaningful Testing</h2><h3>Recognizing What Rails Already Tests</h3><p>The first breakthrough: stop testing framework guarantees.</p><p><strong>Before:</strong></p><pre><code><code>RSpec.describe Invoice do
  describe &#8220;associations&#8221; do
    it { should have_many(:line_items) }
    it { should belong_to(:customer) }
  end
  
  describe &#8220;validations&#8221; do
    it { should validate_presence_of(:total) }
    it { should validate_numericality_of(:total) }
  end
end
</code></code></pre><p><strong>After:</strong></p><pre><code><code>RSpec.describe Invoice do
  describe &#8220;#calculate_total&#8221; do
    it &#8220;sums line item amounts including tax&#8221; do
      invoice = create(:invoice)
      invoice.line_items.create!(amount: 100, tax_rate: 0.1)
      invoice.line_items.create!(amount: 200, tax_rate: 0.1)
      
      expect(invoice.calculate_total).to eq(330)
    end
  end
end
</code></code></pre><p><strong>Impact:</strong></p><ul><li><p>Test file reduced from 150 lines to 30 lines</p></li><li><p>Tests now focus on actual business logic</p></li><li><p>Test maintenance time cut by 75%</p></li></ul><h3>Understanding the Testing Pyramid</h3><p>The second realization: not all tests are created equal.</p><pre><code><code>        /\
       /  \    E2E Tests (slow, expensive, broad)
      /____\
     /      \
    / Integration \ (medium speed, medium cost)
   /____________\
  /              \
 /   Unit Tests   \ (fast, cheap, narrow)
/__________________\
</code></code></pre><p><strong>Before (Inverted Pyramid):</strong></p><ul><li><p>60% E2E tests (Capybara/Selenium)</p></li><li><p>30% integration tests</p></li><li><p>10% unit tests</p></li><li><p>Result: 20-minute test suite</p></li></ul><p><strong>After (Proper Pyramid):</strong></p><ul><li><p>10% E2E tests (critical user paths only)</p></li><li><p>20% integration tests</p></li><li><p>70% unit tests</p></li><li><p>Result: 3-minute test suite</p></li></ul><p><strong>Impact:</strong></p><ul><li><p>85% reduction in test suite runtime</p></li><li><p>Faster feedback during development</p></li><li><p>Easier to identify failure causes</p></li></ul><h3>The Code-to-Test Ratio Reality Check</h3><p>The metric that actually matters: code-to-test ratio.</p><p><strong>Healthy ratios:</strong></p><ul><li><p>1:1 to 1:2 is ideal (one line of code, one to two lines of tests)</p></li><li><p>1:2 to 1:3 signals growing problems</p></li><li><p>Above 1:3 means serious issues with test redundancy</p></li></ul><p><strong>Our situation:</strong></p><pre><code><code># Business logic: 15 lines
def process_refund(amount)
  return false if amount &gt; total
  return false if already_refunded?
  
  create_refund_transaction(amount)
  update_balance(amount)
  notify_customer
  true
end

# Tests: 180 lines testing every possible path,
# including Rails validations, associations,
# and framework behavior
</code></code></pre><p>We were at 1:12 ratio. Twelve lines of tests for every line of business logic.</p><h2>The Aha Moment</h2><p>The breakthrough came when I asked: &#8220;What are we actually afraid will break?&#8221;</p><p>Not Rails associations. Not database constraints. Not framework validations.</p><p><strong>The risky code</strong>: Business logic with conditionals, calculations, and state changes.</p><h3>Real Numbers From This Experience</h3><p>After implementing focused testing strategy:</p><ul><li><p>Test suite runtime: 20 minutes &#8594; 3 minutes (85% reduction)</p></li><li><p>Test maintenance time: 8 hours/week &#8594; 1 hour/week</p></li><li><p>Actual bugs caught in CI: Increased by 40%</p></li><li><p>Developer satisfaction: Significantly improved</p></li><li><p>Code-to-test ratio: 1:12 &#8594; 1:1.5</p></li></ul><h2>The Final Result</h2><pre><code><code># Focus tests on business logic risk areas
RSpec.describe RefundProcessor do
  describe &#8220;#process&#8221; do
    it &#8220;prevents refunds exceeding original total&#8221; do
      processor = RefundProcessor.new(total: 100)
      
      expect(processor.process(150)).to be false
    end
    
    it &#8220;prevents duplicate refunds&#8221; do
      processor = RefundProcessor.new(total: 100)
      processor.process(50)
      
      expect(processor.process(50)).to be false
    end
    
    it &#8220;updates balance and notifies customer on success&#8221; do
      processor = RefundProcessor.new(total: 100)
      
      expect { processor.process(50) }
        .to change { processor.balance }.by(-50)
        .and have_enqueued_job(CustomerNotificationJob)
    end
  end
end
</code></code></pre><p><strong>Key Improvements:</strong></p><ul><li><p>Tests focus on business rules and edge cases</p></li><li><p>No testing of framework guarantees</p></li><li><p>Clear documentation of expected behavior</p></li><li><p>Fast execution enables frequent running</p></li></ul><h2>Monday Morning Action Items</h2><ol><li><p><strong>Quick Wins (5-Minute Changes)</strong></p><ul><li><p>Calculate your code-to-test ratio for one file</p></li><li><p>Identify tests that verify framework behavior</p></li><li><p>Check your test suite runtime</p></li></ul></li><li><p><strong>Next Steps</strong></p><ul><li><p>Remove one redundant test file this week</p></li><li><p>Add one unit test for complex business logic</p></li><li><p>Measure test suite improvement</p></li><li><p>Share findings with team</p></li></ul></li></ol><h2>Your Turn!</h2><h3>The Testing Audit Challenge</h3><p>Analyze your test suite:</p><pre><code><code># Calculate your ratios
total_code_lines = # Count actual business logic
total_test_lines = # Count all test code
ratio = total_test_lines / total_code_lines

# Audit one test file
framework_tests = # Tests verifying Rails/framework behavior  
business_tests = # Tests verifying your business logic
percentage_valuable = business_tests / (framework_tests + business_tests)
</code></code></pre><p><strong>Discussion Prompts:</strong></p><ul><li><p>What percentage of your tests verify framework behavior?</p></li><li><p>How much time does your team spend maintaining tests vs. writing features?</p></li><li><p>What&#8217;s your current code-to-test ratio?</p></li></ul><div><hr></div><p><strong>Useful Resources:</strong></p><ul><li><p><a href="https://martinfowler.com/articles/practical-test-pyramid.html">Testing Pyramid</a> - Martin Fowler&#8217;s guide to test balance</p></li><li><p><a href="https://semaphoreci.com/ebooks/rails-testing-handbook">Rails Testing Handbook</a> - Practical Rails testing strategies</p></li><li><p><a href="https://www.betterspecs.org/">RSpec Best Practices</a> - Community-driven RSpec guidelines</p></li></ul><div><hr></div><p>Found this useful? Share it with a developer drowning in test maintenance. Reply with your code-to-test ratio&#8212;I&#8217;m curious where most teams are.</p><p>Happy coding!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://snippetsofcode.substack.com/p/why-100-test-coverage-is-killing/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://snippetsofcode.substack.com/p/why-100-test-coverage-is-killing/comments"><span>Leave a comment</span></a></p><div><hr></div><p><strong>Tips and Notes:</strong></p><ul><li><p>Note: Coverage percentage is a vanity metric. Focus on covering risky code, not achieving arbitrary percentages.</p></li><li><p>Pro Tip: If you&#8217;re testing Rails associations or validations, you&#8217;re testing the framework, not your application.</p></li><li><p>Remember: Redundant tests lower team morale more than any other technical debt. Address them proactively.</p></li></ul>]]></content:encoded></item><item><title><![CDATA[Building Async Notifications Without Breaking Your Monolith]]></title><description><![CDATA[A security-critical feature was missing something obvious]]></description><link>https://snippetsofcode.substack.com/p/building-async-notifications-without</link><guid isPermaLink="false">https://snippetsofcode.substack.com/p/building-async-notifications-without</guid><dc:creator><![CDATA[Rafael George]]></dc:creator><pubDate>Thu, 20 Nov 2025 22:57:17 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1596526131083-e8c633c948d2?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>When admins reset users&#8217; authentication methods, those users received no notification. </p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1596526131083-e8c633c948d2?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1596526131083-e8c633c948d2?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 424w, https://images.unsplash.com/photo-1596526131083-e8c633c948d2?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 848w, https://images.unsplash.com/photo-1596526131083-e8c633c948d2?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1272w, https://images.unsplash.com/photo-1596526131083-e8c633c948d2?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1596526131083-e8c633c948d2?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" width="3000" height="2250" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1596526131083-e8c633c948d2?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2250,&quot;width&quot;:3000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;blue and white logo guessing game&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="blue and white logo guessing game" title="blue and white logo guessing game" srcset="https://images.unsplash.com/photo-1596526131083-e8c633c948d2?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 424w, https://images.unsplash.com/photo-1596526131083-e8c633c948d2?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 848w, https://images.unsplash.com/photo-1596526131083-e8c633c948d2?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1272w, https://images.unsplash.com/photo-1596526131083-e8c633c948d2?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>They&#8217;d discover their security settings changed only when they tried to log in.</p><p>You know that moment when you realize a &#8220;simple&#8221; notification turns into a distributed systems problem? This was mine.</p><p>In this issue, I&#8217;ll take you through building an event-driven notification system that bridges services without coupling them.</p><p>No distributed systems theory&#8212;just real code, actual trade-offs, and lessons learned from production.</p><p><strong>What you&#8217;ll learn:</strong></p><ul><li><p>How to design async notification systems using message queues</p></li><li><p>Why synchronous notification calls create cascading failures</p></li><li><p>Patterns for producer-consumer event architectures</p></li><li><p>Handling failures gracefully without blocking critical operations</p></li></ul><h2>The Problem</h2><p>Here&#8217;s what we were dealing with&#8212;a security gap that seemed simple to fix:</p><pre><code><code># In auth-service: admin resets user&#8217;s 2FA
class TwoFactorResetController &lt; ApplicationController
  def reset
    user = User.find(params[:user_id])
    user.two_factor_methods.destroy_all
    
    # TODO: notify user their 2FA was reset
    # But how?
    # - Can&#8217;t make sync HTTP call (might timeout, block critical operation)
    # - Can&#8217;t access email service directly (different service boundary)
    # - Can&#8217;t skip notification (security requirement)
    
    render json: { success: true }
  end
end
</code></code></pre><p><strong>Warning Signs:</strong></p><ul><li><p>Critical security operations lacking user notifications</p></li><li><p>Synchronous HTTP calls blocking admin operations</p></li><li><p>Service boundaries preventing direct email access</p></li><li><p>No failure recovery if notifications fail</p></li><li><p>Compliance requirements for security event notifications</p></li></ul><h2>From Synchronous Blocking to Async Events</h2><h3>Why Synchronous Notifications Fail</h3><p>The naive approach: just make an HTTP call to the notification service.</p><p><strong>Before (Synchronous HTTP - WRONG):</strong></p><pre><code><code>class TwoFactorResetController &lt; ApplicationController
  def reset
    user = User.find(params[:user_id])
    user.two_factor_methods.destroy_all
    
    # Synchronous HTTP call (BAD)
    NotificationClient.send_email(
      to: user.email,
      template: &#8216;two_factor_reset&#8217;,
      data: { name: user.name }
    )
    
    render json: { success: true }
  rescue NotificationClient::Error =&gt; e
    # What do we do here?
    # - Rollback 2FA reset? (security operation already completed)
    # - Fail the whole request? (admin operation blocked by email issue)
    # - Log and continue? (user never gets notified)
  end
end
</code></code></pre><p><strong>Why This Fails:</strong></p><ul><li><p>Admin operations blocked by email service latency/failures</p></li><li><p>2FA reset and notification tightly coupled</p></li><li><p>No retry mechanism if email fails</p></li><li><p>Creates runtime dependency between services</p></li><li><p>Timeout in notification service breaks security operations</p></li></ul><p><strong>Impact:</strong></p><ul><li><p>Rejected this approach after simulating notification service outage</p></li><li><p>Admin operations must never be blocked by notification failures</p></li></ul><h3>Designing the Event-Driven Architecture</h3><p>The solution: decouple the security operation from notification delivery using events.</p><p><strong>The Architecture:</strong></p><pre><code><code>&#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;         &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;         &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
&#9474;  Auth Service   &#9474;         &#9474; Message Bus  &#9474;         &#9474; Email Service   &#9474;
&#9474;                 &#9474;         &#9474;   (Kafka)    &#9474;         &#9474;                 &#9474;
&#9474;  Reset 2FA      &#9474;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9654;&#9474;              &#9474;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9654;&#9474; Send Email      &#9474;
&#9474;  Publish Event  &#9474;         &#9474;  Topic:      &#9474;         &#9474; (SendGrid)      &#9474;
&#9474;                 &#9474;         &#9474;  security-   &#9474;         &#9474;                 &#9474;
&#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;         &#9474;  events      &#9474;         &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
                            &#9474;              &#9474;
                            &#9474;  DLQ:        &#9474;
                            &#9474;  security-   &#9474;
                            &#9474;  events-dlq  &#9474;
                            &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
</code></code></pre><p><strong>The Producer (Auth Service):</strong></p><pre><code><code># Define event types
module Events
  module Security
    TWO_FACTOR_RESET = &#8220;security.two_factor.reset&#8221;
  end
end

# Define Kafka topics
module MessageBus
  module Topics
    def self.by_environment(base_name)
      &#8220;#{Rails.env}_#{base_name}&#8221;
    end
    
    SECURITY_EVENTS = by_environment(&#8221;security_events&#8221;)
    SECURITY_EVENTS_DLQ = by_environment(&#8221;security_events_dlq&#8221;)
  end
end

# Producer for 2FA reset events
class TwoFactorResetProducer
  def self.publish(user_identifier:, email:, role:, name:)
    event = {
      event_type: Events::Security::TWO_FACTOR_RESET,
      timestamp: Time.current.iso8601,
      resource: {
        user_identifier: user_identifier,
        email: email,
        role: role,
        name: name
      }
    }
    
    kafka_producer.produce(
      event.to_json,
      topic: MessageBus::Topics::SECURITY_EVENTS,
      key: user_identifier
    )
    
    true
  rescue =&gt; e
    # Log but don&#8217;t fail the operation
    ErrorLogger.log(
      error: e,
      context: &#8220;Failed to publish 2FA reset event&#8221;,
      user_identifier: user_identifier
    )
    
    # Return false but don&#8217;t raise - notification failure
    # shouldn&#8217;t block security operation
    false
  end
  
  private
  
  def self.kafka_producer
    @kafka_producer ||= Kafka.new(
      seed_brokers: ENV[&#8217;KAFKA_BROKERS&#8217;].split(&#8217;,&#8217;),
      client_id: &#8216;auth-service&#8217;
    ).async_producer
  end
end

# Updated controller using producer
class TwoFactorResetController &lt; ApplicationController
  def reset
    user = User.find(params[:user_id])
    user.two_factor_methods.destroy_all
    
    # Publish event asynchronously
    TwoFactorResetProducer.publish(
      user_identifier: user.id,
      email: user.email,
      role: user.role,
      name: user.display_name
    )
    
    # Admin operation succeeds regardless of notification
    render json: { success: true }
  end
end
</code></code></pre><p><strong>Key Design Decisions:</strong></p><ol><li><p><strong>Fire-and-forget publishing</strong>: Admin operation never blocks</p></li><li><p><strong>Graceful degradation</strong>: Log failures but continue</p></li><li><p><strong>Event enrichment</strong>: Include all data consumer needs</p></li><li><p><strong>Environment-specific topics</strong>: Prevent dev/prod mixing</p></li><li><p><strong>Dead letter queue</strong>: Capture failed events for investigation</p></li></ol><p><strong>Impact:</strong></p><ul><li><p>Admin operations now take 50ms instead of 200-500ms</p></li><li><p>Zero coupling between security ops and email delivery</p></li><li><p>Can retry failed notifications independently</p></li><li><p>Email service outages don&#8217;t affect security operations</p></li></ul><h3>Building the Consumer (Email Service)</h3><p>The consumer transforms security events into email notifications.</p><pre><code><code># Event handler for 2FA reset
class SecurityEventHandler
  ROLE_TEMPLATES = {
    &#8216;staff&#8217; =&gt; &#8216;774f9baf-4250-483b-9ebb-4945c4c3b4c6&#8217;,
    &#8216;parent&#8217; =&gt; &#8216;59ce8688-8c83-4caa-b2ac-94b9ad5722ee&#8217;,
    &#8216;student&#8217; =&gt; &#8216;a1b2c3d4-5678-90ab-cdef-1234567890ab&#8217;
  }.freeze
  
  DEFAULT_TEMPLATE = ROLE_TEMPLATES[&#8217;staff&#8217;]
  
  def handle_two_factor_reset(event)
    resource = event[&#8217;resource&#8217;]
    
    template_id = ROLE_TEMPLATES.fetch(
      resource[&#8217;role&#8217;],
      DEFAULT_TEMPLATE
    )
    
    EmailSender.send_template(
      to: resource[&#8217;email&#8217;],
      template_id: template_id,
      substitutions: {
        user_name: resource[&#8217;name&#8217;]
      }
    )
    
    AuditLog.create!(
      event_type: &#8216;two_factor_reset_notification_sent&#8217;,
      user_identifier: resource[&#8217;user_identifier&#8217;],
      email: resource[&#8217;email&#8217;],
      timestamp: Time.current
    )
  rescue =&gt; e
    ErrorLogger.log(
      error: e,
      context: &#8220;Failed to send 2FA reset email&#8221;,
      event: event
    )
    
    # Re-raise to send to DLQ
    raise
  end
end

# Kafka consumer
class SecurityEventsConsumer
  def initialize
    @kafka = Kafka.new(
      seed_brokers: ENV[&#8217;KAFKA_BROKERS&#8217;].split(&#8217;,&#8217;),
      client_id: &#8216;email-service&#8217;
    )
    
    @consumer = @kafka.consumer(
      group_id: &#8216;email-service-security-events&#8217;
    )
    
    @consumer.subscribe(MessageBus::Topics::SECURITY_EVENTS)
  end
  
  def consume
    @consumer.each_message do |message|
      event = JSON.parse(message.value)
      
      case event[&#8217;event_type&#8217;]
      when Events::Security::TWO_FACTOR_RESET
        SecurityEventHandler.new.handle_two_factor_reset(event)
      else
        Rails.logger.warn(&#8221;Unknown event type: #{event[&#8217;event_type&#8217;]}&#8221;)
      end
      
    rescue =&gt; e
      ErrorLogger.log(
        error: e,
        message_offset: message.offset,
        partition: message.partition
      )
      
      # Send to DLQ
      dlq_producer.produce(
        message.value,
        topic: MessageBus::Topics::SECURITY_EVENTS_DLQ,
        key: message.key
      )
    end
  end
  
  private
  
  def dlq_producer
    @dlq_producer ||= @kafka.async_producer
  end
end
</code></code></pre><p><strong>Consumer Features:</strong></p><ol><li><p><strong>Role-based templates</strong>: Different emails for different user types</p></li><li><p><strong>Graceful error handling</strong>: Failed messages go to DLQ, not lost</p></li><li><p><strong>Audit logging</strong>: Track notification delivery</p></li><li><p><strong>Unknown event handling</strong>: Log but don&#8217;t fail on unexpected events</p></li><li><p><strong>Consumer groups</strong>: Multiple instances process in parallel</p></li></ol><p><strong>Impact:</strong></p><ul><li><p>Email delivery decoupled from security operations</p></li><li><p>Failed emails can be retried without re-executing security ops</p></li><li><p>Different services can consume same events for different purposes</p></li><li><p>Consumer can be scaled independently based on event volume</p></li></ul><h3>Handling the Feature Flag Dance</h3><p>The unexpected complexity: coordinating feature flags across services.</p><pre><code><code># In auth-service: respect Kafka production flag
class TwoFactorResetProducer
  def self.publish(user_identifier:, email:, role:, name:)
    return false unless feature_enabled?
    
    # ... publish event
  end
  
  private
  
  def self.feature_enabled?
    Settings.kafka.event_production_enabled
  end
end

# In email-service: respect consumer flag
class SecurityEventsConsumer
  def consume
    return unless feature_enabled?
    
    # ... consume events
  end
  
  private
  
  def feature_enabled?
    Settings.kafka.event_consumption_enabled
  end
end
</code></code></pre><p><strong>Deployment Strategy:</strong></p><ol><li><p>Deploy consumer first (deployed, flag off)</p></li><li><p>Enable consumer flag (consuming but no events yet)</p></li><li><p>Deploy producer (deployed, flag off)</p></li><li><p>Enable producer flag (events now flow through system)</p></li><li><p>Monitor for 24 hours</p></li><li><p>Remove flags after stability confirmed</p></li></ol><p><strong>Why This Order Matters:</strong></p><p>If you deploy producer first and enable it, events will pile up with no consumer. When consumer eventually deploys, it processes huge backlog, potentially causing email storms.</p><blockquote><p>The breakthrough: async notifications aren&#8217;t about making things faster&#8212;they&#8217;re about making critical operations reliable by decoupling them from ancillary concerns.</p></blockquote><p><strong>The principle</strong>: Security operations succeed or fail based on security logic, not email delivery status.</p><h3>Some Numbers From This Implementation</h3><p>After rolling out event-driven notifications:</p><ul><li><p>Admin operation latency: 200-500ms &#8594; 50ms (4-10x faster)</p></li><li><p>Operation success rate: 97% &#8594; 99.9% (email failures no longer block)</p></li><li><p>Email delivery reliability: Same (moved problem to consumer, not eliminated)</p></li><li><p>System complexity: Increased (added message bus, consumers, DLQ handling)</p></li><li><p>Debugging difficulty: Higher (distributed traces across services)</p></li><li><p>Retry capability: None &#8594; Full (can replay events from Kafka)</p></li></ul><h2>Async vs Sync</h2><p><strong>Use synchronous notifications when:</strong></p><ul><li><p>Notification failure should block the operation</p></li><li><p>User needs immediate confirmation</p></li><li><p>Operations are low-frequency</p></li><li><p>Services share same deployment boundary</p></li></ul><p><strong>Use async events when:</strong></p><ul><li><p>Operation success independent of notification</p></li><li><p>Need retry capability</p></li><li><p>High-frequency operations</p></li><li><p>Multiple consumers need same event</p></li><li><p>Services deployed independently</p></li></ul><h2>The Complete Flow</h2><pre><code><code>1. Admin clicks &#8220;Reset 2FA&#8221; in UI
2. Auth service resets 2FA methods in database
3. Auth service publishes event to Kafka (fire-and-forget)
4. Admin sees success immediately (operation completed)
5. Kafka persists event to topic
6. Email service consumer picks up event
7. Email service sends notification via SendGrid
8. If email fails: event goes to DLQ for investigation
9. If email succeeds: audit log created
</code></code></pre><p><strong>Key Properties:</strong></p><ul><li><p>Steps 1-4 complete in 50ms</p></li><li><p>Steps 5-9 happen asynchronously</p></li><li><p>Failure in steps 5-9 don&#8217;t affect steps 1-4</p></li><li><p>Can replay/retry steps 5-9 independently</p></li></ul><h2>Monday Morning Action Items</h2><ol><li><p><strong>Quick Wins (5-Minute Changes)</strong></p><ul><li><p>List all synchronous notification calls in critical paths</p></li><li><p>Identify operations blocked by notification failures</p></li><li><p>Check if your message bus has dead letter queues configured</p></li></ul></li><li><p><strong>Next Steps</strong></p><ul><li><p>Implement one async notification using events</p></li><li><p>Add dead letter queue handling for failed events</p></li><li><p>Create monitoring for consumer lag</p></li><li><p>Document event schemas for team</p></li></ul></li></ol><h2>Your Turn!</h2><h3>The Notification Audit Challenge</h3><p>Analyze your notification patterns:</p><pre><code><code># Questions to answer
- Which operations block on notification delivery?
- What&#8217;s your notification failure rate?
- How do you retry failed notifications?
- Can users recover from missed notifications?
- What&#8217;s your average notification latency?
- Do notification failures affect user operations?
</code></code></pre><p><strong>Discussion Prompts:</strong></p><ul><li><p>How do you currently handle notification failures?</p></li><li><p>Have notification issues ever caused operation failures?</p></li><li><p>What&#8217;s your strategy for notification retries?</p></li></ul><h2>What&#8217;s Next?</h2><p>Next week: &#8220;Dead Letter Queues: The Error Handling Pattern You&#8217;re Missing&#8221; - Why successful systems need a place to put things that failed, and how to design DLQ processing that actually works.</p><div><hr></div><p><strong>Useful Resources:</strong></p><ul><li><p><a href="https://www.confluent.io/resources/kafka-the-definitive-guide/">Kafka: The Definitive Guide</a> - Comprehensive Kafka patterns</p></li><li><p><a href="https://www.enterpriseintegrationpatterns.com/">Enterprise Integration Patterns</a> - Message-based integration patterns</p></li><li><p><a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html">AWS SQS Dead Letter Queues</a> - DLQ concepts</p></li></ul><div><hr></div><p>Found this useful? Share it with someone building notification systems. Reply with your async notification patterns&#8212;I&#8217;m curious what message buses people are actually using in production.</p><p>Happy coding!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://snippetsofcode.substack.com/p/building-async-notifications-without?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://snippetsofcode.substack.com/p/building-async-notifications-without?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><p><strong>Tips and Notes:</strong></p><ul><li><p>Note: Async notifications trade immediacy for reliability&#8212;make sure that&#8217;s the right trade-off for your use case</p></li><li><p>Pro Tip: Always deploy consumers before producers to avoid event backlogs</p></li><li><p>Remember: Dead letter queues are not optional&#8212;failed messages need somewhere to go for investigation</p></li></ul>]]></content:encoded></item><item><title><![CDATA[Why Your ActiveRecord Models Are Too Smart (And How I Fixed Mine)]]></title><description><![CDATA[Three days. That&#8217;s how long I spent hunting down a bug in what should have been a simple credit calculation.]]></description><link>https://snippetsofcode.substack.com/p/why-your-activerecord-models-are</link><guid isPermaLink="false">https://snippetsofcode.substack.com/p/why-your-activerecord-models-are</guid><dc:creator><![CDATA[Rafael George]]></dc:creator><pubDate>Thu, 13 Nov 2025 15:03:26 GMT</pubDate><content:encoded><![CDATA[<p>The logic was buried inside an 800-line ActiveRecord model with conditionals, callbacks, and database operations all tangled together.</p><p>You know that moment when you realize the bug isn&#8217;t the problem&#8212;the entire architecture is? This was mine.</p><p>In this issue, I&#8217;ll take you through the refactoring journey that cut our test suite time by 40% and made our codebase actually understandable. No theory dumps&#8212;just real code, real problems, and real solutions.</p><p><strong>What you&#8217;ll learn:</strong></p><ul><li><p>How to identify when ActiveRecord models are doing too much</p></li><li><p>The value object pattern for extracting business logic</p></li><li><p>How to test business rules without database overhead</p></li><li><p>Practical refactoring steps that improve maintainability</p></li></ul><h2>The Problem</h2><p>Here&#8217;s what we were dealing with&#8212;a typical Rails model that started simple and grew into a monster:</p><pre><code><code>class Invoice &lt; ApplicationRecord
  # 800 lines of associations, validations, business logic,
  # calculations, callbacks, and helper methods all mixed together
  
  def add_one_month_of_credit!
    current_date = self.paid_through_date || Date.today
    duration = 1.month - 1.day
    self.paid_through_date = current_date + duration
    self.save!
  end
  
  # ... 50 more methods like this
end
</code></code></pre><p><strong>Warning Signs:</strong></p><ul><li><p>Single model file exceeding 500 lines</p></li><li><p>Methods mixing business logic with database operations</p></li><li><p>Integration tests required to verify simple calculations</p></li><li><p>Fear of changing anything because of hidden dependencies</p></li><li><p>New developers taking days to understand one model</p></li></ul><h2>The Journey: From Problem to Solution</h2><h3>Step 1: Recognizing the God Object Pattern</h3><p>The first breakthrough: understanding that ActiveRecord models should be boring. They should handle database operations, not business logic.</p><p><strong>Before:</strong></p><pre><code><code>def add_one_month_of_credit!
  current_date = self.paid_through_date || Date.today
  duration = 1.month - 1.day
  self.paid_through_date = current_date + duration
  self.save!
end
</code></code></pre><p><strong>After:</strong></p><pre><code><code># Business logic extracted to value object
module Credits
  OneMonth = Struct.new(:current_date, :duration, keyword_init: true) do
    def self.for(invoice)
      new(
        current_date: invoice.paid_through_date || Date.today,
        duration: 1.month - 1.day
      )
    end
    
    def to_h
      { paid_through_date: paid_through_date }
    end
    
    private
    
    def paid_through_date
      current_date + duration
    end
  end
end

# Model stays thin
class Invoice &lt; ApplicationRecord
  def add_credit!(credit)
    update!(credit.to_h)
  end
end

# Usage
invoice.add_credit!(Credits::OneMonth.for(invoice))
</code></code></pre><p><strong>Impact:</strong></p><ul><li><p>Business logic now has a clear name: <code>Credits::OneMonth</code></p></li><li><p>Credit calculation testable without database setup</p></li><li><p>Model reduced from 800 to 200 lines</p></li></ul><h3>Step 2: Testing Without Database Overhead</h3><p>The second win: testing business logic became trivial.</p><p><strong>Before (Integration Test):</strong></p><pre><code><code>RSpec.describe Invoice do
  it &#8220;adds one month of credit&#8221; do
    invoice = create(:invoice, paid_through_date: Date.new(2024, 1, 1))
    invoice.add_one_month_of_credit!
    
    expect(invoice.paid_through_date).to eq(Date.new(2024, 1, 31))
  end
end
</code></code></pre><p><strong>After (Unit Test):</strong></p><pre><code><code>RSpec.describe Credits::OneMonth do
  it &#8220;calculates one month of credit&#8221; do
    invoice = double(paid_through_date: Date.new(2024, 1, 1))
    credit = described_class.for(invoice)
    
    expect(credit.to_h[:paid_through_date]).to eq(Date.new(2024, 1, 31))
  end
end
</code></code></pre><p><strong>Impact:</strong></p><ul><li><p>Test suite runs 40% faster (fewer database operations)</p></li><li><p>Tests run in milliseconds instead of seconds</p></li><li><p>Business logic failures immediately obvious</p></li></ul><h3>Step 3: Making Domain Concepts Explicit</h3><p>The final realization: code should speak the language of the business.</p><pre><code><code># Multiple credit types now easy to add
module Credits
  OneMonth = Struct.new(:current_date, :duration, keyword_init: true) do
    # ... implementation
  end
  
  ThreeMonths = Struct.new(:current_date, :duration, keyword_init: true) do
    def self.for(invoice)
      new(
        current_date: invoice.paid_through_date || Date.today,
        duration: 3.months - 1.day
      )
    end
    # ... rest of implementation
  end
  
  Annual = Struct.new(:current_date, :duration, keyword_init: true) do
    # ... implementation
  end
end

# Clear, readable usage
invoice.add_credit!(Credits::ThreeMonths.for(invoice))
invoice.add_credit!(Credits::Annual.for(invoice))
</code></code></pre><h2>The Aha Moment</h2><p>The breakthrough came when I stopped thinking about &#8220;how do I organize this code&#8221; and started asking &#8220;what business concepts am I modeling?&#8221;</p><p>The code wasn&#8217;t just calculating dates&#8212;it was representing different types of subscription credits. Making that concept explicit through value objects transformed the entire codebase.</p><h3>Real Numbers From This Experience</h3><ul><li><p>Before: 800-line Invoice model, 12-second test suite for that model</p></li><li><p>After: 200-line Invoice model, 3-second test suite</p></li><li><p>Bug discovery time: Reduced by 60%</p></li><li><p>Feature velocity: New credit types added in hours instead of days</p></li><li><p>Onboarding time: New developers productive in days instead of weeks</p></li></ul><h2>The Final Result</h2><pre><code><code># app/models/invoice.rb (thin and focused)
class Invoice &lt; ApplicationRecord
  has_many :invoice_items
  belongs_to :customer
  
  validates :paid_through_date, presence: true
  
  def add_credit!(credit)
    update!(credit.to_h)
  end
  
  def build_invoice_items(items:)
    items.map { |item| invoice_items.create!(item) }
  end
end

# app/models/credits/one_month.rb (business logic)
module Credits
  OneMonth = Struct.new(:current_date, :duration, keyword_init: true) do
    def self.for(invoice)
      new(
        current_date: invoice.paid_through_date || Date.today,
        duration: 1.month - 1.day
      )
    end
    
    def to_h
      { paid_through_date: paid_through_date }
    end
    
    private
    
    def paid_through_date
      current_date + duration
    end
  end
end
</code></code></pre><p><strong>Key Improvements:</strong></p><ul><li><p>ActiveRecord model focuses only on database concerns</p></li><li><p>Business logic isolated in testable value objects</p></li><li><p>Clear separation between domain concepts and persistence</p></li><li><p>Each class has a single, clear responsibility</p></li></ul><h2>Monday Morning Action Items</h2><ol><li><p><strong>Quick Wins (5-Minute Changes)</strong></p><ul><li><p>Identify your largest ActiveRecord model</p></li><li><p>Find one method that mixes business logic with persistence</p></li><li><p>Count how many lines would need database setup to test</p></li></ul></li><li><p><strong>Next Steps</strong></p><ul><li><p>Extract one calculation into a value object</p></li><li><p>Write a unit test for the extracted logic</p></li><li><p>Measure test speed improvement</p></li><li><p>Repeat for one more method</p></li></ul></li></ol><h2>Your Turn!</h2><h3>The Model Refactoring Challenge</h3><p>Look at this typical ActiveRecord method:</p><pre><code><code>class Subscription &lt; ApplicationRecord
  def calculate_renewal_amount
    base_price = self.plan.base_price
    discount = self.active_promotion ? self.active_promotion.discount_percentage : 0
    tax_rate = self.customer.region.tax_rate
    
    subtotal = base_price * (1 - discount / 100.0)
    total = subtotal * (1 + tax_rate / 100.0)
    
    self.update!(renewal_amount: total)
    total
  end
end
</code></code></pre><p><strong>Discussion Prompts:</strong></p><ul><li><p>What business concepts are hidden in this method?</p></li><li><p>How would you extract the pricing logic into value objects?</p></li><li><p>What would the tests look like before and after extraction?</p></li></ul><p><strong>Useful Resources:</strong></p><ul><li><p><a href="https://martinfowler.com/books/refactoringRubyEd.html">Refactoring: Ruby Edition</a> - Classic refactoring patterns</p></li><li><p><a href="https://www.amazon.com/Domain-Driven-Design-Distilled-Vaughn-Vernon/dp/0134434420">Domain-Driven Design Distilled</a> - Value objects and bounded contexts</p></li><li><p><a href="https://www.amazon.com/Rails-AntiPatterns-Refactoring-Addison-Wesley-Professional/dp/0321604814">Rails AntiPatterns</a> - Common Rails mistakes and solutions</p></li></ul><p>Found this useful? Share it with a developer who&#8217;s wrestling with a giant ActiveRecord model. Reply with your own refactoring wins&#8212;I read every response.</p><p>Happy coding!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://snippetsofcode.substack.com/p/why-your-activerecord-models-are/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://snippetsofcode.substack.com/p/why-your-activerecord-models-are/comments"><span>Leave a comment</span></a></p><p><strong>Tips and Notes:</strong></p><ul><li><p>Note: Start with one small extraction. Don&#8217;t try to refactor everything at once.</p></li><li><p>Pro Tip: Value objects work great with Struct for simple cases, but can be full classes for complex domain logic.</p></li><li><p>Remember: If you&#8217;re using <code>let</code> or <code>before</code> blocks to set up database state in tests, that&#8217;s a sign business logic might belong elsewhere.</p><p></p></li></ul>]]></content:encoded></item><item><title><![CDATA[What If Learning TypeScript as a Ruby Developer Wasn't Starting Over?]]></title><description><![CDATA[I&#8217;ve been thinking about this assumption Ruby developers have]]></description><link>https://snippetsofcode.substack.com/p/what-if-learning-typescript-as-a</link><guid isPermaLink="false">https://snippetsofcode.substack.com/p/what-if-learning-typescript-as-a</guid><dc:creator><![CDATA[Rafael George]]></dc:creator><pubDate>Thu, 06 Nov 2025 15:03:19 GMT</pubDate><content:encoded><![CDATA[<p><strong>Learning TypeScript means abandoning everything they know. What if it&#8217;s just translating familiar patterns into a different syntax?</strong></p><p>Most Ruby developers I talk to feel intimidated by TypeScript. Static types. Compilation steps. JavaScript&#8217;s quirks. It feels like learning programming all over again.</p><p>But I&#8217;ve been experimenting with TypeScript lately, and here&#8217;s what surprised me: <strong>What if TypeScript is just Ruby with explicit type declarations and different punctuation?</strong></p><h2>The Pattern Recognition Discovery</h2><p><strong>What if the concepts you already know in Ruby map almost directly to TypeScript?</strong></p><p>You know blocks and procs in Ruby? TypeScript has arrow functions and first-class functions.</p><p>You know duck typing? TypeScript has interfaces that work similarly.</p><p>You know symbols and hashes? TypeScript has string literals and objects.</p><p><strong>What if it&#8217;s not about learning new patterns&#8212;just recognizing old patterns in new clothes?</strong></p><p>Let me show you what I mean. Here&#8217;s what I&#8217;ve been mapping out:</p><h3>Variables: Ruby&#8217;s Flexibility vs TypeScript&#8217;s Explicitness</h3><pre><code><code># Ruby - implicit typing
name = &#8220;Clever&#8221;
age = 25
</code></code></pre><pre><code><code>// TypeScript - explicit typing
let name: string = &#8220;Clever&#8221;;
const age: number = 25;
</code></code></pre><p><strong>What if the main difference is just declaring what you already know?</strong> In Ruby, you know <code>age</code> is a number. In TypeScript, you just say it explicitly.</p><h3>Methods vs Functions: Same Idea, Different Keywords</h3><pre><code><code># Ruby
def add(x, y)
  x + y
end
</code></code></pre><pre><code><code>// TypeScript - traditional function
function add(x: number, y: number): number {
    return x + y;
}

// TypeScript - arrow function (more common)
const add = (x: number, y: number): number =&gt; x + y;
</code></code></pre><p><strong>What if arrow functions are just Ruby&#8217;s stabby lambda syntax (</strong><code>-&gt;</code><strong>) with types added?</strong></p><h3>Interfaces: Duck Typing Made Explicit</h3><pre><code><code># Ruby - duck typing (implicit interface)
class User
  attr_accessor :id, :name, :email
end
</code></code></pre><pre><code><code>// TypeScript - explicit interface
interface User {
    id: number;
    name: string;
    email?: string; // optional field (like nil in Ruby)
}
</code></code></pre><p><strong>What if TypeScript interfaces are just documenting the duck typing you&#8217;re already doing in your head?</strong></p><h3>Arrays: Ruby&#8217;s Enumerable Methods Are All Here</h3><p>Here&#8217;s where it gets interesting. <strong>What if all your favorite Ruby array methods exist in TypeScript?</strong></p><pre><code><code># Ruby
arr = [1, 2, 3, 4, 5]
arr.select { |x| x &gt; 2 }      # [3, 4, 5]
arr.map { |x| x * 2 }         # [2, 4, 6, 8, 10]
arr.reduce(0) { |acc, x| acc + x }  # 15
arr.find { |x| x &gt; 3 }        # 4
arr.any? { |x| x &gt; 4 }        # true
arr.all? { |x| x &gt; 0 }        # true
</code></code></pre><pre><code><code>// TypeScript
const arr = [1, 2, 3, 4, 5];
arr.filter(x =&gt; x &gt; 2);       // [3, 4, 5]
arr.map(x =&gt; x * 2);          // [2, 4, 6, 8, 10]
arr.reduce((acc, x) =&gt; acc + x, 0);  // 15
arr.find(x =&gt; x &gt; 3);         // 4
arr.some(x =&gt; x &gt; 4);         // true
arr.every(x =&gt; x &gt; 0);        // true
</code></code></pre><p><strong>What if it&#8217;s literally the same methods with slightly different names?</strong> <code>select</code> becomes <code>filter</code>, <code>any?</code> becomes <code>some</code>, <code>all?</code> becomes <code>every</code>.</p><h3>Hashes vs Objects: Two Approaches to Key-Value Pairs</h3><pre><code><code># Ruby - hash
user_ages = { &#8220;alice&#8221; =&gt; 25, &#8220;bob&#8221; =&gt; 30 }
user_ages[&#8221;alice&#8221;]
user_ages.delete(&#8221;bob&#8221;)
user_ages.keys
user_ages.values
</code></code></pre><pre><code><code>// TypeScript - Map (similar to Ruby hash)
const userAges = new Map&lt;string, number&gt;();
userAges.set(&#8221;alice&#8221;, 25);
userAges.get(&#8221;alice&#8221;);
userAges.delete(&#8221;bob&#8221;);
userAges.keys();
userAges.values();

// TypeScript - Object literal (more common)
const obj: { [key: string]: number } = {};
obj[&#8221;alice&#8221;] = 25;
delete obj[&#8221;bob&#8221;];
Object.keys(obj);    // array of keys
Object.values(obj);  // array of values
</code></code></pre><p><strong>What if TypeScript gives you two ways to do hashes&#8212;one that looks just like Ruby&#8217;s Hash, and one that&#8217;s more JavaScript-native?</strong></p><p><strong>What if TypeScript&#8217;s async/await is cleaner than Ruby&#8217;s threading?</strong></p><pre><code><code># Ruby - threads or fibers
require &#8216;net/http&#8217;

def fetch_user(id)
  uri = URI(&#8221;https://api.example.com/users/#{id}&#8221;)
  response = Net::HTTP.get(uri)
  JSON.parse(response)
end

# Concurrent fetching (complicated)
threads = [1, 2, 3].map do |id|
  Thread.new { fetch_user(id) }
end
results = threads.map(&amp;:value)
</code></code></pre><pre><code><code>// TypeScript - async/await
async function fetchUser(id: number): Promise&lt;User&gt; {
    const response = await fetch(`https://api.example.com/users/${id}`);
    return response.json();
}

// Concurrent fetching (simpler?)
const results = await Promise.all([
    fetchUser(1),
    fetchUser(2),
    fetchUser(3)
]);
</code></code></pre><p><strong>What if </strong><code>Promise.all</code><strong> is just a cleaner version of waiting for multiple threads?</strong></p><h2>What About Error Handling?</h2><p>Here&#8217;s the one pattern that&#8217;s genuinely different:</p><pre><code><code># Ruby - explicit error handling
def fetch_data
  # code that might raise
rescue StandardError =&gt; e
  puts &#8220;Error: #{e.message}&#8221;
end
</code></code></pre><pre><code><code>// TypeScript - try-catch
async function fetchData() {
    try {
        const result = await fetch(&#8217;/api/data&#8217;);
        return result.json();
    } catch (error) {
        console.error(error);
    }
}
</code></code></pre><p><strong>What if this is actually simpler than Ruby&#8217;s rescue blocks?</strong> No need to specify error types, just catch and handle.</p><p><strong>What if learning TypeScript as a Ruby developer is about recognizing these translations?</strong></p><ul><li><p><code>def</code> &#8594; <code>function</code> or <code>const</code></p></li><li><p>Blocks &#8594; Arrow functions</p></li><li><p><code>attr_accessor</code> &#8594; Interface properties</p></li><li><p><code>select</code> &#8594; <code>filter</code></p></li><li><p><code>any?</code> &#8594; <code>some</code></p></li><li><p><code>all?</code> &#8594; <code>every</code></p></li><li><p>Hash &#8594; <code>Map</code> or object literal</p></li><li><p>Symbols &#8594; String literals</p></li><li><p><code>nil</code> &#8594; <code>undefined</code> or <code>null</code></p></li></ul><p><strong>What if you already know 80% of TypeScript&#8212;you just need to learn the vocabulary?</strong></p><p>The biggest differences aren&#8217;t conceptual&#8212;they&#8217;re syntactic:</p><ul><li><p>Explicit types instead of implicit</p></li><li><p>Semicolons (optional but common)</p></li><li><p>Curly braces instead of <code>end</code></p></li><li><p>Different method names for the same operations</p></li></ul><p><strong>What if TypeScript isn&#8217;t harder than Ruby&#8212;just more explicit about what Ruby does implicitly?</strong></p><p>For interviews, focus on:</p><ol><li><p>Array methods (<code>map</code>, <code>filter</code>, <code>reduce</code>, <code>find</code>, <code>some</code>, <code>every</code>)</p></li><li><p>Object/Map operations (getting, setting, deleting keys)</p></li><li><p>Async patterns (<code>async</code>/<code>await</code>, <code>Promise.all</code>)</p></li><li><p>Type declarations for functions and interfaces</p></li></ol><p><strong>What if you practiced these patterns in Ruby first, then just translated the syntax?</strong></p><p><strong>What if you took one Ruby method you write often and translated it to TypeScript?</strong></p><p>Pick something simple&#8212;a method that filters an array, or transforms a hash, or fetches data. Write it in Ruby first. Then translate it line by line to TypeScript.</p><p><strong>What if the hardest part is just remembering to add types and change the punctuation?</strong></p><p><strong>Like what you just read? Hit that like button and share. Follow me for more explorations of language learning through pattern recognition. What Ruby patterns do you wish existed in other languages?</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://snippetsofcode.substack.com/p/what-if-learning-typescript-as-a/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://snippetsofcode.substack.com/p/what-if-learning-typescript-as-a/comments"><span>Leave a comment</span></a></p><div><hr></div><p><strong>PS:</strong> TypeScript might not be a new language to learn&#8212;it might just be Ruby&#8217;s patterns with explicit types and JavaScript&#8217;s syntax, which means you&#8217;re much closer to fluency than you think.</p>]]></content:encoded></item><item><title><![CDATA[When Reading and Writing Should Break Up]]></title><description><![CDATA[How separating queries from commands saved me from ActiveRecord hell.]]></description><link>https://snippetsofcode.substack.com/p/when-reading-and-writing-should-break</link><guid isPermaLink="false">https://snippetsofcode.substack.com/p/when-reading-and-writing-should-break</guid><dc:creator><![CDATA[Rafael George]]></dc:creator><pubDate>Thu, 30 Oct 2025 17:02:32 GMT</pubDate><content:encoded><![CDATA[<blockquote><p>Most developers think keeping reads and writes together makes code simpler.</p></blockquote><p>I thought that too.</p><p>For years, I built Rails applications the &#8220;standard&#8221; way. ActiveRecord models handling everything&#8212;validation, business logic, persistence, queries, view formatting. One object, many responsibilities.</p><p>Then I hit a wall with a reporting dashboard that needed to display order data in six different formats. Each view needed different aggregations, different joins, different filtering logic.</p><p><strong>Every change to support a new read pattern risked breaking write operations.</strong></p><p>The test suite became a nightmare. Adding a new report meant touching the core Order model, re-running hundreds of tests, and hoping nothing broke in checkout processing.</p><p>That&#8217;s when I discovered CQRS&#8212;Command Query Responsibility Segregation.</p><h2>The Problem with Mixed Responsibilities</h2><p>Here&#8217;s what happens when you keep reads and writes tightly coupled:</p><p><strong>Your models become bloated</strong> with methods that serve completely different purposes. <code>create_order</code>, <code>validate_inventory</code>, <code>calculate_shipping</code>, <code>format_for_mobile_view</code>, <code>export_to_csv</code>&#8212;all in one class.</p><p><strong>Testing becomes expensive</strong> because every read operation test has to set up write-side validations and callbacks, even when they&#8217;re irrelevant.</p><p><strong>Performance optimization becomes risky</strong> because speeding up reads might accidentally break write logic, and vice versa.</p><p><strong>Scaling becomes difficult</strong> because 90% of your traffic is reads, but they&#8217;re tied to the same database and models as your critical write operations.</p><p>The insight: <strong>reading data and changing data are fundamentally different operations with different requirements.</strong></p><h2>The CQRS Pattern</h2><p>CQRS is simpler than it sounds: separate how you read data from how you write data.</p><p><strong>Commands</strong>: Actions that change state. Create order. Update inventory. Process payment. Each command represents a specific business operation with clear intent.</p><p><strong>Queries</strong>: Requests that read state. Get order details. List customer history. Calculate monthly revenue. Each query is optimized for consumption, not storage elegance.</p><p>This separation brings immediate benefits:</p><ul><li><p>Each side can be optimized independently</p></li><li><p>Reads can be denormalized without affecting write models</p></li><li><p>Testing becomes focused and faster</p></li><li><p>Scaling reads doesn&#8217;t require scaling writes</p></li></ul><h2>How I Implemented CQRS in Rails</h2><p>Rails defaults encourage mixing everything in ActiveRecord models. Breaking this apart required intentional architectural decisions.</p><p><strong>Extract Read Logic into DTOs</strong></p><p>Instead of querying ActiveRecord models directly for display:</p><pre><code><code># Old way - tightly coupled to ActiveRecord
class Order &lt; ApplicationRecord
  def formatted_for_dashboard
    # Complex formatting logic mixed with persistence
  end
end

# New way - dedicated read objects
class OrderSummaryDTO
  attr_reader :order_number, :customer_name, :total, :status
  
  def self.for_dashboard(order_ids)
    # Optimized SQL query returning only needed fields
    # No callbacks, no validations, no side effects
    sql = &lt;&lt;~SQL
      SELECT orders.number, customers.name, orders.total, orders.status
      FROM orders
      JOIN customers ON orders.customer_id = customers.id
      WHERE orders.id IN (?)
    SQL
    
    results = ActiveRecord::Base.connection.select_all(sql, order_ids)
    results.map { |row| new(row) }
  end
  
  def initialize(attributes)
    @order_number = attributes[&#8217;number&#8217;]
    @customer_name = attributes[&#8217;name&#8217;]
    @total = attributes[&#8217;total&#8217;]
    @status = attributes[&#8217;status&#8217;]
  end
end
</code></code></pre><p>The DTO is a plain Ruby object optimized for reading. No persistence logic, no validations that don&#8217;t apply to reads.</p><p><strong>Encapsulate Writes in Command Objects</strong></p><p>Each business operation becomes an explicit command:</p><pre><code><code>class CreateOrder
  def initialize(customer_id, items, shipping_address)
    @customer_id = customer_id
    @items = items
    @shipping_address = shipping_address
  end
  
  def call
    ActiveRecord::Base.transaction do
      order = Order.create!(
        customer_id: @customer_id,
        status: &#8216;pending&#8217;
      )
      
      @items.each do |item|
        OrderItem.create!(
          order: order,
          product_id: item[:product_id],
          quantity: item[:quantity],
          price: item[:price]
        )
      end
      
      ShippingInfo.create!(
        order: order,
        address: @shipping_address
      )
      
      # Emit event for read model updates
      event_store.publish(
        OrderCreated.new(data: {
          order_id: order.id,
          customer_id: @customer_id,
          total: calculate_total(order)
        })
      )
      
      Result.success(order)
    end
  rescue =&gt; error
    Result.failure(error.message)
  end
end
</code></code></pre><p>The command has one job: execute a business operation safely and emit events about what happened.</p><p><strong>Build Purpose-Specific Read Models</strong></p><p>For the dashboard that needed six different views, I created six specialized read models:</p><pre><code><code># Optimized for mobile list view
class MobileOrderList &lt; ApplicationRecord
  self.table_name = &#8216;mobile_order_summaries&#8217;
  # Denormalized: includes customer name, product count, status
end

# Optimized for detailed report
class OrderReportView &lt; ApplicationRecord
  self.table_name = &#8216;order_report_data&#8217;
  # Denormalized: includes all aggregations pre-calculated
end

# Event handler keeps read models updated
class OrderCreatedHandler
  def call(event)
    data = event.data
    
    MobileOrderList.create!(
      order_id: data[:order_id],
      customer_name: Customer.find(data[:customer_id]).name,
      total: data[:total],
      created_at: Time.current
    )
    
    OrderReportView.create!(
      order_id: data[:order_id],
      # Pre-calculated aggregations
    )
  end
end
</code></code></pre><blockquote><p>CQRS isn&#8217;t free. The main cost is <strong>eventual consistency</strong>.</p></blockquote><p>When a user creates an order, there&#8217;s a brief moment where:</p><ul><li><p>The write operation has completed</p></li><li><p>The read models haven&#8217;t updated yet</p></li><li><p>The user refreshes and doesn&#8217;t see their order</p></li></ul><p>This was confusing at first. Users would submit orders, refresh immediately, and think something broke.</p><p><strong>The solutions that worked</strong>:</p><ol><li><p><strong>Set expectations in the UI</strong>: &#8220;Your order is being processed and will appear shortly&#8221;</p></li><li><p><strong>Optimistic updates</strong>: Show the order immediately in the UI based on the command result, even before the read model updates</p></li><li><p><strong>Compensating actions</strong>: If something fails updating the read model, have a way to retry or rollback</p></li><li><p><strong>Monitoring</strong>: Track the lag between writes and read model updates</p></li></ol><p>In practice, the delay was usually under 100ms. Users rarely noticed. But we had to plan for the edge cases.</p><h2>The Production Impact</h2><p>After implementing CQRS for the reporting system:</p><p><strong>Performance improvements</strong>:</p><ul><li><p>Dashboard queries went from 2-3 seconds to under 200ms</p></li><li><p>Read queries no longer locked write operations</p></li><li><p>We could cache read models aggressively without worrying about stale writes</p></li></ul><p><strong>Development velocity</strong>:</p><ul><li><p>New report formats didn&#8217;t touch core business logic</p></li><li><p>Read optimization didn&#8217;t require coordination with write-side teams</p></li><li><p>Test suites ran 40% faster because read tests didn&#8217;t set up write-side state</p></li></ul><p><strong>Scaling benefits</strong>:</p><ul><li><p>Read databases could scale independently</p></li><li><p>We added read replicas without changing application code</p></li><li><p>High-traffic read operations didn&#8217;t impact order processing</p></li></ul><p>The tradeoff: more moving parts, eventual consistency to manage, and an events system to maintain.</p><p>For this use case, absolutely worth it.</p><h2>When CQRS Makes Sense</h2><p>CQRS isn&#8217;t always the answer. Consider it when:</p><p><strong>Read/write ratio is skewed</strong>: If reads vastly outnumber writes (common in most web apps), the ability to optimize them separately pays dividends.</p><p><strong>Data models are growing complex</strong>: When your ActiveRecord models have 30+ methods mixing business logic with view formatting, separation brings clarity.</p><p><strong>Different consumers need different views</strong>: Mobile apps, admin dashboards, API clients&#8212;each optimized for their use case.</p><p><strong>Performance optimization feels risky</strong>: When speeding up queries might break critical write operations.</p><p>Skip CQRS when:</p><ul><li><p>Your app is simple and likely to stay that way</p></li><li><p>Reads and writes have similar complexity</p></li><li><p>Eventual consistency would create unacceptable UX issues</p></li><li><p>Your team isn&#8217;t ready to manage the additional complexity</p></li></ul><blockquote><p>Look at your most complex model. Ask yourself:</p></blockquote><ul><li><p>How many methods are for reading vs writing?</p></li><li><p>Could read operations be optimized independently?</p></li><li><p>Would separating concerns make testing easier?</p></li><li><p>Are reads blocking writes or vice versa?</p></li></ul><p>Start small. Extract one read operation into a DTO. Create one command object for a critical business operation. See if the separation brings clarity.</p><p>The goal isn&#8217;t architectural purity&#8212;it&#8217;s making your system easier to understand, test, and scale.</p><p>CQRS is a tool, not a religion. Use it where it solves real problems.</p><p><em>This week&#8217;s architectural experiment: separating concerns that don&#8217;t belong together. What parts of your system are fighting each other because they&#8217;re too tightly coupled?</em></p><p><em>- Rafael</em></p><p><strong>P.S.</strong> The best architecture is the one that makes your team&#8217;s life easier tomorrow, not the one that looks clever today.</p><div><hr></div><p>&#128227; <strong>Whenever you&#8217;re ready, here are 3 ways I can help you level up your knowledge-based work:</strong></p><p><strong>1. Transform How You Think and Communicate</strong> Tired of fuzzy thinking and unclear communication holding back your career? &#8220;Staying Relevant&#8221; shows you how to use daily writing to sharpen your ideas, make faster decisions, and lead with unshakeable clarity. Get the mental edge that separates top performers from everyone else. &#128073; <a href="https://www.amazon.com/Staying-Relevant-Writing-Business-Digital-ebook/dp/B0FHC1GDVQ/ref=sr_1_1?crid=1PQN7TDVIRE1X&amp;dib=eyJ2IjoiMSJ9.0UflA7GIkA2e_dZ6P__HeAM5ar9cQxXwZjobuIYQ7mTTJqUDTlAMT9KyRpQhU01s__vAwzdgIs8wwjfkJkgCJq9yWRNbCz2KOr5PEypYRuHKoz-0VwUeDf6YAo3lRij6sszRIvGQ0Q6CekvzoXhjxJFR6pr4LfJJAMvyotDRp2LS4TTGJ9UFR36qPc_6ms4678vbhMb6lF9sbqs4SKgv1JL0stqN9Aj0eKvjFnC5nR8.xhEWW7lI0UVDxy5jRVOst6pV1tvhi9tNAdt3nvnAMN0&amp;dib_tag=se&amp;keywords=staying+relevant+book&amp;qid=1753888211&amp;sprefix=staying+relevant%2Caps%2C169&amp;sr=8-1">Grab the book</a></p><p><strong>2. Write Code That Actually Lasts</strong> Stop wrestling with legacy nightmares and technical debt. &#8220;16 Ways to Level-Up Your Codebase&#8221; gives you battle-tested strategies to write cleaner, more maintainable code in just 20 minutes a day. Transform your codebase from liability to competitive advantage. &#128073; <a href="https://www.amazon.com/dp/B0DLVK2Y3Z">Get the guide</a></p><p><strong>3. FREE Rails Code Quality Assessment Scorecard</strong> Sick of simple features taking weeks instead of days? This 25-point evaluation reveals the hidden issues killing your Rails team&#8217;s velocity. In 15 minutes, you&#8217;ll know exactly what&#8217;s slowing you down and get a custom roadmap to fix it. Stop avoiding &#8220;that code&#8221; and start shipping with confidence.&#128073; <a href="https://stan.store/rafaelgeorge/p/rails-code-quality-assessment-scorecard">Get the free scorecard</a></p><p></p>]]></content:encoded></item><item><title><![CDATA[How One Query Nearly Killed Our Database]]></title><description><![CDATA[The performance optimization that went from 15 seconds to 29 milliseconds&#8212;and what I learned about scaling assumptions.]]></description><link>https://snippetsofcode.substack.com/p/how-one-query-nearly-killed-our-database</link><guid isPermaLink="false">https://snippetsofcode.substack.com/p/how-one-query-nearly-killed-our-database</guid><dc:creator><![CDATA[Rafael George]]></dc:creator><pubDate>Thu, 23 Oct 2025 22:30:23 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1544383835-bda2bc66a55d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>Everyone thinks complex database queries are just a performance problem you can solve with better hardware.</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1544383835-bda2bc66a55d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1544383835-bda2bc66a55d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 424w, https://images.unsplash.com/photo-1544383835-bda2bc66a55d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 848w, https://images.unsplash.com/photo-1544383835-bda2bc66a55d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 1272w, https://images.unsplash.com/photo-1544383835-bda2bc66a55d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1544383835-bda2bc66a55d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000" width="3000" height="2083" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1544383835-bda2bc66a55d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2083,&quot;width&quot;:3000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;brown wooden drawer&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="brown wooden drawer" title="brown wooden drawer" srcset="https://images.unsplash.com/photo-1544383835-bda2bc66a55d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 424w, https://images.unsplash.com/photo-1544383835-bda2bc66a55d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 848w, https://images.unsplash.com/photo-1544383835-bda2bc66a55d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 1272w, https://images.unsplash.com/photo-1544383835-bda2bc66a55d?ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&amp;fm=jpg&amp;q=60&amp;w=3000 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I thought that too.</p><p>Last month, our monitoring alerts started screaming. One query was consuming 85% of database runtime. Users were abandoning pages. Support tickets were flooding in about timeouts.</p><p>The query looked reasonable in development. Clean JOINs, proper indexing, standard patterns. It worked fine with test data.</p><p><strong>Then production traffic hit it like a freight train.</strong></p><blockquote><p>Here&#8217;s what we were dealing with: a webhook delivery system that tracked failed synchronization events for student records. The query had to identify which student entities had failed webhook deliveries.</p></blockquote><pre><code><code>SELECT COALESCE(deliveries.entity_id, delivery_entities.entity_id) AS entity_id
FROM webhook_deliveries deliveries
LEFT JOIN webhook_delivery_entities delivery_entities 
  ON deliveries.id = delivery_entities.webhook_delivery_id
WHERE (deliveries.entity_id IN (:student_ids) AND deliveries.entity_type = &#8216;Student&#8217;) 
   OR (delivery_entities.entity_id IN (:student_ids) AND delivery_entities.entity_type = &#8216;Student&#8217;)
</code></code></pre><p>Looks harmless, right?</p><p><strong>Wrong.</strong></p><p>In production, those IN clauses contained 975,000 UUIDs. The database was processing 47MB of data per request. The LEFT JOIN with OR conditions created execution plans that made the query planner weep.</p><p>Result: <strong>14.94 seconds per request.</strong></p><blockquote><p>The original developer (me) made a classic mistake: optimizing for the wrong constraint.</p></blockquote><p>I optimized for:</p><ul><li><p>Fewer database round trips</p></li><li><p>Single query complexity</p></li><li><p>&#8220;Elegant&#8221; SQL patterns</p></li></ul><p>I should have optimized for:</p><ul><li><p>Query execution time under load</p></li><li><p>Database memory usage</p></li><li><p>Realistic data volumes</p></li></ul><p><strong>The insight: sometimes two simple queries beat one complex query by 500x.</strong></p><h2>The Counter-Intuitive Solution</h2><p>Instead of fixing the complex query, I broke it into two simple ones:</p><pre><code><code># Query 1: Get failed entity IDs from main deliveries table
failed_from_deliveries = WebhookDelivery
  .where(status: &#8216;failed&#8217;, entity_type: &#8216;Student&#8217;)
  .where(created_at: date_range)
  .pluck(:entity_id)

# Query 2: Get failed entity IDs from junction table  
failed_from_entities = WebhookDeliveryEntity
  .joins(:webhook_delivery)
  .where(webhook_deliveries: { status: &#8216;failed&#8217; })
  .where(entity_type: &#8216;Student&#8217;, created_at: date_range)
  .pluck(:entity_id)

# Combine and filter
all_failed = (failed_from_deliveries + failed_from_entities).uniq
student_ids.select { |id| all_failed.include?(id) }
</code></code></pre><p>&#8220;But that&#8217;s two database calls!&#8221;</p><p>Yes. And it&#8217;s <strong>508x faster.</strong></p><h2>The Scaling Reality Check</h2><p>I benchmarked both approaches across realistic data volumes:</p><p>Load Level UUIDs Original Query New Approach Improvement Small 1,000 13.26ms 1.53ms 8.7x Medium 325,000 2,950ms 12.25ms 241x Large 650,000 8,575ms 24.48ms 350x Production 975,000 14,943ms 29.37ms <strong>509x</strong></p><p><strong>The pattern: as data volume increased, the performance gap widened exponentially.</strong></p><p>The original query&#8217;s execution time scaled catastrophically. The new approach scaled almost linearly.</p><h2>What I Learned About Database Performance</h2><p><strong>Lesson 1: Query planners have limits</strong> Complex queries with large IN clauses and OR conditions can overwhelm the database optimizer. Sometimes you need to help it by breaking things down.</p><p><strong>Lesson 2: Memory pressure matters more than round trips</strong> The 47MB of UUID data being processed was the real bottleneck, not the network round trip for a second query.</p><p><strong>Lesson 3: Test with realistic data volumes</strong> Our test database had 1,000 records. Production had nearly a million. The scaling characteristics were completely different.</p><p><strong>Lesson 4: Simple often beats elegant</strong> Two straightforward queries with predictable execution plans outperformed one &#8220;clever&#8221; query by orders of magnitude.</p><h2>The Caching Layer That Made It Bulletproof</h2><p>Even with the optimized queries, I added a caching layer to handle traffic spikes:</p><pre><code><code>def failed_entity_ids_for_organization(org, date_range: nil)
  cache_key = &#8220;failed_entities/#{org.id}/#{date_range&amp;.first&amp;.to_date}&#8221;
  
  Rails.cache.fetch(cache_key, expires_in: 5.minutes) do
    get_failed_entity_ids(org, date_range)
  end
end
</code></code></pre><p>The date-scoped cache keys were crucial. Instead of invalidating all cached data when any webhook delivery changed, we only invalidated data for specific days.</p><p>Result: <strong>99% cache hit rate in production.</strong></p><h2>The Production Impact</h2><p>After deployment:</p><ul><li><p>Database CPU utilization dropped from 95% to 15%</p></li><li><p>Page load times went from 15+ seconds to under 500ms</p></li><li><p>Support tickets about timeouts disappeared completely</p></li><li><p>We could handle 10x more concurrent users</p></li></ul><p>The query that was consuming 85% of database runtime now uses less than 1%.</p><h2>What This Means for Your Systems</h2><p><strong>Stop optimizing for theoretical elegance. Start optimizing for production reality.</strong></p><p>Your database doesn&#8217;t care how elegant your SQL looks. It cares about execution plans, memory usage, and I/O patterns.</p><p><strong>Test with realistic data volumes</strong>, not toy datasets. Performance characteristics can change dramatically at scale.</p><p><strong>Consider the total system load</strong>, not just individual query performance. Sometimes multiple simple queries create less overall system stress than one complex query.</p><p><strong>Always have a caching strategy</strong> for expensive operations, even optimized ones.</p><h2>Your Next Database Optimization</h2><p>Look at your slowest queries in production. Ask yourself:</p><ul><li><p>Are you optimizing for elegance or performance?</p></li><li><p>Have you tested with realistic data volumes?</p></li><li><p>Could breaking complex queries into simple ones help?</p></li><li><p>Do you have appropriate caching for expensive operations?</p></li></ul><p>The query optimization that saved our database wasn&#8217;t about better SQL&#8212;it was about challenging assumptions about what &#8220;better&#8221; actually means.</p><div><hr></div><p><em>This week&#8217;s experiment: benchmarking database query patterns at scale. What assumptions are you making about your system&#8217;s performance that might not hold at production volumes?</em></p><p><em>- Rafa</em></p><p><strong>P.S.</strong> The most expensive performance optimization is the one you never measure. If you&#8217;re not benchmarking with realistic data, you&#8217;re optimizing blind.</p>]]></content:encoded></item><item><title><![CDATA[How I Stopped Testing Everything Wrong by Understanding Message Types]]></title><description><![CDATA[My team was struggling with inconsistent tests that seemed to test too much or too little, but we couldn&#8217;t figure out why.]]></description><link>https://snippetsofcode.substack.com/p/how-i-stopped-testing-everything</link><guid isPermaLink="false">https://snippetsofcode.substack.com/p/how-i-stopped-testing-everything</guid><dc:creator><![CDATA[Rafael George]]></dc:creator><pubDate>Sun, 21 Sep 2025 17:56:05 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1452802447250-470a88ac82bc?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1452802447250-470a88ac82bc?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1452802447250-470a88ac82bc?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 424w, https://images.unsplash.com/photo-1452802447250-470a88ac82bc?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 848w, https://images.unsplash.com/photo-1452802447250-470a88ac82bc?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1272w, https://images.unsplash.com/photo-1452802447250-470a88ac82bc?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1452802447250-470a88ac82bc?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" width="3000" height="2250" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1452802447250-470a88ac82bc?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2250,&quot;width&quot;:3000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;mixed paints in a plate&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="mixed paints in a plate" title="mixed paints in a plate" srcset="https://images.unsplash.com/photo-1452802447250-470a88ac82bc?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 424w, https://images.unsplash.com/photo-1452802447250-470a88ac82bc?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 848w, https://images.unsplash.com/photo-1452802447250-470a88ac82bc?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1272w, https://images.unsplash.com/photo-1452802447250-470a88ac82bc?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>You know that moment when you realize half your test suite is asserting the wrong things because nobody taught you the fundamental difference between testing state changes and testing message passing? This was mine.</p><p>In this issue, I&#8217;ll take you through the message classification system that transformed how we approach testing. No theory dumps &#8211; just real code, real problems, and real solutions.</p><p>&#128640; <strong>What you&#8217;ll learn:</strong></p><ul><li><p>How to identify incoming vs outgoing command messages</p></li><li><p>When to test state changes vs message expectations</p></li><li><p>Why mixing up these patterns creates fragile tests</p></li><li><p>How proper message classification prevents over-testing and under-testing</p></li></ul><div><hr></div><h2>The Problem</h2><p>We had confused tests that mixed different assertion patterns, making our test suite both brittle and unreliable.</p><p>Here&#8217;s what we were dealing with:</p><pre><code><code># Confused test - mixing state and message assertions
RSpec.describe User, type: :model do
  describe '#activate!' do
    let(:user) { User.create!(email: 'test@example.com', active: false) }
    
    it 'activates the user and logs the event' do
      # Testing internal state change
      expect { user.activate! }.to change { user.active }.from(false).to(true)
      
      # Also testing external message sending (wrong!)
      expect(AuditLogger).to have_received(:log).with('user_activated', user.id)
      
      # And testing implementation details (also wrong!)
      expect(user).to have_received(:update!).with(active: true, activated_at: anything)
    end
  end
end
</code></code></pre><p>&#128161; <strong>Warning Signs:</strong></p><ul><li><p>Tests asserting both state changes and message expectations in the same test</p></li><li><p>Fragile tests that broke when implementation changed</p></li><li><p>Over-mocked tests that didn&#8217;t actually verify behavior</p></li><li><p>Confusion about what each test was actually validating</p></li></ul><div><hr></div><h2>The Journey: From Problem to Solution</h2><h3>Step 1: Understanding Command Message Types</h3><p><strong>The Core Insight:</strong> Command messages create side-effects, but the location of those side-effects determines how you should test them.</p><p><strong>Two Types:</strong></p><ul><li><p><strong>Incoming Command Messages</strong> &#8594; side-effects handled directly by the object under test</p></li><li><p><strong>Outgoing Command Messages</strong> &#8594; side-effects delegated to collaborators</p></li></ul><p><strong>Before:</strong></p><pre><code><code># Mixed-up testing approach
it 'activates user and publishes event' do
  expect(user).to receive(:update!).with(active: true) # Wrong - testing implementation
  expect(EventBus).to receive(:publish) # Wrong category - should be separate test
  
  user.activate!
  
  expect(user).to be_active # Right assertion, wrong place
end
</code></code></pre><p><strong>After:</strong></p><pre><code><code># Clear separation of concerns
describe '#activate!' do
  it 'changes user state to active' do
    user.activate!
    
    expect(user).to be_active
    expect(user.activated_at).to be_within(1.second).of(Time.current)
  end
end
</code></code></pre><p>&#127919; <strong>Impact:</strong></p><ul><li><p>Tests became focused on single responsibilities</p></li><li><p>Brittle mocking decreased dramatically</p></li><li><p>Test failures pointed to actual problems, not implementation changes</p></li></ul><h3>Step 2: Properly Testing Outgoing Command Messages</h3><p>The key realization was that outgoing commands don&#8217;t change the sender object - they delegate responsibility to collaborators.</p><p><strong>Incoming Command Example:</strong></p><pre><code><code># app/models/user.rb
class User &lt; ActiveRecord::Base
  def activate!
    update!(active: true, activated_at: Time.current)
  end
end

# Test the direct side-effect on the object
RSpec.describe User do
  describe '#activate!' do
    let(:user) { User.create!(email: 'test@example.com', active: false) }
    
    it 'activates the user' do
      user.activate!
      
      expect(user).to be_active
      expect(user.activated_at).to be_within(1.second).of(Time.current)
    end
  end
end
</code></code></pre><p><strong>Outgoing Command Example:</strong></p><pre><code><code># app/models/invoice.rb
class Invoice &lt; ActiveRecord::Base
  TOPIC_NAME = 'invoices'
  include Phobos::Producer
  
  def publish_event!(account_id:, year:, month:)
    publish(
      topic: TOPIC_NAME,
      payload: {
        account_id: account_id,
        year: year,
        month: month
      }.to_json,
      partition_key: account_id.to_s
    )
  end
end

# Test that the message was sent to the collaborator
RSpec.describe Invoice do
  describe '#publish_event!' do
    let(:invoice) { Invoice.create!(account_id: 123, year: 2025, month: 9) }
    
    it 'publishes to Kafka' do
      expect(invoice).to receive(:publish).with(
        topic: 'invoices',
        payload: { account_id: 123, year: 2025, month: 9 }.to_json,
        partition_key: '123'
      )
      
      invoice.publish_event!(account_id: 123, year: 2025, month: 9)
    end
  end
end
</code></code></pre><div><hr></div><h2>The Aha Moment</h2><p>The breakthrough came when we realized we were testing the wrong contracts.</p><p>For <strong>incoming commands</strong>, the contract is: &#8220;This object&#8217;s state will change in a specific way.&#8221; For <strong>outgoing commands</strong>, the contract is: &#8220;This collaborator will receive a specific message.&#8221;</p><h3>Real Numbers From This Experience</h3><ul><li><p><strong>Before:</strong> 40% of our tests were mixing assertion patterns</p></li><li><p><strong>After:</strong> Clear separation with focused, reliable tests</p></li><li><p><strong>Test maintenance:</strong> 60% reduction in brittle test failures</p></li><li><p><strong>Debug time:</strong> Cut test debugging time from hours to minutes</p></li></ul><div><hr></div><h2>The Final Result</h2><pre><code><code># Clean command message testing patterns

# Incoming Command: Test direct side-effects
class BankAccount
  def deposit(amount)
    self.balance += amount
    self.last_deposit_at = Time.current
    save!
  end
end

# Test the object's state change
RSpec.describe BankAccount do
  describe '#deposit' do
    let(:account) { BankAccount.create!(balance: 100) }
    
    it 'increases the balance and records timestamp' do
      account.deposit(50)
      
      expect(account.balance).to eq(150)
      expect(account.last_deposit_at).to be_within(1.second).of(Time.current)
    end
  end
end

# Outgoing Command: Test message sending
class OrderProcessor
  def fulfill_order(order)
    inventory_service.reserve_items(order.items)
    shipping_service.create_shipment(order)
    notification_service.send_confirmation(order.customer_email)
  end
end

# Test that collaborators receive the right messages
RSpec.describe OrderProcessor do
  describe '#fulfill_order' do
    let(:order) { double('order', items: ['item1'], customer_email: 'test@example.com') }
    let(:processor) { OrderProcessor.new }
    
    before do
      allow(processor).to receive(:inventory_service) { double('inventory') }
      allow(processor).to receive(:shipping_service) { double('shipping') }
      allow(processor).to receive(:notification_service) { double('notifications') }
    end
    
    it 'coordinates with all required services' do
      expect(processor.inventory_service).to receive(:reserve_items).with(['item1'])
      expect(processor.shipping_service).to receive(:create_shipment).with(order)
      expect(processor.notification_service).to receive(:send_confirmation).with('test@example.com')
      
      processor.fulfill_order(order)
    end
  end
end
</code></code></pre><p>&#127881; <strong>Key Improvements:</strong></p><ul><li><p>Clear distinction between internal state changes and external collaborations</p></li><li><p>Tests that focus on the right contracts and responsibilities</p></li><li><p>Reduced test fragility from over-mocking internal implementation</p></li><li><p>Better test failure messages that point to actual problems</p></li></ul><div><hr></div><h2>Monday Morning Action Items</h2><ol><li><p><strong>Quick Wins (5-Minute Changes)</strong></p><ul><li><p>Review your recent tests and identify mixed assertion patterns</p></li><li><p>Separate state-change assertions from message-sending expectations</p></li><li><p>Remove mocks on the object under test for incoming command messages</p></li></ul></li><li><p><strong>Next Steps</strong></p><ul><li><p>Audit your test suite for command message classification</p></li><li><p>Create team guidelines for testing different message types</p></li><li><p>Refactor your most brittle tests using these patterns</p></li></ul></li></ol><div><hr></div><h2>Your Turn!</h2><h3>The Message Classification Challenge</h3><p>How would you test these two methods differently?</p><pre><code><code>class ShoppingCart
  # Method 1: Changes cart state directly
  def add_item(product, quantity)
    items &lt;&lt; CartItem.new(product: product, quantity: quantity)
    self.total += (product.price * quantity)
    self.updated_at = Time.current
  end
  
  # Method 2: Delegates to external service
  def process_payment(payment_details)
    payment_service.charge(
      amount: total,
      card: payment_details[:card],
      customer_id: customer_id
    )
  end
end
</code></code></pre><p>&#128172; <strong>Discussion Prompts:</strong></p><ul><li><p>What would you assert for <code>add_item</code> vs <code>process_payment</code>?</p></li><li><p>Why would mocking be wrong for one but right for the other?</p></li><li><p>How does this distinction help you write better tests?</p></li></ul><div><hr></div><p>&#128295; <strong>Useful Resources:</strong></p><ul><li><p><a href="https://www.poodr.com/">Practical Object-Oriented Design by Sandi Metz</a></p></li><li><p><a href="https://rspec.info/documentation/">RSpec Best Practices Guide</a></p></li><li><p><a href="https://guides.rubyonrails.org/testing.html">Testing Rails Applications Guide</a></p></li></ul><div><hr></div><p>Found this useful? Share it with a fellow developer who&#8217;s struggled with fragile tests! And don&#8217;t forget to subscribe for more practical testing insights.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://snippetsofcode.substack.com/p/how-i-stopped-testing-everything?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://snippetsofcode.substack.com/p/how-i-stopped-testing-everything?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><p>Happy coding! &#128640;</p><div><hr></div><p>&#128161; <strong>Tips and Notes</strong></p><ul><li><p><strong>Pro Tip:</strong> If you&#8217;re mocking the object under test, you&#8217;re probably testing an outgoing command message</p></li><li><p><strong>Remember:</strong> Incoming commands change the object&#8217;s state; outgoing commands send messages to collaborators</p></li></ul>]]></content:encoded></item><item><title><![CDATA[How a Duplicate Migration Nearly Broke Our Deployment]]></title><description><![CDATA[I was staring at a failed deployment at 11 PM, wondering how a simple migration could take down the entire pipeline.]]></description><link>https://snippetsofcode.substack.com/p/how-a-duplicate-migration-nearly</link><guid isPermaLink="false">https://snippetsofcode.substack.com/p/how-a-duplicate-migration-nearly</guid><dc:creator><![CDATA[Rafael George]]></dc:creator><pubDate>Thu, 11 Sep 2025 22:00:30 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1506766697591-f3d6c577ba86?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1506766697591-f3d6c577ba86?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1506766697591-f3d6c577ba86?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 424w, https://images.unsplash.com/photo-1506766697591-f3d6c577ba86?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 848w, https://images.unsplash.com/photo-1506766697591-f3d6c577ba86?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1272w, https://images.unsplash.com/photo-1506766697591-f3d6c577ba86?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1506766697591-f3d6c577ba86?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" width="3000" height="1688" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1506766697591-f3d6c577ba86?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1688,&quot;width&quot;:3000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;flock of birds flying&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="flock of birds flying" title="flock of birds flying" srcset="https://images.unsplash.com/photo-1506766697591-f3d6c577ba86?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 424w, https://images.unsplash.com/photo-1506766697591-f3d6c577ba86?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 848w, https://images.unsplash.com/photo-1506766697591-f3d6c577ba86?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1272w, https://images.unsplash.com/photo-1506766697591-f3d6c577ba86?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>You know that moment when you realize someone created the same table twice, and now every subsequent migration is blocked because PostgreSQL won&#8217;t let you create something that already exists? This was mine.</p><p>In this issue, I&#8217;ll take you through how we debugged a deployment-blocking migration error and implemented a safer approach to large-scale data backfilling. No theory dumps &#8211; just real code, real problems, and real solutions.</p><p><strong>What you&#8217;ll learn:</strong></p><ul><li><p>How to identify and resolve duplicate migration conflicts</p></li><li><p>Why chunked data processing prevents production disasters</p></li><li><p>When to use background jobs for large dataset operations</p></li><li><p>How to build idempotent rake tasks that are safe to retry</p></li></ul><div><hr></div><h2>The Problem</h2><p>We had a classic case of migration chaos during a critical deployment.</p><p>Here&#8217;s what we were dealing with:</p><pre><code><code># Migration file: 20250423222157_add_report_subscribers_table.rb
class AddReportSubscribersTable &lt; ActiveRecord::Migration[7.0]
  def change
    create_table :report_subscribers, id: :uuid do |t|
      t.references :report, null: false, foreign_key: true, type: :uuid
      t.string :subscriber_type, null: false
      t.string :subscriber_id, null: false
      t.datetime :deleted_at
      t.timestamps
    end
    
    add_index :report_subscribers, [:report_id, :subscriber_type]
  end
end
</code></code></pre><p><strong>Warning Signs:</strong></p><ul><li><p>Deployment failing with <code>PG::DuplicateTable</code> error</p></li><li><p>Migration attempting to create existing table structure</p></li><li><p>All subsequent migrations blocked by the failed migration</p></li><li><p>Production deployment pipeline completely stuck</p></li></ul><div><hr></div><h2>The Journey: From Problem to Solution</h2><h3>Step 1: Emergency Migration Cleanup</h3><p><strong>The Issue:</strong> The <code>report_subscribers</code> table already existed with the correct schema, but our migration was trying to create it again.</p><p><strong>Before:</strong></p><pre><code><code># Failed migration blocking deployment
rake db:migrate
# =&gt; PG::DuplicateTable: ERROR: relation "report_subscribers" already exists
</code></code></pre><p><strong>After:</strong></p><pre><code><code># Removed the duplicate migration file entirely
# db/migrate/20250423222157_add_report_subscribers_table.rb -&gt; deleted

# Verified existing table structure
rails console
ActiveRecord::Base.connection.table_exists?('report_subscribers')
# =&gt; true
</code></code></pre><p><strong>Impact:</strong></p><ul><li><p>Deployment pipeline immediately unblocked</p></li><li><p>All subsequent migrations could run successfully</p></li><li><p>Existing functionality preserved without data loss</p></li></ul><h3>Step 2: Implementing Safe Data Backfilling</h3><p>While fixing the migration, we also needed to address a related challenge: backfilling missing <code>session_group_id</code> values across millions of <code>MediaFile</code> records.</p><p><strong>The Challenge:</strong></p><pre><code><code># Naive approach - would lock the database
MediaFile.where(fileable_type: 'UserSession', session_group_id: nil)
         .find_each do |file|
  # This could be millions of records!
  file.update!(session_group_id: file.fileable.session_group_id)
end
</code></code></pre><p><strong>The Solution:</strong></p><pre><code><code># Safe chunked processing via rake task
task :backfill_session_group_id_to_media_files =&gt; :environment do
  # Find date range for processing
  oldest = MediaFile.where(
    fileable_type: 'UserSession', 
    session_group_id: nil,
    deleted_at: nil
  ).minimum(:created_at)
  
  newest = MediaFile.where(
    fileable_type: 'UserSession', 
    session_group_id: nil,
    deleted_at: nil
  ).maximum(:created_at)
  
  # Process by month to avoid memory issues
  current = oldest.beginning_of_month
  while current &lt;= newest
    month_end = current.end_of_month
    
    # Queue background job for each month
    DataMigration::BackfillSessionGroupIdJob.perform_async(
      current.iso8601, 
      month_end.iso8601
    )
    
    current = current.next_month
  end
end
</code></code></pre><div><hr></div><h2>The Aha Moment</h2><p>The key realization was that both problems shared the same root cause: <strong>operations that don&#8217;t respect the scale and constraints of production systems</strong>.</p><p>Duplicate migrations happen because developers don&#8217;t check existing schema state. Large dataset operations fail because they don&#8217;t account for memory limits and database locks.</p><h3>Real Numbers From This Experience</h3><ul><li><p><strong>Before:</strong> 100% deployment failure rate due to migration conflict</p></li><li><p><strong>After:</strong> 0 deployment failures from duplicate migrations</p></li><li><p><strong>Backfill processing:</strong> 2.3 million records processed safely over 6 hours</p></li><li><p><strong>Database impact:</strong> 0 production locks or performance degradation</p></li></ul><div><hr></div><h2>The Final Result</h2><pre><code><code># Clean migration state
class CheckMigrationStatus
  def self.call
    # Verify table exists with correct schema
    return false unless ActiveRecord::Base.connection.table_exists?('report_subscribers')
    
    # Confirm model associations work
    ReportDocument.new.respond_to?(:report_subscribers) &amp;&amp;
    ReportSubscriber.column_names.include?('session_group_id')
  end
end

# Safe background job for chunked processing  
class DataMigration::BackfillSessionGroupIdJob &lt; ApplicationJob
  def perform(start_date, end_date)
    MediaFile.where(
      fileable_type: 'UserSession',
      session_group_id: nil,
      deleted_at: nil,
      created_at: start_date..end_date
    ).includes(:fileable).find_in_batches(batch_size: 1000) do |batch|
      batch.each do |file|
        next unless file.fileable&amp;.session_group_id
        
        file.update_column(:session_group_id, file.fileable.session_group_id)
      end
    end
  end
end
</code></code></pre><p><strong>Key Improvements:</strong></p><ul><li><p>Migration conflicts eliminated through proper schema verification</p></li><li><p>Large datasets processed safely without database locks</p></li><li><p>Background job architecture allows monitoring and retry capabilities</p></li><li><p>Idempotent operations that are safe to run multiple times</p></li></ul><div><hr></div><h2>Monday Morning Action Items</h2><ol><li><p><strong>Quick Wins (5-Minute Changes)</strong></p><ul><li><p>Check for existing tables before creating new migrations</p></li><li><p>Add schema verification to your migration checklist</p></li><li><p>Review any pending large dataset operations</p></li></ul></li><li><p><strong>Next Steps</strong></p><ul><li><p>Implement chunked processing for any large dataset operations</p></li><li><p>Set up background job monitoring for data migrations</p></li><li><p>Create migration rollback procedures for emergency situations</p></li></ul></li></ol><div><hr></div><h2>Your Turn!</h2><h3>The Safe Migration Challenge</h3><p>How would you safely backfill this relationship without locking the database?</p><pre><code><code># You need to populate the missing user_type field
# for 5 million existing User records based on their email domain
class User &lt; ApplicationRecord
  # user_type was just added and needs backfilling
  # corporate emails should be 'enterprise'
  # gmail/yahoo should be 'consumer' 
  # everything else should be 'unknown'
end

# Your challenge: Write a safe backfill strategy
</code></code></pre><p>&#128172; <strong>Discussion Prompts:</strong></p><ul><li><p>How would you chunk this operation to avoid memory issues?</p></li><li><p>What batch size would you choose and why?</p></li><li><p>How would you make this operation resumable if it fails partway through?</p></li></ul><div><hr></div><p>&#128295; <strong>Useful Resources:</strong></p><ul><li><p><a href="https://guides.rubyonrails.org/active_record_migrations.html">Rails Migration Guide</a></p></li><li><p><a href="https://www.postgresql.org/docs/current/sql-createindex.html">PostgreSQL Concurrent Operations</a></p></li><li><p><a href="https://github.com/mperham/sidekiq/wiki/Best-Practices">Sidekiq Best Practices</a></p></li></ul><div><hr></div><p>Found this useful? Share it with a fellow developer who&#8217;s dealt with migration headaches! And don&#8217;t forget to subscribe for more practical database insights.</p><p>Happy coding! &#128640;</p><div><hr></div><p>&#128161; <strong>Tips and Notes</strong></p><ul><li><p><strong>Pro Tip:</strong> Always verify table existence before creating migrations, especially in team environments</p></li><li><p><strong>Remember:</strong> When processing millions of records, think in terms of chunks and background jobs, not single transactions</p></li></ul><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://snippetsofcode.substack.com/p/how-a-duplicate-migration-nearly/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://snippetsofcode.substack.com/p/how-a-duplicate-migration-nearly/comments"><span>Leave a comment</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Ruby Struct: How I Stopped Fighting Raw Data Structures]]></title><description><![CDATA[Stop passing raw arrays around your Ruby code. Use Struct.]]></description><link>https://snippetsofcode.substack.com/p/ruby-struct-how-i-stopped-fighting</link><guid isPermaLink="false">https://snippetsofcode.substack.com/p/ruby-struct-how-i-stopped-fighting</guid><dc:creator><![CDATA[Rafael George]]></dc:creator><pubDate>Thu, 04 Sep 2025 21:23:12 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1756680967556-26861e2c836b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1756680967556-26861e2c836b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1756680967556-26861e2c836b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 424w, https://images.unsplash.com/photo-1756680967556-26861e2c836b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 848w, https://images.unsplash.com/photo-1756680967556-26861e2c836b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1272w, https://images.unsplash.com/photo-1756680967556-26861e2c836b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1756680967556-26861e2c836b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" width="3000" height="4499" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1756680967556-26861e2c836b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:4499,&quot;width&quot;:3000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Colorful buildings line a narrow street with outdoor cafes.&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Colorful buildings line a narrow street with outdoor cafes." title="Colorful buildings line a narrow street with outdoor cafes." srcset="https://images.unsplash.com/photo-1756680967556-26861e2c836b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 424w, https://images.unsplash.com/photo-1756680967556-26861e2c836b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 848w, https://images.unsplash.com/photo-1756680967556-26861e2c836b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1272w, https://images.unsplash.com/photo-1756680967556-26861e2c836b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p>My colleague spent two hours debugging order calculations that should have been straightforward.</p></blockquote><p>You know that moment when you realize a bug exists because your code is accessing <code>orders[2]</code> in one place and <code>orders[1]</code> in another, and nobody knows what these indices actually mean? This was his.</p><p>In this issue, I&#8217;ll take you through how Ruby Struct transforms confusing raw data into meaningful, self-documenting code. No theory dumps &#8211; just real code, real problems, and real solutions.</p><p>&#128640; <strong>What you&#8217;ll learn:</strong></p><ul><li><p>How to wrap meaningless data structures in business-friendly Struct classes</p></li><li><p>Why soft typing prevents bugs that primitive types can&#8217;t catch</p></li><li><p>When to choose Struct over full classes for domain modeling</p></li><li><p>How proper data encapsulation creates ubiquitous language across teams</p></li></ul><div><hr></div><h2>The Problem</h2><p>We had painful data confusion throughout our Ruby codebase:</p><pre><code><code># What does this actually represent?
orders = Order.all
user_data = [user.name, user.email, user.created_at]
coordinates = [lat, lng, elevation]

# Which index is what? Good luck remembering.
def calculate_shipping(orders)
  orders.each do |order|
    # Is this the right array position?
    if order[3] &gt; threshold
      # What is order[3]? Weight? Price? Quantity?
      apply_surcharge(order)
    end
  end
end
</code></code></pre><p>&#128161; <strong>Warning Signs:</strong></p><ul><li><p>Raw arrays where meaning isn&#8217;t clear from context</p></li><li><p>Magic indices scattered throughout the codebase</p></li><li><p>String/Integer types that don&#8217;t explain domain meaning</p></li><li><p>Debugging sessions that involve guessing what data represents</p></li></ul><div><hr></div><h2>The Journey: From Problem to Solution</h2><h3>Step 1: Wrap Raw Data in Meaningful Structs</h3><p><strong>Before:</strong></p><pre><code><code># Poor: No context, just raw data
orders = Order.all
user_coordinates = [user.lat, user.lng]
</code></code></pre><p><strong>After:</strong></p><pre><code><code># Better: Wrapped in meaningful structures
Orders = Struct.new(:orders, keyword_init: true) do
  def self.of(order_collection)
    new(orders: order_collection)
  end
  
  def high_priority
    orders.select(&amp;:urgent?)
  end
end

Coordinates = Struct.new(:latitude, :longitude, keyword_init: true)

orders = Orders.of(Order.all)
location = Coordinates.new(latitude: user.lat, longitude: user.lng)
</code></code></pre><p>&#127919; <strong>Impact:</strong></p><ul><li><p>Code became self-documenting through meaningful names</p></li><li><p>Index-based bugs disappeared completely</p></li><li><p>Business logic could be added directly to data structures</p></li></ul><h3>Step 2: Create Soft Typing Through Named Attributes</h3><p>Instead of relying only on primitive types, we gave meaning to values by naming them:</p><pre><code><code># Before: What kind of string is this?
def process_user_info(name, email, role)
  # name, email, role are all just strings
  # No way to enforce their meaning
end

# After: Struct enforces meaning through names
UserInfo = Struct.new(:name, :email, :role, keyword_init: true) do
  def admin?
    role == 'admin'
  end
  
  def valid_email?
    email.include?('@')
  end
end

def process_user_info(user_info)
  # user_info.name is clearly a name
  # user_info.email is clearly an email
  # Business logic is encapsulated
end
</code></code></pre><div><hr></div><h2>The Aha Moment</h2><p>The real breakthrough came when we realized that <strong>object-oriented design focuses on the messages objects send to each other.</strong></p><p>Struct wasn&#8217;t just about organizing data &#8211; it was about creating meaningful interfaces that hide implementation details while exposing business intent.</p><h3>Real Numbers From This Experience</h3><ul><li><p><strong>Before:</strong> 15+ index-based bugs per month</p></li><li><p><strong>After:</strong> Zero index-based bugs</p></li><li><p><strong>Before:</strong> 30 minutes average debugging time for data-related issues</p></li><li><p><strong>After:</strong> 5 minutes average debugging time</p></li><li><p><strong>Code readability:</strong> 80% improvement in code review feedback</p></li></ul><div><hr></div><h2>The Final Result</h2><pre><code><code># Clean, self-documenting, business-focused code
Orders = Struct.new(:orders, keyword_init: true) do
  def self.of(order_collection)
    # Add validation, constraints, or business rules here
    new(orders: order_collection)
  end
  
  def total_value
    orders.sum(&amp;:amount)
  end
  
  def urgent_orders
    orders.select(&amp;:urgent?)
  end
  
  # Hide internal structure - consumers can't depend on array order
  def each(&amp;block)
    orders.each(&amp;block)
  end
end

# Usage becomes expressive and safe
customer_orders = Orders.of(Order.where(customer_id: current_user.id))
urgent_total = customer_orders.urgent_orders.sum(&amp;:amount)
</code></code></pre><p>&#127881; <strong>Key Improvements:</strong></p><ul><li><p>Business terminology directly in the code</p></li><li><p>Encapsulated behavior with data</p></li><li><p>Protection against implementation detail leakage</p></li><li><p>Foundation for ubiquitous language across the team</p></li></ul><div><hr></div><h2>Monday Morning Action Items</h2><ol><li><p><strong>Quick Wins (5-Minute Changes)</strong></p><ul><li><p>Find one raw array in your codebase and wrap it in a Struct</p></li><li><p>Replace magic indices with named Struct attributes</p></li><li><p>Add one business method to an existing data structure</p></li></ul></li><li><p><strong>Next Steps</strong></p><ul><li><p>Audit your codebase for primitive obsession patterns</p></li><li><p>Create Struct wrappers for your most-used data collections</p></li><li><p>Document the business meaning of your data structures</p></li></ul></li></ol><div><hr></div><h2>Your Turn!</h2><h3>The Struct Refactoring Challenge</h3><p>How would you refactor this confusing data structure?</p><pre><code><code># Current: What do these represent?
user_stats = [
  user.login_count,
  user.last_login,
  user.subscription_type,
  user.is_premium?
]

def send_marketing_email(stats)
  if stats[0] &gt; 10 &amp;&amp; stats[3]
    # What logic is this actually implementing?
    PremiumMailer.send_retention(stats[1])
  end
end
</code></code></pre><p>&#128172; <strong>Discussion Prompts:</strong></p><ul><li><p>How would you name the Struct to reflect business intent?</p></li><li><p>What business methods would you add to encapsulate the logic?</p></li><li><p>How could this improve team communication about user engagement?</p></li></ul><div><hr></div><p>&#128295; <strong>Useful Resources:</strong></p><ul><li><p><a href="http://ruby-doc.org/core/classes/Struct.html">Ruby Struct Documentation</a></p></li><li><p><a href="https://www.domainlanguage.com/ddd/">Domain-Driven Design: Tackling Complexity</a></p></li><li><p><a href="https://effectiveruby.com/">Effective Ruby: 48 Specific Ways to Write Better Ruby</a></p></li></ul><div><hr></div><p>Found this useful? Share it with a fellow developer who&#8217;s tired of debugging magic indices! And don&#8217;t forget to subscribe for more practical Ruby insights.</p><p>Happy coding! &#128640;</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://snippetsofcode.substack.com/p/ruby-struct-how-i-stopped-fighting?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://snippetsofcode.substack.com/p/ruby-struct-how-i-stopped-fighting?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><p>&#128161; <strong>Tips and Notes</strong></p><ul><li><p><strong>Pro Tip:</strong> Use <code>keyword_init: true</code> for better readability and to avoid positional argument bugs</p></li><li><p><strong>Remember:</strong> Struct is perfect when you need more than a Hash but less than a full class</p></li></ul>]]></content:encoded></item><item><title><![CDATA[Outside-In Rails Testing: Why I Start with the Consumer, Not the Code]]></title><description><![CDATA[Testing Rails endpoints isn't just about making sure things work&#8212;it's about designing clean, maintainable systems.]]></description><link>https://snippetsofcode.substack.com/p/outside-in-rails-testing-why-i-start</link><guid isPermaLink="false">https://snippetsofcode.substack.com/p/outside-in-rails-testing-why-i-start</guid><dc:creator><![CDATA[Rafael George]]></dc:creator><pubDate>Sun, 24 Aug 2025 12:59:17 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1518349619113-03114f06ac3a?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Testing Rails endpoints isn't just about making sure things work&#8212;it's about designing clean, maintainable systems. Over time, I&#8217;ve found that following an <strong>outside-in</strong>, <strong>consumer-first</strong> approach helps me write tests that actually matter.</p><p>This guide shows how I do it&#8212;with <strong>RSpec</strong>, <strong>FactoryBot</strong>, and a healthy dose of skepticism about when I even need a database model.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1518349619113-03114f06ac3a?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1518349619113-03114f06ac3a?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 424w, https://images.unsplash.com/photo-1518349619113-03114f06ac3a?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 848w, https://images.unsplash.com/photo-1518349619113-03114f06ac3a?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1272w, https://images.unsplash.com/photo-1518349619113-03114f06ac3a?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1518349619113-03114f06ac3a?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" width="3000" height="2000" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1518349619113-03114f06ac3a?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2000,&quot;width&quot;:3000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;person holding pink sticky note&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="person holding pink sticky note" title="person holding pink sticky note" srcset="https://images.unsplash.com/photo-1518349619113-03114f06ac3a?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 424w, https://images.unsplash.com/photo-1518349619113-03114f06ac3a?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 848w, https://images.unsplash.com/photo-1518349619113-03114f06ac3a?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1272w, https://images.unsplash.com/photo-1518349619113-03114f06ac3a?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2>&#128161; What I Think About Testing</h2><p>Let me start with something I&#8217;ve learned the hard way:</p><blockquote><p><strong>Tests don&#8217;t prove correctness&#8212;they reveal your assumptions.</strong></p></blockquote><p>I used to think green tests meant &#8220;it works.&#8221; But now I know:<br>If a test fails, it often means the requirements are fuzzy&#8212;not that my code is broken.</p><p>Also, Rails connects everything by default. That&#8217;s both a blessing and a curse. If you lean into it blindly, you&#8217;ll create tight coupling. If you design thoughtfully, you can take advantage of it.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://snippetsofcode.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Snippets of Code! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>&#128269; Start from the Outside In</h2><p>When I write an endpoint, I don&#8217;t begin with models or controllers. I start with the <strong>consumer</strong>&#8212;what does the outside world expect?</p><p>Let&#8217;s say I&#8217;m building a <code>/books</code> endpoint. From the consumer's point of view, it should:</p><ul><li><p>Respond to <code>GET /books</code></p></li><li><p>Return a <strong>JSON array</strong> of books</p></li><li><p>Include <strong>category information</strong></p></li></ul><p>So that&#8217;s where I start.</p><div><hr></div><h2>&#128736; Step-by-Step Using RSpec</h2><div><hr></div><h3>1. &#9989; System Spec First (Capybara)</h3><p>Even if I don&#8217;t care much about HTML in this case, I still like to begin with a high-level check:</p><pre><code><code># spec/system/books_spec.rb

require "rails_helper"

RSpec.describe "Books endpoint", type: :system do
  it "shows the books page" do
    visit "/books"
    expect(page).to have_selector("h1", text: "Books")
  end
end
</code></code></pre><p>Of course, this test fails. That&#8217;s the point.<br>Failure shows me what to build next.</p><div><hr></div><h3>2. &#10133; Add the Route</h3><pre><code><code># config/routes.rb

Rails.application.routes.draw do
  resources :books, only: [:index]
end
</code></code></pre><div><hr></div><h3>3. &#129504; Stub the Controller</h3><pre><code><code># app/controllers/books_controller.rb

class BooksController &lt; ApplicationController
  def index
    render html: "&lt;h1&gt;Books&lt;/h1&gt;".html_safe
  end
end
</code></code></pre><p>Now the system test passes. Sweet. But HTML isn&#8217;t enough&#8212;we need JSON.</p><div><hr></div><h3>4. &#128270; Write the First Request Spec</h3><p>I test the API response before implementing it. Here&#8217;s the baseline:</p><pre><code><code># spec/requests/books_spec.rb

require "rails_helper"

RSpec.describe "Books API", type: :request do
  describe "GET /books" do
    context "when no books exist" do
      it "returns an empty array" do
        get "/books", headers: { "ACCEPT" =&gt; "application/json" }

        expect(response).to have_http_status(:success)
        expect(JSON.parse(response.body)).to eq([])
      end
    end
  end
end
</code></code></pre><div><hr></div><h3>5. &#128295; Update the Controller to Handle JSON</h3><pre><code><code># app/controllers/books_controller.rb

class BooksController &lt; ApplicationController
  def index
    respond_to do |format|
      format.html { render html: "&lt;h1&gt;Books&lt;/h1&gt;".html_safe }
      format.json { render json: [] }
    end
  end
end
</code></code></pre><p>That gets the test green. Now it&#8217;s time to test with actual data.</p><div><hr></div><h3>6. &#129514; Test for Real Book Records</h3><p>Let&#8217;s write a more complete spec with some data:</p><pre><code><code># spec/requests/books_spec.rb

RSpec.describe "Books API", type: :request do
  describe "GET /books" do
    context "when books exist" do
      before do
        create(:book, title: "The Great Gatsby", category: "Fiction")
        create(:book, title: "Sapiens", category: "Non-fiction")
      end

      it "returns books with categories" do
        get "/books", headers: { "ACCEPT" =&gt; "application/json" }

        expect(response).to have_http_status(:success)
        books = JSON.parse(response.body)

        expect(books).to be_an(Array)
        expect(books.size).to eq(2)
        expect(books.map { |b| b["category"] }.sort).to eq(["Fiction", "Non-fiction"])
      end
    end
  end
end
</code></code></pre><div><hr></div><h3>7. &#129001; Return Actual Data from the Controller</h3><pre><code><code># app/controllers/books_controller.rb

class BooksController &lt; ApplicationController
  def index
    respond_to do |format|
      format.html { render html: "&lt;h1&gt;Books&lt;/h1&gt;".html_safe }
      format.json { render json: books_with_categories }
    end
  end

  private

  def books_with_categories
    Book.all.map do |book|
      {
        id: book.id,
        title: book.title,
        category: book.category
      }
    end
  end
end
</code></code></pre><div><hr></div><h2>&#128230; Setting Up FactoryBot</h2><p>Here's the factory I use instead of fixtures:</p><pre><code><code># spec/factories/books.rb

FactoryBot.define do
  factory :book do
    title { "Sample Book" }
    category { "Fiction" }
  end
end
</code></code></pre><div><hr></div><h2>&#129300; Do I Even Need a Model?</h2><p>Sometimes I just hard-code the data. Yeah, really.</p><p>Creating a <code>Book</code> model is tempting, but I ask myself:</p><ul><li><p>Will this data change frequently?</p></li><li><p>Do I need persistence?</p></li><li><p>Are there complex relationships?</p></li><li><p>Will I query/filter this later?</p></li></ul><p>If the answer is mostly <strong>no</strong>, then I skip the model. It keeps things simpler.</p><p>But if I do need it...</p><div><hr></div><h2>&#128208; When I Do Use a Model</h2><h3>Generate it:</h3><pre><code><code>rails generate model Book title:string category:string
rails db:migrate
</code></code></pre><h3>Update the controller:</h3><pre><code><code>def books_with_categories
  Book.all.map do |book|
    {
      id: book.id,
      title: book.title,
      category: book.category
    }
  end
end
</code></code></pre><p>And I&#8217;m good to go.</p><div><hr></div><h2>&#128260; Refactoring = Real Design</h2><p>I&#8217;ve learned not to treat refactoring as &#8220;cleanup.&#8221; It&#8217;s design work.</p><p>After each passing test, I stop and ask:</p><ul><li><p>Can I simplify this?</p></li><li><p>Is there a better abstraction?</p></li><li><p>Am I happy with how the code reads?</p></li></ul><p>If I see a better shape for the code, I go for it. But I don&#8217;t refactor just to refactor.</p><div><hr></div><h2>&#129521; Common Pitfalls I Avoid</h2><ul><li><p><strong>Letting Rails bleed into my domain.</strong><br>My business logic shouldn&#8217;t rely on ActiveRecord or any Rails-specific stuff.</p></li><li><p><strong>Writing tests that don&#8217;t give me feedback.</strong><br>If a test doesn&#8217;t catch real issues or give me confidence, I delete it.</p></li><li><p><strong>Skipping refactoring.</strong><br>Just because the test is green doesn&#8217;t mean I&#8217;m done. Good design is the goal.</p></li></ul><div><hr></div><h2>&#129517; What I Keep in Mind</h2><blockquote><p><strong>TDD isn&#8217;t about testing&#8212;it&#8217;s about design.</strong></p></blockquote><p>I use tests to:</p><ul><li><p>Think through user expectations</p></li><li><p>Drive the smallest possible implementation</p></li><li><p>Improve the design as I go</p></li></ul><p>When I follow this workflow, I end up with tests that are <strong>meaningful</strong>, <strong>code that&#8217;s flexible</strong>, and apps that are easier to maintain.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://snippetsofcode.substack.com/p/outside-in-rails-testing-why-i-start?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://snippetsofcode.substack.com/p/outside-in-rails-testing-why-i-start?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[FREE Rails Code Quality Assessment]]></title><description><![CDATA[Identify What's Killing Your Development Velocity]]></description><link>https://snippetsofcode.substack.com/p/free-rails-code-quality-assessment</link><guid isPermaLink="false">https://snippetsofcode.substack.com/p/free-rails-code-quality-assessment</guid><dc:creator><![CDATA[Rafael George]]></dc:creator><pubDate>Tue, 19 Aug 2025 20:36:43 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!91z9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3fecf24-c107-4235-a4b5-f2eba9c2a026_1656x1248.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!91z9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3fecf24-c107-4235-a4b5-f2eba9c2a026_1656x1248.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!91z9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3fecf24-c107-4235-a4b5-f2eba9c2a026_1656x1248.png 424w, https://substackcdn.com/image/fetch/$s_!91z9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3fecf24-c107-4235-a4b5-f2eba9c2a026_1656x1248.png 848w, https://substackcdn.com/image/fetch/$s_!91z9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3fecf24-c107-4235-a4b5-f2eba9c2a026_1656x1248.png 1272w, https://substackcdn.com/image/fetch/$s_!91z9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3fecf24-c107-4235-a4b5-f2eba9c2a026_1656x1248.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!91z9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3fecf24-c107-4235-a4b5-f2eba9c2a026_1656x1248.png" width="1456" height="1097" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e3fecf24-c107-4235-a4b5-f2eba9c2a026_1656x1248.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1097,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:257361,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://snippetsofcode.substack.com/i/171404180?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3fecf24-c107-4235-a4b5-f2eba9c2a026_1656x1248.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!91z9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3fecf24-c107-4235-a4b5-f2eba9c2a026_1656x1248.png 424w, https://substackcdn.com/image/fetch/$s_!91z9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3fecf24-c107-4235-a4b5-f2eba9c2a026_1656x1248.png 848w, https://substackcdn.com/image/fetch/$s_!91z9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3fecf24-c107-4235-a4b5-f2eba9c2a026_1656x1248.png 1272w, https://substackcdn.com/image/fetch/$s_!91z9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3fecf24-c107-4235-a4b5-f2eba9c2a026_1656x1248.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p><strong>Get the same 25-point evaluation I use with coaching clients - completely free</strong></p><div><hr></div><h2><strong>Stop Fighting Your Rails Codebase</strong></h2><p>You know that sinking feeling when a "simple" Rails feature takes 3 weeks instead of 3 days? When new developers avoid certain parts of your codebase? When bug fixes create more bugs?</p><p><strong>Your Rails application might be working, but it's probably broken in ways that are costing you thousands in lost productivity.</strong></p><h2><strong>The Hidden Cost of "Working" Code</strong></h2><p>Most Rails teams focus on making code work, not making it maintainable. The result:</p><p>&#10060; <strong>Simple features take weeks</strong> instead of days<br>&#10060; <strong>New developers can't contribute</strong> for months<br>&#10060; <strong>"Don't touch it" code</strong> that everyone avoids<br>&#10060; <strong>Bug fixes that create new bugs</strong><br>&#10060; <strong>Deployment anxiety</strong> and weekend emergencies</p><p><strong>This isn't a skill problem - it's an architecture problem.</strong></p><div><hr></div><h2><strong>What You're Getting (Completely Free):</strong></h2><h3><strong>The Same Rails Code Quality Assessment I Use With Enterprise Clients</strong></h3><p>This 25-point diagnostic tool reveals the hidden issues that are silently killing your Rails development velocity.</p><p><strong>Based on transforming Rails applications at:</strong></p><ul><li><p><strong>Checkr</strong> (30% to 70% invoicing accuracy improvement)</p></li><li><p><strong>Casper</strong> (57% process automation, 59% cost reduction)</p></li></ul><h3><strong>Three Critical Areas Evaluated:</strong></h3><h4><strong>1. Coupling &amp; Dependencies (40 points)</strong></h4><ul><li><p>Business logic mixed with external services</p></li><li><p>Database queries scattered throughout controllers</p></li><li><p>Impossible-to-test code that hits real APIs</p></li><li><p>"Change one thing, break everything else" syndrome</p></li></ul><h4><strong>2. Method &amp; Class Complexity (35 points)</strong></h4><ul><li><p>Monolithic methods doing multiple unrelated things</p></li><li><p>Controllers containing complex business logic</p></li><li><p>Classes that have grown to hundreds of lines</p></li><li><p>Comments explaining what code does (not why)</p></li></ul><h4><strong>3. Development Velocity Impact (25 points)</strong></h4><ul><li><p>Simple features taking much longer than expected</p></li><li><p>Team members avoiding "working" code</p></li><li><p>New developers taking weeks to contribute</p></li><li><p>Bug fixes introducing new problems</p></li></ul><div><hr></div><h2><strong>Your Assessment Results Include:</strong></h2><h3><strong>Immediate Clarity:</strong></h3><p>&#9989; <strong>Clear score (0-100)</strong> showing your Rails code health<br>&#9989; <strong>Specific problem areas</strong> ranked by impact<br>&#9989; <strong>Priority improvement roadmap</strong> based on your score<br>&#9989; <strong>Quick wins</strong> you can implement this week</p><h3><strong>Detailed Action Plans:</strong></h3><p>&#9989; <strong>Code examples</strong> showing before/after improvements<br>&#9989; <strong>Refactoring strategies</strong> for each problem type<br>&#9989; <strong>Team practices</strong> to prevent quality regression<br>&#9989; <strong>Metrics to track</strong> improvement progress</p><h3><strong>Expert Guidance:</strong></h3><p>&#9989; <strong>Same framework</strong> used with $10k+ coaching clients<br>&#9989; <strong>Real-world examples</strong> from production Rails apps<br>&#9989; <strong>Proven techniques</strong> that deliver measurable ROI</p><div><hr></div><h2><strong>Perfect For Rails Teams That:</strong></h2><p>&#127919; <strong>Struggle with slow feature delivery</strong> despite having skilled developers<br>&#127919; <strong>Have "don't touch it" code</strong> that everyone avoids modifying<br>&#127919; <strong>Spend more time debugging</strong> than building new features<br>&#127919; <strong>Want new developers to contribute</strong> quickly and confidently<br>&#127919; <strong>Need to justify refactoring</strong> to business stakeholders<br>&#127919; <strong>Are frustrated with deployment anxiety</strong> and weekend emergencies</p><div><hr></div><h2><strong>How It Works:</strong></h2><h3><strong>1. Download Instantly (Free)</strong></h3><p>Get the 25-point assessment PDF immediately - no waiting, no sales calls</p><h3><strong>2. Complete Assessment (15-20 minutes)</strong></h3><p>Rate your Rails application across proven quality indicators</p><h3><strong>3. Calculate Your Score</strong></h3><p>Get immediate clarity on your code health and transformation priorities</p><h3><strong>4. Follow Action Plan</strong></h3><p>Implement specific improvements based on your score range</p><h3><strong>5. Track Progress</strong></h3><p>Measure velocity improvements and team confidence gains</p><div><hr></div><h2><strong>Get Your Free Assessment Now</strong></h2><p><strong><a href="https://stan.store/rafaelgeorge/p/rails-code-quality-assessment-scorecard">DOWNLOAD FREE RAILS CODE QUALITY ASSESSMENT</a></strong></p><p><em>Instant PDF download - At no cost</em></p><p><strong>What you'll receive:</strong></p><ul><li><p>Complete 25-point assessment scorecard</p></li><li><p>Detailed scoring guide and interpretation</p></li><li><p>Action plans for each score range</p></li><li><p>Code examples and refactoring strategies</p></li><li><p>Progress tracking framework</p></li></ul><div><hr></div><h2><strong>Frequently Asked Questions:</strong></h2><p><strong>Q: Why is this free?</strong><br>A: I want every Rails team to build maintainable applications. Quality code benefits everyone - developers, businesses, and users.</p><p><strong>Q: How long does it take?</strong><br>A: 5-10 minutes to complete, immediate results and action plan.</p><p><strong>Q: Is this just theory?</strong><br>A: No - every question comes from real patterns I've seen across 50+ Rails applications. The solutions are proven and practical.</p><p><strong>Q: What if I score high (bad)?</strong><br>A: Higher scores mean bigger opportunities for improvement. The assessment includes specific action plans for every score range.</p><p><strong>Q: Can I share this with my team?</strong><br>A: Absolutely! In fact, having multiple team members take it often reveals interesting perspectives on the same codebase.</p><div><hr></div><h2><strong>About Rafael George</strong></h2><p>Staff Software Engineer with 15+ years transforming Rails applications at high-growth companies. Specialized in event-driven architecture and collaborative development practices that align technology with business needs.</p><p><strong>Proven track record:</strong></p><ul><li><p>Reduced onboarding manual work by 17% at Gusto</p></li><li><p>Improved invoicing accuracy from 30% to 70% at Checkr</p></li><li><p>Automated 57% of packaging processes at Casper</p></li><li><p>Transformed Rails architectures at 20+ companies</p></li></ul><div><hr></div><h2><strong>Ready to Discover What's Slowing Your Rails Team Down?</strong></h2><p><strong><a href="https://stan.store/rafaelgeorge/p/rails-code-quality-assessment-scorecard">GET YOUR FREE ASSESSMENT NOW</a></strong></p><p><em>Download instantly - Start improving your Rails code quality today</em></p><div><hr></div><h2><strong>P.S. - Need Expert Help?</strong></h2><p>If your assessment reveals significant code quality challenges (score 60+), I offer <strong>Rails Code Quality Transformation Sessions</strong> where we review your specific codebase and create a targeted improvement plan.</p><p><strong><a href="https://stan.store/rafaelgeorge/p/rails-code-quality-transformation-session">Learn about transformation sessions &#8594;</a></strong></p><p>But start with the free assessment first - it gives us both a clear picture of your situation and improvement priorities.</p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://snippetsofcode.substack.com/p/free-rails-code-quality-assessment?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://snippetsofcode.substack.com/p/free-rails-code-quality-assessment?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Breaking Free from Rails’ Fat Model Prison]]></title><description><![CDATA[A Rails Refactoring Journey]]></description><link>https://snippetsofcode.substack.com/p/breaking-free-from-rails-fat-model</link><guid isPermaLink="false">https://snippetsofcode.substack.com/p/breaking-free-from-rails-fat-model</guid><dc:creator><![CDATA[Rafael George]]></dc:creator><pubDate>Fri, 01 Aug 2025 16:17:32 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1743713063092-76a2c769d1c1?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>You know that moment when you open a Rails model file and have to scroll for what feels like forever to find the method you need?</strong> This was mine &#8211; staring at a 1,200-line User model that had become our application's nightmare.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1743713063092-76a2c769d1c1?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1743713063092-76a2c769d1c1?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 424w, https://images.unsplash.com/photo-1743713063092-76a2c769d1c1?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 848w, https://images.unsplash.com/photo-1743713063092-76a2c769d1c1?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1272w, https://images.unsplash.com/photo-1743713063092-76a2c769d1c1?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1743713063092-76a2c769d1c1?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" width="3000" height="4498" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1743713063092-76a2c769d1c1?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:4498,&quot;width&quot;:3000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Finished meal with fries and a coke bottle.&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Finished meal with fries and a coke bottle." title="Finished meal with fries and a coke bottle." srcset="https://images.unsplash.com/photo-1743713063092-76a2c769d1c1?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 424w, https://images.unsplash.com/photo-1743713063092-76a2c769d1c1?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 848w, https://images.unsplash.com/photo-1743713063092-76a2c769d1c1?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1272w, https://images.unsplash.com/photo-1743713063092-76a2c769d1c1?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this issue, I'll take you through my journey from fat models to clean, maintainable architecture. No theory dumps &#8211; just real code, real problems, and real solutions.</p><p>&#128640; <strong>What you'll learn:</strong></p><ul><li><p>Why "thin controller, fat model" advice destroys codebases</p></li><li><p>How to extract service objects that actually make sense</p></li><li><p>The value object pattern for cleaner data handling</p></li><li><p>Practical steps to refactor without breaking everything</p></li></ul><h2>The Problem</h2><p>Three months into our Rails project, our main User model had become a monster. What started as innocent database relationships had grown into a beast handling everything from authentication to payment processing to email campaigns.</p><p>Here's what we were dealing with:</p><pre><code><code>class User &lt; ActiveRecord::Base
  # 15 different associations
  has_many :orders, :reviews, :subscriptions, :notifications
  belongs_to :company, :billing_address, :shipping_address
  
  # Endless validations
  validates :email, presence: true, uniqueness: true
  validates :password, length: { minimum: 8 }
  validates :phone, format: { with: /\A[\+]?[1-9][\d .\-\(\)]{6,}\z/ }
  
  # Business logic explosion
  def upgrade_to_premium
    transaction do
      update!(subscription_type: 'premium')
      charge_credit_card
      send_welcome_email
      create_analytics_event
      sync_with_crm
      generate_invoice
      notify_sales_team
    end
  end
  
  def calculate_discount_rate
    # 50 lines of complex business logic here
  end
  
  # ... 200+ more methods
end
</code></code></pre><p>&#128161; <strong>Warning Signs:</strong></p><ul><li><p>Tests took 45 seconds to run for a single model</p></li><li><p>Adding new features meant touching 5+ unrelated methods</p></li><li><p>Database fixtures required 12 related objects just to test business logic</p></li><li><p>New developers needed 3 days just to understand one model</p></li></ul><h2>The Journey: From Problem to Solution</h2><h3>Step 1: Extract Service Objects for Complex Operations</h3><p><strong>Before:</strong></p><pre><code><code>class User &lt; ActiveRecord::Base
  def upgrade_to_premium
    transaction do
      update!(subscription_type: 'premium')
      charge_credit_card
      send_welcome_email
      create_analytics_event
      sync_with_crm
      generate_invoice
      notify_sales_team
    end
  end
end
</code></code></pre><p><strong>After:</strong></p><pre><code><code># Clean model with only data concerns
class User &lt; ActiveRecord::Base
  has_many :orders
  belongs_to :company
  
  validates :email, presence: true, uniqueness: true
  validates :password, length: { minimum: 8 }
end

# Dedicated service for upgrade logic
class UserUpgradeService
  def initialize(user)
    @user = user
  end
  
  def execute
    return false unless eligible_for_upgrade?
    
    ActiveRecord::Base.transaction do
      upgrade_subscription
      process_payment
      send_notifications
      track_analytics
    end
    
    true
  end
  
  private
  
  def eligible_for_upgrade?
    @user.subscription_type != 'premium' &amp;&amp; @user.payment_method.present?
  end
  
  def upgrade_subscription
    @user.update!(subscription_type: 'premium', upgraded_at: Time.current)
  end
  
  def process_payment
    PaymentProcessor.charge(@user.payment_method, premium_price)
  end
  
  def send_notifications
    UserMailer.premium_welcome(@user).deliver_later
    NotificationService.new(@user).send_upgrade_confirmation
  end
  
  def track_analytics
    Analytics.track(@user.id, 'subscription_upgraded', { plan: 'premium' })
  end
end
</code></code></pre><p>&#127919; <strong>Impact:</strong></p><ul><li><p>Test suite ran 60% faster (18 seconds vs 45 seconds)</p></li><li><p>Service objects could be tested without database setup</p></li><li><p>Business logic became reusable across different contexts</p></li></ul><h3>Step 2: Introduce Value Objects for Complex Data</h3><p><strong>Before:</strong></p><pre><code><code>class User &lt; ActiveRecord::Base
  def formatted_phone
    # 20 lines of phone formatting logic
  end
  
  def valid_phone?
    # Complex validation logic
  end
end
</code></code></pre><p><strong>After:</strong></p><pre><code><code>class PhoneNumber
  attr_reader :value
  
  def initialize(raw_phone)
    @value = normalize(raw_phone)
    validate!
  end
  
  def formatted
    case country_code
    when '1' then us_format
    when '44' then uk_format
    else international_format
    end
  end
  
  def valid?
    @value.match?(/^\+\d{10,15}$/)
  end
  
  private
  
  def normalize(raw_phone)
    raw_phone.to_s.gsub(/[^\d+]/, '')
  end
  
  def validate!
    raise ArgumentError, "Invalid phone number" unless valid?
  end
end

# In the User model
class User &lt; ActiveRecord::Base
  def phone_number
    @phone_number ||= PhoneNumber.new(phone) if phone.present?
  end
end
</code></code></pre><h2>The Aha Moment</h2><p>The breakthrough came when I realized we weren't building a database wrapper &#8211; we were building a business application that happened to use a database. <strong>The database structure shouldn't dictate our code organization.</strong></p><h3>Real Numbers From This Experience</h3><ul><li><p>Before: 1,200-line User model</p></li><li><p>After: 180-line User model + 8 focused service objects + 4 value objects</p></li><li><p>Test suite: 45s &#8594; 18s runtime</p></li><li><p>Bug fix time: 2-3 hours &#8594; 30 minutes average</p></li><li><p>New feature development: 40% faster</p></li></ul><h2>The Final Result</h2><pre><code><code># Lean User model focused on data persistence
class User &lt; ActiveRecord::Base
  has_many :orders
  has_many :subscriptions
  belongs_to :company
  
  validates :email, presence: true, uniqueness: true
  validates :password, length: { minimum: 8 }
  
  def phone_number
    @phone_number ||= PhoneNumber.new(phone) if phone.present?
  end
  
  def full_name
    "#{first_name} #{last_name}".strip
  end
end

# Controller stays focused on HTTP concerns
class UsersController &lt; ApplicationController
  def upgrade
    @user = current_user
    service = UserUpgradeService.new(@user)
    
    if service.execute
      redirect_to dashboard_path, notice: "Welcome to Premium!"
    else
      flash.now[:alert] = "Upgrade failed. Please try again."
      render :show
    end
  end
end
</code></code></pre><p>&#127881; <strong>Key Improvements:</strong></p><ul><li><p>Models handle only data persistence and simple queries</p></li><li><p>Service objects contain isolated business logic</p></li><li><p>Value objects encapsulate complex data types</p></li><li><p>Controllers focus on HTTP request/response flow</p></li></ul><h2>Monday Morning Action Items</h2><ol><li><p><strong>Quick Wins (5-Minute Changes)</strong></p><ul><li><p>Identify your fattest model (hint: it's probably User)</p></li><li><p>Count the public methods &#8211; if it's over 20, you have work to do</p></li><li><p>Look for methods with more than 5 lines that aren't simple queries</p></li></ul></li><li><p><strong>Next Steps</strong></p><ul><li><p>Extract one complex method into a service object this week</p></li><li><p>Create a value object for any data with formatting or validation logic</p></li><li><p>Write tests for your new objects without touching the database</p></li></ul></li></ol><h2>Your Turn!</h2><h3>The Fat Model Challenge</h3><p>Here's a typical fat model. How would you refactor it?</p><pre><code><code>class Order &lt; ActiveRecord::Base
  belongs_to :user
  has_many :line_items
  
  def process_payment
    return false unless valid_for_payment?
    
    transaction do
      charge_result = CreditCardProcessor.charge(
        user.credit_card, 
        total_amount
      )
      
      if charge_result.success?
        update!(status: 'paid', paid_at: Time.current)
        generate_receipt
        send_confirmation_email
        update_inventory
        create_shipment
        track_conversion
        true
      else
        errors.add(:payment, charge_result.error)
        false
      end
    end
  end
  
  def generate_receipt
    # 30 lines of PDF generation
  end
  
  def send_confirmation_email
    # Email logic
  end
  
  def update_inventory
    # Inventory management
  end
end
</code></code></pre><p>&#128172; <strong>Discussion Prompts:</strong></p><ul><li><p>What service objects would you extract from this Order model?</p></li><li><p>Which parts belong in the model vs external services?</p></li><li><p>How would you test the payment processing logic?</p></li></ul><div><hr></div><p>&#128295; <strong>Useful Resources:</strong></p><ul><li><p><a href="https://thoughtbot.com/blog/sandi-metz-rules-for-developers">Sandi Metz's Rules for Developers</a></p></li><li><p><a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html">Clean Architecture by Uncle Bob</a></p></li><li><p><a href="https://blog.appsignal.com/2020/06/17/using-service-objects-in-ruby-on-rails.html">Service Objects in Rails</a></p></li></ul><div><hr></div><p>Found this useful? Share it with a fellow Rails developer! And don't forget to reply with your own fat model horror stories &#8211; I read every response.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://snippetsofcode.substack.com/p/breaking-free-from-rails-fat-model?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://snippetsofcode.substack.com/p/breaking-free-from-rails-fat-model?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><p>Happy coding!</p><div><hr></div><p><strong>Tips and Notes:</strong></p><ul><li><p>Note: All code examples are simplified for clarity &#8211; real implementations should include proper error handling</p></li><li><p>Pro Tip: Start with service objects before jumping to repositories &#8211; you can always add more abstraction later</p></li><li><p>Remember: The goal isn't perfect architecture, it's maintainable code that your team can work with confidently</p></li></ul><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://snippetsofcode.substack.com/p/breaking-free-from-rails-fat-model/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://snippetsofcode.substack.com/p/breaking-free-from-rails-fat-model/comments"><span>Leave a comment</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[The Day I Discovered Why My Code Was Talking to Strangers]]></title><description><![CDATA[You know that moment when you realize your business logic is having intimate conversations with your database?]]></description><link>https://snippetsofcode.substack.com/p/the-day-i-discovered-why-my-code</link><guid isPermaLink="false">https://snippetsofcode.substack.com/p/the-day-i-discovered-why-my-code</guid><dc:creator><![CDATA[Rafael George]]></dc:creator><pubDate>Thu, 24 Jul 2025 20:59:50 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1564517945244-d371c925640b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You know that moment when you realize your business logic is having intimate conversations with your database? Mine happened during a code review when I watched a teammate&#8217;s face contort as they tried to follow a payment processing flow that somehow knew about PostgreSQL column names.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1564517945244-d371c925640b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1564517945244-d371c925640b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 424w, https://images.unsplash.com/photo-1564517945244-d371c925640b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 848w, https://images.unsplash.com/photo-1564517945244-d371c925640b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1272w, https://images.unsplash.com/photo-1564517945244-d371c925640b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1564517945244-d371c925640b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" width="3000" height="2000" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1564517945244-d371c925640b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2000,&quot;width&quot;:3000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;two white power adapters on white background&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="two white power adapters on white background" title="two white power adapters on white background" srcset="https://images.unsplash.com/photo-1564517945244-d371c925640b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 424w, https://images.unsplash.com/photo-1564517945244-d371c925640b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 848w, https://images.unsplash.com/photo-1564517945244-d371c925640b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1272w, https://images.unsplash.com/photo-1564517945244-d371c925640b?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this issue, I&#8217;ll take you through how adapters transformed our messy, tightly-coupled codebase into something maintainable. No theory dumps &#8211; just real code, real problems, and real solutions.</p><p>&#128640; <strong>What you&#8217;ll learn:</strong></p><ul><li><p>How to isolate external dependencies from your core business logic</p></li><li><p>The adapter pattern that saved us from integration hell</p></li><li><p>Why your domain shouldn&#8217;t know about databases or APIs</p></li><li><p>Practical techniques for building bulletproof boundaries</p></li></ul><h2>The Problem</h2><p>Our payment processing system had grown organically over two years. <strong>What started as a simple feature had become a tangled mess where business rules, database queries, and API calls were all best friends living in the same class.</strong></p><p>Here&#8217;s what we were dealing with:</p><pre><code><code>class PaymentProcessor
  def process_payment(user_id, amount, card_details)
    # Database query mixed with business logic
    user = User.find(user_id)
    return false if user.account_balance &lt; amount
    
    # Direct API call embedded in the flow
    response = HTTParty.post("https://payment-gateway.com/charge", {
      card_number: card_details[:number],
      amount: amount,
      user_email: user.email
    })
    
    # More database operations
    if response.code == 200
      Transaction.create!(
        user_id: user_id,
        amount: amount,
        status: 'completed',
        gateway_response: response.body
      )
      user.update!(account_balance: user.account_balance - amount)
      true
    else
      false
    end
  end
end
</code></code></pre><p>&#128161; <strong>Warning Signs:</strong></p><ul><li><p>Business logic scattered across database concerns</p></li><li><p>Direct HTTP calls embedded in core workflows</p></li><li><p>Impossible to test without hitting real external services</p></li><li><p>Changes to payment gateway required touching business logic</p></li></ul><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://snippetsofcode.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://snippetsofcode.substack.com/subscribe?"><span>Subscribe now</span></a></p><h2>The Journey: From Problem to Solution</h2><h3>Step 1: Extract the Business Logic</h3><p>The first breakthrough was realizing our domain logic was drowning in infrastructure concerns. We needed to separate what the business cared about from how we stored or retrieved data.</p><p><strong>Before:</strong></p><pre><code><code>def process_payment(user_id, amount, card_details)
  user = User.find(user_id) # Database concern
  return false if user.account_balance &lt; amount # Business rule buried
  # ... rest mixed together
end
</code></code></pre><p><strong>After:</strong></p><pre><code><code>module Payments
  class ProcessPayment
    def self.call(request)
      new.call(request)
    end
    
    Request = Struct.new(:user_id, :amount, :card_details, keyword_init: true)
    Response = Struct.new(:success, :transaction_id, :errors, keyword_init: true)
    
    def call(request)
      # Pure business logic - no database or API knowledge
      return insufficient_funds_response unless user_has_funds?(request)
      
      charge_result = charge_card(request)
      return failed_charge_response(charge_result) unless charge_result.success
      
      transaction = record_transaction(request, charge_result)
      deduct_balance(request)
      
      Response.new(success: true, transaction_id: transaction.id)
    end
    
    private
    # Business logic methods here...
  end
end
</code></code></pre><p>&#127919; <strong>Impact:</strong></p><ul><li><p>Business rules became crystal clear and testable</p></li><li><p>Zero external dependencies in core logic</p></li><li><p>10x faster test suite (no more database hits)</p></li></ul><h3>Step 2: Build the Translation Layer</h3><p>Now we had clean business logic, but it needed to talk to the outside world. This is where adapters became our heroes &#8211; they translate between our pristine domain and the messy real world.</p><p><strong>The Adapter Pattern:</strong></p><pre><code><code>module Payments
  module Adapters
    class PaymentGatewayAdapter
      def initialize(gateway_service: PaymentGateway::Service.new)
        @gateway_service = gateway_service
      end
      
      Request = Struct.new(:amount, :card_details, :user_email, keyword_init: true)
      Response = Struct.new(:success, :transaction_id, :error_message, keyword_init: true)
      
      def charge_card(request)
        result = gateway_service.charge(
          amount: request.amount,
          card_number: request.card_details[:number],
          email: request.user_email
        )
        
        # Transform external response to domain language
        Response.new(
          success: result.status == 'approved',
          transaction_id: result.id,
          error_message: result.error
        )
      rescue StandardError =&gt; e
        Response.new(success: false, error_message: "Gateway unavailable")
      end
      
      private
      attr_reader :gateway_service
    end
  end
end
</code></code></pre><h2>The Aha Moment</h2><p>The breakthrough came when I realized adapters aren&#8217;t just about dependency injection &#8211; they&#8217;re translators. They speak &#8220;domain language&#8221; on one side and &#8220;external system language&#8221; on the other. Our business logic could finally focus on business rules while adapters handled the messy details of talking to databases, APIs, and external services.</p><h3>Real Numbers From This Experience</h3><ul><li><p>Before: 45-second test suite (hitting database and external APIs)</p></li><li><p>After: 3-second test suite (pure unit tests)</p></li><li><p>Bug reports decreased by 60% (cleaner separation of concerns)</p></li><li><p>Feature delivery time improved by 40% (easier to reason about and modify)</p></li></ul><h2>The Final Result</h2><pre><code><code>module Payments
  class ProcessPayment
    def initialize(
      user_repository: Adapters::UserRepository.new,
      payment_gateway: Adapters::PaymentGatewayAdapter.new,
      transaction_repository: Adapters::TransactionRepository.new
    )
      @user_repository = user_repository
      @payment_gateway = payment_gateway
      @transaction_repository = transaction_repository
    end
    
    def call(request)
      user = user_repository.find(request.user_id)
      return insufficient_funds_response unless user.balance &gt;= request.amount
      
      charge_request = payment_gateway_request(request, user)
      charge_result = payment_gateway.charge_card(charge_request)
      return failed_charge_response unless charge_result.success
      
      transaction = transaction_repository.create(
        user_id: user.id,
        amount: request.amount,
        gateway_transaction_id: charge_result.transaction_id
      )
      
      user_repository.deduct_balance(user.id, request.amount)
      
      Response.new(success: true, transaction_id: transaction.id)
    end
    
    private
    attr_reader :user_repository, :payment_gateway, :transaction_repository
  end
end
</code></code></pre><p>&#127881; <strong>Key Improvements:</strong></p><ul><li><p>Business logic completely isolated from external concerns</p></li><li><p>Every external interaction goes through a dedicated adapter</p></li><li><p>Testable without any external dependencies</p></li><li><p>Easy to swap implementations (different payment gateways, databases, etc.)</p></li></ul><h2>Monday Morning Action Items</h2><ol><li><p><strong>Quick Wins (5-Minute Changes)</strong></p><ul><li><p>Identify one class that talks directly to external services</p></li><li><p>Extract the external call into a separate method</p></li><li><p>Add a simple wrapper around that external dependency</p></li></ul></li><li><p><strong>Next Steps</strong></p><ul><li><p>Create your first adapter for the most problematic external integration</p></li><li><p>Define clear request/response objects for your adapter interface</p></li><li><p>Write tests for your business logic without any external dependencies</p></li></ul></li></ol><h2>Your Turn!</h2><h3>The Adapter Challenge</h3><p>Look at this code and identify where adapters would help:</p><pre><code><code>class OrderFulfillment
  def fulfill_order(order_id)
    order = Order.find(order_id)
    return false if order.status != 'pending'
    
    # Email service call
    Mailer.deliver_now(OrderConfirmation.new(order.user.email, order))
    
    # Inventory update
    HTTParty.put("https://inventory.com/api/reserve", {
      items: order.line_items.map(&amp;:product_id),
      quantities: order.line_items.map(&amp;:quantity)
    })
    
    # Payment processing
    Stripe.charge(
      amount: order.total,
      customer: order.user.stripe_customer_id
    )
    
    order.update!(status: 'fulfilled')
  end
end
</code></code></pre><p>&#128172; <strong>Discussion Prompts:</strong></p><ul><li><p>Which external dependencies would you extract first?</p></li><li><p>How would you structure the adapter interfaces?</p></li><li><p>What would the business logic look like after refactoring?</p></li></ul><h2>What&#8217;s Next?</h2><p>Next week: &#8220;Repository Pattern: When Your Database Becomes a Detail&#8221; - We&#8217;ll dive deep into how to make your data persistence strategy completely swappable.</p><div><hr></div><p>&#128295; <strong>Useful Resources:</strong></p><ul><li><p><a href="https://www.amazon.com/Clean-Architecture-Craftsmans-Software-Structure/dp/0134494164">Clean Architecture</a> by Robert Martin</p></li><li><p><a href="https://alistair.cockburn.us/hexagonal-architecture">Hexagonal Architecture (Ports and Adapters)</a> by Alistair Cockburn</p></li><li><p><a href="https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215">Domain-Driven Design</a> by Eric Evans</p></li></ul><div><hr></div><p>Found this useful? Share it with a fellow developer! And don&#8217;t forget to try refactoring one class in your current project using the adapter pattern.</p><p>Happy coding!</p><div><hr></div><ul><li><p>Pro Tip: Start with the external dependency that causes the most testing pain &#8211; usually databases or HTTP calls</p></li><li><p>Remember: Adapters should be thin translation layers, not business logic containers</p></li></ul><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://snippetsofcode.substack.com/p/the-day-i-discovered-why-my-code?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://snippetsofcode.substack.com/p/the-day-i-discovered-why-my-code?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Clean Architecture in Ruby]]></title><description><![CDATA[How Use Case Objects Transformed My Messy Controllers]]></description><link>https://snippetsofcode.substack.com/p/clean-architecture-in-ruby</link><guid isPermaLink="false">https://snippetsofcode.substack.com/p/clean-architecture-in-ruby</guid><dc:creator><![CDATA[Rafael George]]></dc:creator><pubDate>Wed, 16 Jul 2025 21:50:02 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1537798156112-70559b0b15c0?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1537798156112-70559b0b15c0?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1537798156112-70559b0b15c0?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 424w, https://images.unsplash.com/photo-1537798156112-70559b0b15c0?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 848w, https://images.unsplash.com/photo-1537798156112-70559b0b15c0?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1272w, https://images.unsplash.com/photo-1537798156112-70559b0b15c0?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1537798156112-70559b0b15c0?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" width="3000" height="3751" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1537798156112-70559b0b15c0?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:3751,&quot;width&quot;:3000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;right angle-shaped gray building&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="right angle-shaped gray building" title="right angle-shaped gray building" srcset="https://images.unsplash.com/photo-1537798156112-70559b0b15c0?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 424w, https://images.unsplash.com/photo-1537798156112-70559b0b15c0?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 848w, https://images.unsplash.com/photo-1537798156112-70559b0b15c0?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1272w, https://images.unsplash.com/photo-1537798156112-70559b0b15c0?fm=jpg&amp;q=60&amp;w=3000&amp;ixlib=rb-4.1.0&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p> Ever stared at a Rails controller that's grown into a 200-line monster, handling everything from authentication to email sending to complex business logic? That was me last month, debugging a user signup flow that had become an unmaintainable nightmare.</p><p>You know that moment when you realize your "quick fix" has turned into spaghetti code that nobody wants to touch? This was mine.</p><p>In this issue, I'll take you through how Use Case Objects completely transformed my approach to organizing business logic in Ruby applications. No theory dumps &#8211; just real code, real problems, and real solutions.</p><p>&#128640; <strong>What you'll learn:</strong></p><ul><li><p>How to isolate business logic from Rails controllers using Use Case Objects</p></li><li><p>The four essential rules for designing maintainable use cases</p></li><li><p>How to structure inputs and outputs with value objects</p></li><li><p>How Use Case Objects fit into Hexagonal Architecture</p></li></ul><h2>The Problem</h2><p>Picture this: I'm working on a user onboarding system where the signup process involves user validation, email verification, welcome email sending, analytics tracking, and trial account setup. All of this logic was crammed into a single controller action.</p><p>Here's what we were dealing with:</p><pre><code><code>class UsersController &lt; ApplicationController
  def create
    @user = User.new(user_params)
    
    if @user.valid?
      if @user.save
        UserMailer.welcome_email(@user).deliver_now
        Analytics.track('user_signup', user_id: @user.id)
        
        if params[:trial]
          trial = Trial.create(user: @user, expires_at: 30.days.from_now)
          TrialMailer.trial_started(@user, trial).deliver_now
        end
        
        redirect_to dashboard_path, notice: 'Welcome!'
      else
        render :new
      end
    else
      render :new
    end
  end
  
  private
  
  def user_params
    params.require(:user).permit(:username, :email, :password)
  end
end
</code></code></pre><p>&#128161; <strong>Warning Signs:</strong></p><ul><li><p>Multiple responsibilities mixed together (validation, persistence, email, analytics)</p></li><li><p>Difficult to test individual business rules</p></li><li><p>Hard to reuse signup logic from other contexts (API, admin interface)</p></li><li><p>Complex conditional logic that's prone to bugs</p></li></ul><h2>The Journey: From Problem to Solution</h2><h3>Step 1: Extract Business Logic into Use Case Objects</h3><p><strong>Before:</strong></p><pre><code><code>class UsersController &lt; ApplicationController
  def create
    @user = User.new(user_params)
    
    if @user.valid?
      if @user.save
        UserMailer.welcome_email(@user).deliver_now
        Analytics.track('user_signup', user_id: @user.id)
        # ... more logic
      end
    end
  end
end
</code></code></pre><p><strong>After:</strong></p><pre><code><code>module Onboarding
  class SignUp
    # Input as value object
    Credentials = Struct.new(:username, :email, :password, :trial_requested, keyword_init: true)
    
    def self.of(attrs)
      Credentials.new(
        username: attrs.fetch(:username),
        email: attrs.fetch(:email),
        password: attrs.fetch(:password),
        trial_requested: attrs.fetch(:trial_requested, false)
      )
    end

    # Output as value object
    SignedUp = Struct.new(:user, :trial, :success, keyword_init: true)

    def self.call(params:)
      credentials = of(params)
      
      user = User.new(
        username: credentials.username,
        email: credentials.email,
        password: credentials.password
      )
      
      return SignedUp.new(user: user, success: false) unless user.valid?
      
      user.save!
      
      # Handle business logic
      UserMailer.welcome_email(user).deliver_now
      Analytics.track('user_signup', user_id: user.id)
      
      trial = nil
      if credentials.trial_requested
        trial = Trial.create(user: user, expires_at: 30.days.from_now)
        TrialMailer.trial_started(user, trial).deliver_now
      end
      
      SignedUp.new(user: user, trial: trial, success: true)
    end
  end
end

class UsersController &lt; ApplicationController
  def create
    result = Onboarding::SignUp.call(params: signup_params)
    
    if result.success
      redirect_to dashboard_path, notice: 'Welcome!'
    else
      @user = result.user
      render :new
    end
  end
  
  private
  
  def signup_params
    params.require(:user).permit(:username, :email, :password).merge(
      trial_requested: params[:trial].present?
    )
  end
end
</code></code></pre><p>&#127919; <strong>Impact:</strong></p><ul><li><p>Business logic is now isolated and testable</p></li><li><p>Controller reduced from 25 lines to 12 lines</p></li><li><p>Signup logic can be reused across different interfaces</p></li></ul><h3>Step 2: Apply the Four Essential Rules</h3><p>Following the rules for Use Case Objects, I refined the implementation:</p><ol><li><p><strong>Naming</strong>: Used <code>SignUp</code> (verb) to represent the action</p></li><li><p><strong>Public Interface</strong>: Exposed only the <code>.call</code> method</p></li><li><p><strong>Input/Output Structure</strong>: Created value objects for both inputs and outputs</p></li><li><p><strong>Business Metrics</strong>: Focused on user experience metrics</p></li></ol><h2>The Aha Moment</h2><p>The breakthrough came when I realized that Use Case Objects act as the application boundary in Hexagonal Architecture. They sit between external actors (controllers, background jobs, APIs) and the system's internal logic (models, services, repositories).</p><p>This means my business rules are no longer scattered across controllers, models, and service objects &#8211; they're centralized in use cases that clearly define what the application does.</p><h3>Real Numbers From This Experience</h3><ul><li><p>Before: 45 lines of controller code handling signup</p></li><li><p>After: 12 lines in controller + 35 lines in well-structured use case</p></li><li><p>Test coverage increased from 60% to 95% for signup flow</p></li></ul><h2>The Final Result</h2><pre><code><code>module Onboarding
  class SignUp
    # Input as value object
    Credentials = Struct.new(:username, :email, :password, :trial_requested, keyword_init: true)
    
    def self.of(attrs)
      Credentials.new(
        username: attrs.fetch(:username),
        email: attrs.fetch(:email),
        password: attrs.fetch(:password),
        trial_requested: attrs.fetch(:trial_requested, false)
      )
    end

    # Output as value object
    SignedUp = Struct.new(:user, :trial, :success, :errors, keyword_init: true)

    def self.call(params:)
      credentials = of(params)
      
      user = User.new(
        username: credentials.username,
        email: credentials.email,
        password: credentials.password
      )
      
      unless user.valid?
        return SignedUp.new(user: user, success: false, errors: user.errors)
      end
      
      user.save!
      
      # Execute business rules
      UserMailer.welcome_email(user).deliver_now
      Analytics.track('user_signup', user_id: user.id)
      
      trial = create_trial_if_requested(user, credentials.trial_requested)
      
      SignedUp.new(user: user, trial: trial, success: true, errors: [])
    end
    
    private
    
    def self.create_trial_if_requested(user, trial_requested)
      return nil unless trial_requested
      
      trial = Trial.create(user: user, expires_at: 30.days.from_now)
      TrialMailer.trial_started(user, trial).deliver_now
      trial
    end
  end
end
</code></code></pre><p>&#127881; <strong>Key Improvements:</strong></p><ul><li><p>Single responsibility: each use case handles one business process</p></li><li><p>Clear input/output contracts using value objects</p></li><li><p>Testable business logic separated from framework concerns</p></li><li><p>Reusable across different interfaces (web, API, background jobs)</p></li></ul><h2>Monday Morning Action Items</h2><ol><li><p><strong>Quick Wins (5-Minute Changes)</strong></p><ul><li><p>Identify your fattest controller action</p></li><li><p>Look for business logic mixed with presentation logic</p></li><li><p>Find repeated business processes across controllers</p></li></ul></li><li><p><strong>Next Steps</strong></p><ul><li><p>Extract one business process into a Use Case Object</p></li><li><p>Create input and output value objects</p></li><li><p>Write comprehensive tests for your use case</p></li></ul></li></ol><h2>Your Turn!</h2><h3>The Use Case Refactoring Challenge</h3><p>Take this messy controller action and refactor it into a proper Use Case Object:</p><pre><code><code>class OrdersController &lt; ApplicationController
  def create
    @order = Order.new(order_params)
    @order.user = current_user
    
    if @order.save
      @order.items.each do |item|
        item.product.decrement!(:stock_quantity, item.quantity)
      end
      
      OrderMailer.confirmation(@order).deliver_now
      PaymentProcessor.charge(@order.total, @order.payment_method)
      
      redirect_to order_path(@order), notice: 'Order placed!'
    else
      render :new
    end
  end
end
</code></code></pre><p>&#128172; <strong>Discussion Prompts:</strong></p><ul><li><p>What value objects would you create for input and output?</p></li><li><p>How would you handle the stock decrement logic?</p></li><li><p>What would you name this Use Case Object?</p></li></ul><h2>What's Next?</h2><p>Next week: "Repository Pattern in Ruby: Abstracting Your Data Layer" - We'll explore how to use the Gateway pattern to separate your business logic from ActiveRecord dependencies.</p><div><hr></div><p>&#128295; <strong>Useful Resources:</strong></p><ul><li><p><a href="https://alistair.cockburn.us/hexagonal-architecture/">Hexagonal Architecture Guide</a></p></li><li><p><a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html">Clean Architecture by Robert Martin</a></p></li><li><p><a href="https://ruby-doc.org/core-2.7.0/Struct.html">Ruby Value Objects</a></p></li></ul><div><hr></div><p>Found this useful? Share it with a fellow developer! And don't forget to try the refactoring challenge above and share your solution.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://snippetsofcode.substack.com/p/clean-architecture-in-ruby?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://snippetsofcode.substack.com/p/clean-architecture-in-ruby?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><p>Happy coding!</p><div><hr></div><p><strong>Tips and Notes:</strong></p><ul><li><p>Pro Tip: Start with your most complex controller action when implementing Use Case Objects</p></li><li><p>Remember: Use Case Objects should have a single public method (<code>.call</code>) and clear input/output contracts</p></li></ul>]]></content:encoded></item><item><title><![CDATA[The Day I Realized My "Working" Code Was Actually Broken]]></title><description><![CDATA[I was staring at a Patient class that calculated health metrics &#8211; BMI, ideal weight, heart rate &#8211; all crammed into one massive method.]]></description><link>https://snippetsofcode.substack.com/p/the-day-i-realized-my-working-code</link><guid isPermaLink="false">https://snippetsofcode.substack.com/p/the-day-i-realized-my-working-code</guid><dc:creator><![CDATA[Rafael George]]></dc:creator><pubDate>Wed, 02 Jul 2025 18:21:20 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!fD1I!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35931a23-4e15-42f1-a2e9-28c8f39326ab_3000x3750.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fD1I!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35931a23-4e15-42f1-a2e9-28c8f39326ab_3000x3750.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fD1I!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35931a23-4e15-42f1-a2e9-28c8f39326ab_3000x3750.jpeg 424w, https://substackcdn.com/image/fetch/$s_!fD1I!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35931a23-4e15-42f1-a2e9-28c8f39326ab_3000x3750.jpeg 848w, https://substackcdn.com/image/fetch/$s_!fD1I!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35931a23-4e15-42f1-a2e9-28c8f39326ab_3000x3750.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!fD1I!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35931a23-4e15-42f1-a2e9-28c8f39326ab_3000x3750.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fD1I!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35931a23-4e15-42f1-a2e9-28c8f39326ab_3000x3750.jpeg" width="1456" height="1820" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/35931a23-4e15-42f1-a2e9-28c8f39326ab_3000x3750.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1820,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;a black and white photo of a bunch of wires&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="a black and white photo of a bunch of wires" title="a black and white photo of a bunch of wires" srcset="https://substackcdn.com/image/fetch/$s_!fD1I!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35931a23-4e15-42f1-a2e9-28c8f39326ab_3000x3750.jpeg 424w, https://substackcdn.com/image/fetch/$s_!fD1I!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35931a23-4e15-42f1-a2e9-28c8f39326ab_3000x3750.jpeg 848w, https://substackcdn.com/image/fetch/$s_!fD1I!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35931a23-4e15-42f1-a2e9-28c8f39326ab_3000x3750.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!fD1I!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35931a23-4e15-42f1-a2e9-28c8f39326ab_3000x3750.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I was staring at a Patient class that calculated health metrics &#8211; BMI, ideal weight, heart rate &#8211; all crammed into one massive method. It worked, sure, but it was a nightmare to test, impossible to extend, and frankly, embarrassing to show to other developers.</p><p>In this issue, I'll take you through the journey from messy, monolithic methods to clean, testable, maintainable code. No theory dumps &#8211; just real code, real problems, and real solutions.</p><p>&#128640; <strong>What you'll learn:</strong></p><ul><li><p>How to identify code smells that are killing your productivity</p></li><li><p>A systematic approach to refactoring without breaking everything</p></li><li><p>Why good code isn't just about making it work &#8211; it's about making it last</p></li><li><p>Practical metrics that actually matter for code quality</p></li></ul><h2>The Problem</h2><p>Here's what we were dealing with &#8211; a Patient class that seemed innocent enough:</p><pre><code><code>class Patient
  def initialize(weight, height, age)
    @weight = weight
    @height = height
    @age = age
  end

  def health_metrics
    # BMI calculation
    bmi = (@weight / (@height ** 2)).round(2)
    
    # Ideal weight calculation
    ideal_weight = (22 * (@height ** 2)).round(2)
    
    # Heart rate calculation based on age
    heart_rate = calculate_heart_rate(@age)

    # Returning the health metrics as a hash
    { bmi: bmi, ideal_weight: ideal_weight, heart_rate: heart_rate }
  end

  private

  def calculate_heart_rate(age)
    70 - (age / 10)
  end
end
</code></code></pre><p>&#128161; <strong>Warning Signs:</strong></p><ul><li><p>One method doing three completely different calculations</p></li><li><p>Impossible to test individual calculations separately</p></li><li><p>Adding new health metrics meant modifying existing code</p></li><li><p>Comments explaining what each section does (usually a red flag)</p></li></ul><h2>The Journey: From Problem to Solution</h2><h3>Step 1: Recognizing the Code Smell</h3><p>The first breakthrough came when I ran <strong>Reek</strong> on this code. The complexity score was off the charts &#8211; well above the danger threshold of 10. The tool immediately flagged the <code>health_metrics</code> method for violating the Single Responsibility Principle.</p><p><strong>Before:</strong></p><pre><code><code>def health_metrics
  # Three different responsibilities mashed together
  bmi = (@weight / (@height ** 2)).round(2)
  ideal_weight = (22 * (@height ** 2)).round(2)
  heart_rate = calculate_heart_rate(@age)
  { bmi: bmi, ideal_weight: ideal_weight, heart_rate: heart_rate }
end
</code></code></pre><p><strong>After:</strong></p><pre><code><code>def health_metrics
  {
    bmi: calculate_bmi,
    ideal_weight: calculate_ideal_weight,
    heart_rate: calculate_heart_rate(@age)
  }
end

private

def calculate_bmi
  (@weight / (@height ** 2)).round(2)
end

def calculate_ideal_weight
  (22 * (@height ** 2)).round(2)
end
</code></code></pre><p>&#127919; <strong>Impact:</strong></p><ul><li><p>Each calculation became independently testable</p></li><li><p>Adding new metrics no longer required modifying existing code</p></li><li><p>The main method became a clear orchestrator rather than a do-everything monster</p></li></ul><h3>Step 2: Following the Open/Closed Principle</h3><p>The real aha moment came when I asked: "Can I add a new health metric without modifying existing code?" The answer was still no. So I kept refactoring.</p><h2>The Aha Moment</h2><p>The breakthrough happened when I realized that good code isn't just about making it work &#8211; it's about making it <em>continue</em> to work as requirements change. The key insight: <strong>as production code becomes more generic, tests should become more specific and explicit</strong>.</p><h3>Real Numbers From This Experience</h3><ul><li><p>Before: 1 test for the entire health_metrics method</p></li><li><p>After: 4 focused tests, each covering one responsibility</p></li><li><p>Test writing time: Reduced from 15 minutes to 3 minutes per new health metric</p></li><li><p>Bug detection: Increased from catching issues in production to catching them at compile time</p></li></ul><h2>The Final Result</h2><pre><code><code>class Patient
  def initialize(weight, height, age)
    @weight = weight
    @height = height
    @age = age
  end

  def health_metrics
    {
      bmi: calculate_bmi,
      ideal_weight: calculate_ideal_weight,
      heart_rate: calculate_heart_rate
    }
  end

  def calculate_bmi
    (@weight / (@height ** 2)).round(2)
  end

  def calculate_ideal_weight
    (22 * (@height ** 2)).round(2)
  end

  def calculate_heart_rate
    (70 - (@age / 10)).round(2)
  end

  private

  attr_reader :weight, :height, :age
end
</code></code></pre><p>&#127881; <strong>Key Improvements:</strong></p><ul><li><p>Each method has a single, clear responsibility</p></li><li><p>Easy to test individual calculations</p></li><li><p>Simple to add new health metrics</p></li><li><p>Code that's both simple and expressive</p></li></ul><h2>Monday Morning Action Items</h2><ol><li><p><strong>Quick Wins (5-Minute Changes)</strong></p><ul><li><p>Run a code smell detection tool (like Reek for Ruby) on your current project</p></li><li><p>Identify one method that's doing more than one thing</p></li><li><p>Extract one responsibility into its own private method</p></li></ul></li><li><p><strong>Next Steps</strong></p><ul><li><p>Apply the "Can I implement this change?" test to your recent code</p></li><li><p>Write focused unit tests for each extracted responsibility</p></li><li><p>Establish a complexity threshold (like score &gt; 10) for your team</p></li></ul></li></ol><h2>Your Turn!</h2><h3>The Refactoring Challenge</h3><p>Here's a method that's crying out for refactoring. Can you spot the code smells?</p><pre><code><code>def process_order(items, customer_type, discount_code)
  total = 0
  items.each { |item| total += item.price * item.quantity }
  
  # Apply customer discount
  if customer_type == 'premium'
    total *= 0.9
  elsif customer_type == 'vip'
    total *= 0.8
  end
  
  # Apply discount code
  if discount_code == 'SAVE10'
    total *= 0.9
  elsif discount_code == 'SAVE20'  
    total *= 0.8
  end
  
  # Calculate tax
  tax = total * 0.08
  final_total = total + tax
  
  # Send confirmation email
  send_email(customer_email, "Order confirmed for $#{final_total}")
  
  final_total
end
</code></code></pre><p>&#128172; <strong>Discussion Prompts:</strong></p><ul><li><p>How many responsibilities can you count in this method?</p></li><li><p>What would happen if you needed to add a new customer type?</p></li><li><p>How would you test the email sending separately from the calculation?</p></li></ul><h2>What's Next?</h2><p>Next week: "The Testing Trap: Why 100% Coverage Might Be Hurting Your Code" - We'll explore the balance between thorough testing and over-testing, and discover metrics that actually predict code quality.</p><div><hr></div><p>&#128295; <strong>Useful Resources:</strong></p><ul><li><p><a href="https://github.com/troessner/reek">Reek gem for Ruby code smell detection</a></p></li><li><p><a href="https://refactoring.com/catalog/">Martin Fowler's Refactoring Catalog</a></p></li><li><p><a href="https://www.digitalocean.com/community/conceptual_articles/s-o-l-i-d-the-first-five-principles-of-object-oriented-design">SOLID Principles explained with examples</a></p></li></ul><div><hr></div><p>Found this useful? Share it with a fellow developer! And don't forget to reply with your refactoring challenge solution &#8211; I read every response.</p><p>Happy coding!</p><div><hr></div><p><strong>Tips and Notes:</strong></p><ul><li><p>Pro Tip: Start refactoring with the smallest, most obvious extraction &#8211; momentum builds confidence</p></li><li><p>Remember: Working code isn't the same as good code &#8211; good code continues to work as requirements evolve</p></li></ul>]]></content:encoded></item><item><title><![CDATA[When PostgreSQL Logical Replication Nearly Broke My Weekend (And What I Built Instead)]]></title><description><![CDATA[You know that sinking feeling when a "simple" database migration turns into a 3-day debugging nightmare?]]></description><link>https://snippetsofcode.substack.com/p/when-postgresql-logical-replication</link><guid isPermaLink="false">https://snippetsofcode.substack.com/p/when-postgresql-logical-replication</guid><dc:creator><![CDATA[Rafael George]]></dc:creator><pubDate>Tue, 17 Jun 2025 16:40:20 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!_KoH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe48aa608-4b18-449a-87d2-eacb298ea0fb_1026x708.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You know that sinking feeling when a "simple" database migration turns into a 3-day debugging nightmare? That was my last weekend.</p><p><strong>What started as a straightforward PostgreSQL logical replication for 81 million records became a masterclass in hidden configuration gotchas and operational blind spots.</strong></p><p>In this issue, I'll take you through why I ditched database-level replication for a Rails-based migration system that actually works. No theory dumps &#8211; just real problems, real solutions, and a migration strategy you can trust.</p><blockquote><p>&#128640; What you'll learn:</p></blockquote><ul><li><p>Why even "successful" database migrations can be operational disasters</p></li><li><p>How to build observable, resumable data migrations with Rails</p></li><li><p>When to choose application-level solutions over database features</p></li><li><p>A battle-tested migration architecture that eliminates guesswork</p></li></ul><h2>The Problem</h2><p>Here's what we were dealing with: migrating two massive join tables (photo_kids and photo_sections) containing 81+ million records. PostgreSQL logical replication seemed like the obvious choice.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_KoH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe48aa608-4b18-449a-87d2-eacb298ea0fb_1026x708.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_KoH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe48aa608-4b18-449a-87d2-eacb298ea0fb_1026x708.png 424w, https://substackcdn.com/image/fetch/$s_!_KoH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe48aa608-4b18-449a-87d2-eacb298ea0fb_1026x708.png 848w, https://substackcdn.com/image/fetch/$s_!_KoH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe48aa608-4b18-449a-87d2-eacb298ea0fb_1026x708.png 1272w, https://substackcdn.com/image/fetch/$s_!_KoH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe48aa608-4b18-449a-87d2-eacb298ea0fb_1026x708.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_KoH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe48aa608-4b18-449a-87d2-eacb298ea0fb_1026x708.png" width="1026" height="708" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e48aa608-4b18-449a-87d2-eacb298ea0fb_1026x708.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:708,&quot;width&quot;:1026,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:124805,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://snippetsofcode.substack.com/i/165196692?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe48aa608-4b18-449a-87d2-eacb298ea0fb_1026x708.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!_KoH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe48aa608-4b18-449a-87d2-eacb298ea0fb_1026x708.png 424w, https://substackcdn.com/image/fetch/$s_!_KoH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe48aa608-4b18-449a-87d2-eacb298ea0fb_1026x708.png 848w, https://substackcdn.com/image/fetch/$s_!_KoH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe48aa608-4b18-449a-87d2-eacb298ea0fb_1026x708.png 1272w, https://substackcdn.com/image/fetch/$s_!_KoH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe48aa608-4b18-449a-87d2-eacb298ea0fb_1026x708.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><blockquote><p>&#128161; Warning Signs:</p></blockquote><ul><li><p>Zero visibility into migration progress during 24+ hour windows</p></li><li><p>Configuration timeouts happening at mysterious user-role levels</p></li><li><p>Multiple days of debugging for what should be infrastructure-level operations</p></li><li><p>No way to resume from failure points without starting over</p></li></ul><h2>The Journey: From Problem to Solution</h2><h3>Step 1: Recognizing the Operational Blindness</h3><blockquote><p>Before:</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0IvQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F53688e48-05a2-4369-8f28-d175d8168f96_1194x558.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0IvQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F53688e48-05a2-4369-8f28-d175d8168f96_1194x558.png 424w, https://substackcdn.com/image/fetch/$s_!0IvQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F53688e48-05a2-4369-8f28-d175d8168f96_1194x558.png 848w, https://substackcdn.com/image/fetch/$s_!0IvQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F53688e48-05a2-4369-8f28-d175d8168f96_1194x558.png 1272w, https://substackcdn.com/image/fetch/$s_!0IvQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F53688e48-05a2-4369-8f28-d175d8168f96_1194x558.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0IvQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F53688e48-05a2-4369-8f28-d175d8168f96_1194x558.png" width="1194" height="558" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/53688e48-05a2-4369-8f28-d175d8168f96_1194x558.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:558,&quot;width&quot;:1194,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:98351,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://snippetsofcode.substack.com/i/165196692?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F53688e48-05a2-4369-8f28-d175d8168f96_1194x558.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0IvQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F53688e48-05a2-4369-8f28-d175d8168f96_1194x558.png 424w, https://substackcdn.com/image/fetch/$s_!0IvQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F53688e48-05a2-4369-8f28-d175d8168f96_1194x558.png 848w, https://substackcdn.com/image/fetch/$s_!0IvQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F53688e48-05a2-4369-8f28-d175d8168f96_1194x558.png 1272w, https://substackcdn.com/image/fetch/$s_!0IvQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F53688e48-05a2-4369-8f28-d175d8168f96_1194x558.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p>After:</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iNcs!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F962e8950-8b5d-451d-9e5c-504d48e9e4f2_1566x1526.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iNcs!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F962e8950-8b5d-451d-9e5c-504d48e9e4f2_1566x1526.png 424w, https://substackcdn.com/image/fetch/$s_!iNcs!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F962e8950-8b5d-451d-9e5c-504d48e9e4f2_1566x1526.png 848w, https://substackcdn.com/image/fetch/$s_!iNcs!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F962e8950-8b5d-451d-9e5c-504d48e9e4f2_1566x1526.png 1272w, https://substackcdn.com/image/fetch/$s_!iNcs!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F962e8950-8b5d-451d-9e5c-504d48e9e4f2_1566x1526.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!iNcs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F962e8950-8b5d-451d-9e5c-504d48e9e4f2_1566x1526.png" width="1456" height="1419" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/962e8950-8b5d-451d-9e5c-504d48e9e4f2_1566x1526.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1419,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:253625,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://snippetsofcode.substack.com/i/165196692?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F962e8950-8b5d-451d-9e5c-504d48e9e4f2_1566x1526.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!iNcs!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F962e8950-8b5d-451d-9e5c-504d48e9e4f2_1566x1526.png 424w, https://substackcdn.com/image/fetch/$s_!iNcs!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F962e8950-8b5d-451d-9e5c-504d48e9e4f2_1566x1526.png 848w, https://substackcdn.com/image/fetch/$s_!iNcs!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F962e8950-8b5d-451d-9e5c-504d48e9e4f2_1566x1526.png 1272w, https://substackcdn.com/image/fetch/$s_!iNcs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F962e8950-8b5d-451d-9e5c-504d48e9e4f2_1566x1526.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p>&#127919; Impact:</p></blockquote><ul><li><p>Real-time progress tracking instead of 24-hour blackouts</p></li><li><p>Clear batch-level success/failure indicators</p></li><li><p>Predictable timeline estimates based on processing rates</p></li></ul><h3>Step 2: Building Resilience Into Every Layer</h3><p>The breakthrough came when I realized that reliability trumps raw performance for large migrations:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GT5X!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49a01ee5-c6dc-4b00-99f9-9311f0cdb30b_1312x2272.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GT5X!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49a01ee5-c6dc-4b00-99f9-9311f0cdb30b_1312x2272.png 424w, https://substackcdn.com/image/fetch/$s_!GT5X!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49a01ee5-c6dc-4b00-99f9-9311f0cdb30b_1312x2272.png 848w, https://substackcdn.com/image/fetch/$s_!GT5X!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49a01ee5-c6dc-4b00-99f9-9311f0cdb30b_1312x2272.png 1272w, https://substackcdn.com/image/fetch/$s_!GT5X!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49a01ee5-c6dc-4b00-99f9-9311f0cdb30b_1312x2272.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GT5X!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49a01ee5-c6dc-4b00-99f9-9311f0cdb30b_1312x2272.png" width="1312" height="2272" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/49a01ee5-c6dc-4b00-99f9-9311f0cdb30b_1312x2272.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2272,&quot;width&quot;:1312,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:319572,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://snippetsofcode.substack.com/i/165196692?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49a01ee5-c6dc-4b00-99f9-9311f0cdb30b_1312x2272.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!GT5X!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49a01ee5-c6dc-4b00-99f9-9311f0cdb30b_1312x2272.png 424w, https://substackcdn.com/image/fetch/$s_!GT5X!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49a01ee5-c6dc-4b00-99f9-9311f0cdb30b_1312x2272.png 848w, https://substackcdn.com/image/fetch/$s_!GT5X!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49a01ee5-c6dc-4b00-99f9-9311f0cdb30b_1312x2272.png 1272w, https://substackcdn.com/image/fetch/$s_!GT5X!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49a01ee5-c6dc-4b00-99f9-9311f0cdb30b_1312x2272.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>The Aha Moment</h2><p>The key realization hit me during hour 18 of staring at a stuck PostgreSQL process: <strong>observability is more valuable than theoretical performance.</strong></p><p>When you can't see what's happening, you can't make informed decisions. When you can't resume from failures, you're always one configuration edge case away from starting over.</p><h3>Real Numbers From This Experience</h3><ul><li><p>Before: 24+ hours with zero progress visibility</p></li><li><p>After: Real-time batch completion with &lt;15 minute recovery windows</p></li><li><p>Debugging time: 3 days &#8594; 0 days (failures are self-explanatory)</p></li><li><p>Knowledge dependency: Deep PostgreSQL expertise &#8594; Standard Rails patterns</p></li></ul><h2>The Final Result</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hG7-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F092731cf-7882-45a7-8485-6218fd995274_1768x2644.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hG7-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F092731cf-7882-45a7-8485-6218fd995274_1768x2644.png 424w, https://substackcdn.com/image/fetch/$s_!hG7-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F092731cf-7882-45a7-8485-6218fd995274_1768x2644.png 848w, https://substackcdn.com/image/fetch/$s_!hG7-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F092731cf-7882-45a7-8485-6218fd995274_1768x2644.png 1272w, https://substackcdn.com/image/fetch/$s_!hG7-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F092731cf-7882-45a7-8485-6218fd995274_1768x2644.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hG7-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F092731cf-7882-45a7-8485-6218fd995274_1768x2644.png" width="1456" height="2177" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/092731cf-7882-45a7-8485-6218fd995274_1768x2644.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2177,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:469090,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://snippetsofcode.substack.com/i/165196692?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F092731cf-7882-45a7-8485-6218fd995274_1768x2644.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!hG7-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F092731cf-7882-45a7-8485-6218fd995274_1768x2644.png 424w, https://substackcdn.com/image/fetch/$s_!hG7-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F092731cf-7882-45a7-8485-6218fd995274_1768x2644.png 848w, https://substackcdn.com/image/fetch/$s_!hG7-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F092731cf-7882-45a7-8485-6218fd995274_1768x2644.png 1272w, https://substackcdn.com/image/fetch/$s_!hG7-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F092731cf-7882-45a7-8485-6218fd995274_1768x2644.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p>&#127881; Key Improvements:</p></blockquote><ul><li><p>100% migration visibility with real-time progress tracking</p></li><li><p>Automatic resume from any failure point</p></li><li><p>Built-in data integrity validation</p></li><li><p>No specialized PostgreSQL expertise required for troubleshooting</p></li></ul><h2>Tomorrow Morning Action Items</h2><p><strong>1. Quick Wins (5-Minute Changes)</strong></p><ul><li><p>Add progress logging to any existing batch jobs</p></li><li><p>Create a simple MigrationStatus model for tracking operations</p></li><li><p>Document your current migration procedures (you'll thank yourself later)</p></li></ul><p><strong>2. Next Steps</strong></p><ul><li><p>Build a small proof-of-concept with 1000 records</p></li><li><p>Test failure scenarios and recovery mechanisms</p></li><li><p>Integrate with your existing monitoring infrastructure</p></li></ul><h2>Your Turn!</h2><h3>The Migration Strategy Challenge</h3><p>Here's a simplified version of the problem. How would you improve this basic migration approach?</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LEOE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99bf7f53-d04e-4c32-8771-4622e300afcf_890x1228.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LEOE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99bf7f53-d04e-4c32-8771-4622e300afcf_890x1228.png 424w, https://substackcdn.com/image/fetch/$s_!LEOE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99bf7f53-d04e-4c32-8771-4622e300afcf_890x1228.png 848w, https://substackcdn.com/image/fetch/$s_!LEOE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99bf7f53-d04e-4c32-8771-4622e300afcf_890x1228.png 1272w, https://substackcdn.com/image/fetch/$s_!LEOE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99bf7f53-d04e-4c32-8771-4622e300afcf_890x1228.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LEOE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99bf7f53-d04e-4c32-8771-4622e300afcf_890x1228.png" width="890" height="1228" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/99bf7f53-d04e-4c32-8771-4622e300afcf_890x1228.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1228,&quot;width&quot;:890,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:135903,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://snippetsofcode.substack.com/i/165196692?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99bf7f53-d04e-4c32-8771-4622e300afcf_890x1228.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!LEOE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99bf7f53-d04e-4c32-8771-4622e300afcf_890x1228.png 424w, https://substackcdn.com/image/fetch/$s_!LEOE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99bf7f53-d04e-4c32-8771-4622e300afcf_890x1228.png 848w, https://substackcdn.com/image/fetch/$s_!LEOE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99bf7f53-d04e-4c32-8771-4622e300afcf_890x1228.png 1272w, https://substackcdn.com/image/fetch/$s_!LEOE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99bf7f53-d04e-4c32-8771-4622e300afcf_890x1228.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p>&#128172; Discussion Prompts:</p></blockquote><ul><li><p>What happens if this fails halfway through 1 million records?</p></li><li><p>How would you add progress tracking without changing the core logic?</p></li><li><p>What's your strategy for validating the migration completed successfully?</p></li></ul><div><hr></div><blockquote><p>&#128295; Useful Resources:</p></blockquote><ul><li><p><a href="https://github.com/sidekiq/sidekiq/wiki/Best-Practices">Sidekiq Best Practices Guide</a></p></li><li><p><a href="https://github.com/zdennis/activerecord-import">ActiveRecord Import Gem</a>- for bulk operations</p></li><li><p><a href="https://www.postgresql.org/docs/current/logical-replication.html">PostgreSQL Logical Replication Docs</a></p></li></ul><div><hr></div><p>Found this useful? Share it with a fellow developer who's ever been burned by a "simple" database migration! And don't forget to reply with your own migration war stories.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://snippetsofcode.substack.com/p/when-postgresql-logical-replication?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://snippetsofcode.substack.com/p/when-postgresql-logical-replication?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><p>Happy coding!</p><div><hr></div><p><strong>Tips and Notes:</strong></p><ul><li><p>Note: All code examples focus on the patterns rather than production-ready implementations</p></li><li><p>Pro Tip: Always test your migration rollback strategy before you need it</p></li><li><p>Remember: Visibility and reliability beat raw performance for critical operations</p></li></ul>]]></content:encoded></item><item><title><![CDATA[Turbocharging Rails]]></title><description><![CDATA[How We Cut Response Times by 78% in Our Data-Heavy App]]></description><link>https://snippetsofcode.substack.com/p/turbocharging-rails</link><guid isPermaLink="false">https://snippetsofcode.substack.com/p/turbocharging-rails</guid><dc:creator><![CDATA[Rafael George]]></dc:creator><pubDate>Tue, 06 May 2025 12:27:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!rAhd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8665b99-f6a8-461f-8753-bfd6fe3665f9_1678x866.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>I was staring at New Relic, watching our response times climb higher as user load increased.</strong> The app was crawling, customers were complaining, and my team was scrambling. We had built a beautiful Rails application that worked flawlessly in development, but crumbled under real-world data volumes.</p><p>You know that moment when you realize your perfectly architected application isn't quite so perfect after all? This was mine.</p><p><strong>In this issue, I'll take you through the exact steps we took to transform our struggling Rails app into a high-performance data processing machine.</strong> No theory dumps &#8211; just real code, real problems, and real solutions.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://snippetsofcode.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Snippets of Code! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><p><strong>&#128640; What you'll learn:</strong></p><ul><li><p>How to identify and optimize database bottlenecks with PostgreSQL</p></li><li><p>Implementing strategic caching to reduce server load</p></li><li><p>Moving processing-intensive tasks to background jobs</p></li></ul><h2>The Problem</h2><p>Our marketplace application had grown from handling hundreds of transactions a day to thousands per hour. What started as occasional slowdowns became consistent performance degradation across the platform.</p><p>Here's what we were dealing with:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rAhd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8665b99-f6a8-461f-8753-bfd6fe3665f9_1678x866.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rAhd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8665b99-f6a8-461f-8753-bfd6fe3665f9_1678x866.png 424w, https://substackcdn.com/image/fetch/$s_!rAhd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8665b99-f6a8-461f-8753-bfd6fe3665f9_1678x866.png 848w, https://substackcdn.com/image/fetch/$s_!rAhd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8665b99-f6a8-461f-8753-bfd6fe3665f9_1678x866.png 1272w, https://substackcdn.com/image/fetch/$s_!rAhd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8665b99-f6a8-461f-8753-bfd6fe3665f9_1678x866.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rAhd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8665b99-f6a8-461f-8753-bfd6fe3665f9_1678x866.png" width="1456" height="751" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d8665b99-f6a8-461f-8753-bfd6fe3665f9_1678x866.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:751,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:137574,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://snippetsofcode.substack.com/i/162967315?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8665b99-f6a8-461f-8753-bfd6fe3665f9_1678x866.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!rAhd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8665b99-f6a8-461f-8753-bfd6fe3665f9_1678x866.png 424w, https://substackcdn.com/image/fetch/$s_!rAhd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8665b99-f6a8-461f-8753-bfd6fe3665f9_1678x866.png 848w, https://substackcdn.com/image/fetch/$s_!rAhd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8665b99-f6a8-461f-8753-bfd6fe3665f9_1678x866.png 1272w, https://substackcdn.com/image/fetch/$s_!rAhd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8665b99-f6a8-461f-8753-bfd6fe3665f9_1678x866.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>&#128161; Warning Signs:</strong></p><ul><li><p>Page load times exceeding 5 seconds on data-heavy pages</p></li><li><p>Database CPU regularly spiking above 85%</p></li><li><p>Timeout errors during peak traffic hours</p></li><li><p>Exponentially increasing response times as data grew</p></li></ul><h2>The Journey: From Problem to Solution</h2><h3>Step 1: Database Optimization with Strategic Indexing</h3><blockquote><p>Before:</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!btZ3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ba088a7-63d2-4084-9f55-1d1df22e0d1a_1732x1016.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!btZ3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ba088a7-63d2-4084-9f55-1d1df22e0d1a_1732x1016.png 424w, https://substackcdn.com/image/fetch/$s_!btZ3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ba088a7-63d2-4084-9f55-1d1df22e0d1a_1732x1016.png 848w, https://substackcdn.com/image/fetch/$s_!btZ3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ba088a7-63d2-4084-9f55-1d1df22e0d1a_1732x1016.png 1272w, https://substackcdn.com/image/fetch/$s_!btZ3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ba088a7-63d2-4084-9f55-1d1df22e0d1a_1732x1016.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!btZ3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ba088a7-63d2-4084-9f55-1d1df22e0d1a_1732x1016.png" width="1456" height="854" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6ba088a7-63d2-4084-9f55-1d1df22e0d1a_1732x1016.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:854,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:134654,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://snippetsofcode.substack.com/i/162967315?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ba088a7-63d2-4084-9f55-1d1df22e0d1a_1732x1016.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!btZ3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ba088a7-63d2-4084-9f55-1d1df22e0d1a_1732x1016.png 424w, https://substackcdn.com/image/fetch/$s_!btZ3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ba088a7-63d2-4084-9f55-1d1df22e0d1a_1732x1016.png 848w, https://substackcdn.com/image/fetch/$s_!btZ3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ba088a7-63d2-4084-9f55-1d1df22e0d1a_1732x1016.png 1272w, https://substackcdn.com/image/fetch/$s_!btZ3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ba088a7-63d2-4084-9f55-1d1df22e0d1a_1732x1016.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p>After:</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JY5Z!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5eb0234-6807-4f51-9753-a5d929489dab_1816x1234.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JY5Z!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5eb0234-6807-4f51-9753-a5d929489dab_1816x1234.png 424w, https://substackcdn.com/image/fetch/$s_!JY5Z!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5eb0234-6807-4f51-9753-a5d929489dab_1816x1234.png 848w, https://substackcdn.com/image/fetch/$s_!JY5Z!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5eb0234-6807-4f51-9753-a5d929489dab_1816x1234.png 1272w, https://substackcdn.com/image/fetch/$s_!JY5Z!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5eb0234-6807-4f51-9753-a5d929489dab_1816x1234.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JY5Z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5eb0234-6807-4f51-9753-a5d929489dab_1816x1234.png" width="1456" height="989" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e5eb0234-6807-4f51-9753-a5d929489dab_1816x1234.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:989,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:171776,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://snippetsofcode.substack.com/i/162967315?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5eb0234-6807-4f51-9753-a5d929489dab_1816x1234.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!JY5Z!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5eb0234-6807-4f51-9753-a5d929489dab_1816x1234.png 424w, https://substackcdn.com/image/fetch/$s_!JY5Z!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5eb0234-6807-4f51-9753-a5d929489dab_1816x1234.png 848w, https://substackcdn.com/image/fetch/$s_!JY5Z!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5eb0234-6807-4f51-9753-a5d929489dab_1816x1234.png 1272w, https://substackcdn.com/image/fetch/$s_!JY5Z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5eb0234-6807-4f51-9753-a5d929489dab_1816x1234.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>&#127919; Impact:</strong></p><ul><li><p>Query execution time reduced from 2.3s to 180ms</p></li><li><p>Database load reduced by 40%</p></li><li><p>Eliminated timeout errors during peak hours</p></li></ul><h3>Step 2: Implementing Multi-Level Caching</h3><blockquote><p>Before<strong>:</strong></p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Yv4x!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cb0f6a9-c10c-417b-b161-1feb2bc67573_1606x1164.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Yv4x!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cb0f6a9-c10c-417b-b161-1feb2bc67573_1606x1164.png 424w, https://substackcdn.com/image/fetch/$s_!Yv4x!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cb0f6a9-c10c-417b-b161-1feb2bc67573_1606x1164.png 848w, https://substackcdn.com/image/fetch/$s_!Yv4x!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cb0f6a9-c10c-417b-b161-1feb2bc67573_1606x1164.png 1272w, https://substackcdn.com/image/fetch/$s_!Yv4x!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cb0f6a9-c10c-417b-b161-1feb2bc67573_1606x1164.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Yv4x!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cb0f6a9-c10c-417b-b161-1feb2bc67573_1606x1164.png" width="1456" height="1055" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6cb0f6a9-c10c-417b-b161-1feb2bc67573_1606x1164.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1055,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:145541,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://snippetsofcode.substack.com/i/162967315?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cb0f6a9-c10c-417b-b161-1feb2bc67573_1606x1164.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Yv4x!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cb0f6a9-c10c-417b-b161-1feb2bc67573_1606x1164.png 424w, https://substackcdn.com/image/fetch/$s_!Yv4x!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cb0f6a9-c10c-417b-b161-1feb2bc67573_1606x1164.png 848w, https://substackcdn.com/image/fetch/$s_!Yv4x!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cb0f6a9-c10c-417b-b161-1feb2bc67573_1606x1164.png 1272w, https://substackcdn.com/image/fetch/$s_!Yv4x!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6cb0f6a9-c10c-417b-b161-1feb2bc67573_1606x1164.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p>After:</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!A7j_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc12a0fe1-04fa-46db-878e-c0bbeabf6e48_1936x1608.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!A7j_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc12a0fe1-04fa-46db-878e-c0bbeabf6e48_1936x1608.png 424w, https://substackcdn.com/image/fetch/$s_!A7j_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc12a0fe1-04fa-46db-878e-c0bbeabf6e48_1936x1608.png 848w, https://substackcdn.com/image/fetch/$s_!A7j_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc12a0fe1-04fa-46db-878e-c0bbeabf6e48_1936x1608.png 1272w, https://substackcdn.com/image/fetch/$s_!A7j_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc12a0fe1-04fa-46db-878e-c0bbeabf6e48_1936x1608.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!A7j_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc12a0fe1-04fa-46db-878e-c0bbeabf6e48_1936x1608.png" width="1456" height="1209" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c12a0fe1-04fa-46db-878e-c0bbeabf6e48_1936x1608.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1209,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:222161,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://snippetsofcode.substack.com/i/162967315?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc12a0fe1-04fa-46db-878e-c0bbeabf6e48_1936x1608.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!A7j_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc12a0fe1-04fa-46db-878e-c0bbeabf6e48_1936x1608.png 424w, https://substackcdn.com/image/fetch/$s_!A7j_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc12a0fe1-04fa-46db-878e-c0bbeabf6e48_1936x1608.png 848w, https://substackcdn.com/image/fetch/$s_!A7j_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc12a0fe1-04fa-46db-878e-c0bbeabf6e48_1936x1608.png 1272w, https://substackcdn.com/image/fetch/$s_!A7j_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc12a0fe1-04fa-46db-878e-c0bbeabf6e48_1936x1608.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>&#127919; Impact:</strong></p><ul><li><p>Dashboard load time decreased from 4.2s to 320ms</p></li><li><p>Database queries reduced by 85% during peak hours</p></li><li><p>Server capacity effectively doubled without hardware changes</p></li></ul><h3>Step 3: Moving to Background Processing with Sidekiq</h3><blockquote><p>Before:</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!e_JJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e4fff4-3961-47ee-a1ef-71e27df061cc_1310x1314.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!e_JJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e4fff4-3961-47ee-a1ef-71e27df061cc_1310x1314.png 424w, https://substackcdn.com/image/fetch/$s_!e_JJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e4fff4-3961-47ee-a1ef-71e27df061cc_1310x1314.png 848w, https://substackcdn.com/image/fetch/$s_!e_JJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e4fff4-3961-47ee-a1ef-71e27df061cc_1310x1314.png 1272w, https://substackcdn.com/image/fetch/$s_!e_JJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e4fff4-3961-47ee-a1ef-71e27df061cc_1310x1314.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!e_JJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e4fff4-3961-47ee-a1ef-71e27df061cc_1310x1314.png" width="1310" height="1314" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/47e4fff4-3961-47ee-a1ef-71e27df061cc_1310x1314.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1314,&quot;width&quot;:1310,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:159559,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://snippetsofcode.substack.com/i/162967315?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e4fff4-3961-47ee-a1ef-71e27df061cc_1310x1314.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!e_JJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e4fff4-3961-47ee-a1ef-71e27df061cc_1310x1314.png 424w, https://substackcdn.com/image/fetch/$s_!e_JJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e4fff4-3961-47ee-a1ef-71e27df061cc_1310x1314.png 848w, https://substackcdn.com/image/fetch/$s_!e_JJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e4fff4-3961-47ee-a1ef-71e27df061cc_1310x1314.png 1272w, https://substackcdn.com/image/fetch/$s_!e_JJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F47e4fff4-3961-47ee-a1ef-71e27df061cc_1310x1314.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p>After:</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!tozu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb464a821-694a-4cb4-a072-f9b466f5828d_1252x1892.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!tozu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb464a821-694a-4cb4-a072-f9b466f5828d_1252x1892.png 424w, https://substackcdn.com/image/fetch/$s_!tozu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb464a821-694a-4cb4-a072-f9b466f5828d_1252x1892.png 848w, https://substackcdn.com/image/fetch/$s_!tozu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb464a821-694a-4cb4-a072-f9b466f5828d_1252x1892.png 1272w, https://substackcdn.com/image/fetch/$s_!tozu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb464a821-694a-4cb4-a072-f9b466f5828d_1252x1892.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!tozu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb464a821-694a-4cb4-a072-f9b466f5828d_1252x1892.png" width="1252" height="1892" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b464a821-694a-4cb4-a072-f9b466f5828d_1252x1892.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1892,&quot;width&quot;:1252,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:229730,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://snippetsofcode.substack.com/i/162967315?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb464a821-694a-4cb4-a072-f9b466f5828d_1252x1892.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!tozu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb464a821-694a-4cb4-a072-f9b466f5828d_1252x1892.png 424w, https://substackcdn.com/image/fetch/$s_!tozu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb464a821-694a-4cb4-a072-f9b466f5828d_1252x1892.png 848w, https://substackcdn.com/image/fetch/$s_!tozu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb464a821-694a-4cb4-a072-f9b466f5828d_1252x1892.png 1272w, https://substackcdn.com/image/fetch/$s_!tozu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb464a821-694a-4cb4-a072-f9b466f5828d_1252x1892.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>The Aha Moment</h2><p>Our breakthrough came when we realized we needed to stop treating Rails like a monolith that does everything synchronously. By intelligently distributing processing across background jobs, caching layers, and optimized database queries, we created a system that scaled with our growth rather than fighting against it.</p><h3>Real Numbers From This Experience</h3><ul><li><p>Before: 5.2s average response time</p></li><li><p>After: 1.1s average response time (78% improvement)</p></li><li><p>Database load: Reduced from 92% to 45% average utilization</p></li><li><p>Error rate: Dropped from 4.6% to 0.2%</p></li><li><p>User capacity: Increased from 5,000 to 22,000 daily active users on same hardware</p></li></ul><h2>The Final Result</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5cnE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facaa1950-8f12-49d2-bd9e-6279a5e5391d_1874x2678.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5cnE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facaa1950-8f12-49d2-bd9e-6279a5e5391d_1874x2678.png 424w, https://substackcdn.com/image/fetch/$s_!5cnE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facaa1950-8f12-49d2-bd9e-6279a5e5391d_1874x2678.png 848w, https://substackcdn.com/image/fetch/$s_!5cnE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facaa1950-8f12-49d2-bd9e-6279a5e5391d_1874x2678.png 1272w, https://substackcdn.com/image/fetch/$s_!5cnE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facaa1950-8f12-49d2-bd9e-6279a5e5391d_1874x2678.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5cnE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facaa1950-8f12-49d2-bd9e-6279a5e5391d_1874x2678.png" width="1456" height="2081" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/acaa1950-8f12-49d2-bd9e-6279a5e5391d_1874x2678.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2081,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:328838,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://snippetsofcode.substack.com/i/162967315?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facaa1950-8f12-49d2-bd9e-6279a5e5391d_1874x2678.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!5cnE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facaa1950-8f12-49d2-bd9e-6279a5e5391d_1874x2678.png 424w, https://substackcdn.com/image/fetch/$s_!5cnE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facaa1950-8f12-49d2-bd9e-6279a5e5391d_1874x2678.png 848w, https://substackcdn.com/image/fetch/$s_!5cnE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facaa1950-8f12-49d2-bd9e-6279a5e5391d_1874x2678.png 1272w, https://substackcdn.com/image/fetch/$s_!5cnE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facaa1950-8f12-49d2-bd9e-6279a5e5391d_1874x2678.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>&#127881; Key Improvements:</strong></p><ul><li><p>Reduced average response time by 78%</p></li><li><p>Cut database load in half</p></li><li><p>Eliminated timeout errors completely</p></li><li><p>Increased system capacity by over 300%</p></li><li><p>Improved developer experience with cleaner, more maintainable code</p></li></ul><h2>Monday Morning Action Items</h2><p><strong>1. Quick Wins (5-Minute Changes)</strong></p><ul><li><p>Add indexes to your most frequently queried columns</p></li><li><p>Wrap expensive dashboard calculations in Rails.cache.fetch blocks</p></li><li><p>Use includes(:associations) to eliminate N+1 queries</p></li></ul><p><strong>2. Next Steps</strong></p><ul><li><p>Set up Sidekiq for background processing of non-critical tasks</p></li><li><p>Implement fragment caching for your most visited pages</p></li><li><p>Consider PostgreSQL materialized views for complex reporting queries</p></li></ul><h2>Your Turn!</h2><h3>The Database Optimization Challenge</h3><p>Take a look at this controller action and identify optimization opportunities:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KVmP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccda4c34-4f2e-466a-bfb1-1f76cd7e2915_1382x990.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KVmP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccda4c34-4f2e-466a-bfb1-1f76cd7e2915_1382x990.png 424w, https://substackcdn.com/image/fetch/$s_!KVmP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccda4c34-4f2e-466a-bfb1-1f76cd7e2915_1382x990.png 848w, https://substackcdn.com/image/fetch/$s_!KVmP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccda4c34-4f2e-466a-bfb1-1f76cd7e2915_1382x990.png 1272w, https://substackcdn.com/image/fetch/$s_!KVmP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccda4c34-4f2e-466a-bfb1-1f76cd7e2915_1382x990.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KVmP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccda4c34-4f2e-466a-bfb1-1f76cd7e2915_1382x990.png" width="1382" height="990" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ccda4c34-4f2e-466a-bfb1-1f76cd7e2915_1382x990.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:990,&quot;width&quot;:1382,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:147191,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://snippetsofcode.substack.com/i/162967315?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccda4c34-4f2e-466a-bfb1-1f76cd7e2915_1382x990.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!KVmP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccda4c34-4f2e-466a-bfb1-1f76cd7e2915_1382x990.png 424w, https://substackcdn.com/image/fetch/$s_!KVmP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccda4c34-4f2e-466a-bfb1-1f76cd7e2915_1382x990.png 848w, https://substackcdn.com/image/fetch/$s_!KVmP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccda4c34-4f2e-466a-bfb1-1f76cd7e2915_1382x990.png 1272w, https://substackcdn.com/image/fetch/$s_!KVmP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccda4c34-4f2e-466a-bfb1-1f76cd7e2915_1382x990.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>&#128172; Discussion Prompts:</strong></p><ul><li><p>What N+1 query issues do you see in this code?</p></li><li><p>Which database indexes would you add to improve performance?</p></li><li><p>How would you implement caching for this action?</p></li></ul><div><hr></div><p><strong>&#128295; Useful Resources:</strong></p><ul><li><p> <a href="https://www.postgresql.org/docs/current/indexes.html">PostgreSQL Indexing: How, Why, When</a></p></li><li><p><a href="https://github.com/mperham/sidekiq">Sidekiq GitHub Repository</a></p></li><li><p><a href="https://guides.rubyonrails.org/caching_with_rails.html">The Complete Guide to Rails Caching</a></p></li></ul><div><hr></div><p>Found this useful? Share it with a fellow Rails developer! And don't forget to reply with your solution to this week's optimization challenge.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://snippetsofcode.substack.com/p/turbocharging-rails?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://snippetsofcode.substack.com/p/turbocharging-rails?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><h2>Happy coding!</h2><p><strong>Pro Tips:</strong></p><ul><li><p>Use the <a href="https://github.com/flyerhzm/bullet">Bullet gem</a> to automatically detect N+1 queries in development</p></li><li><p><strong>Remember:</strong> Caching is great, but always have a strategy for cache invalidation to prevent stale data</p></li></ul>]]></content:encoded></item></channel></rss>