{
  "version": "https://jsonfeed.org/version/1",
  "title": "Rails on enumerator.dev",
  "icon": "<no value>",
  "home_page_url": "https://enumerator.dev/",
  "feed_url": "https://enumerator.dev/feed.json",
  "items": [
      {
        "id": "https://enumerator.dev/naming-things-for-what-they-do/",
        "title": "Naming Things for What They Do",
        "content_html": "<p>I just saw the Rails World announcement that flavorjones is working on <a href=\"https://github.com/basecamp/activerecord-tenanted\">ActiveRecord::Tenanted</a> and all I can say is, “What a reasonable name!”</p>\n<p>When I started willow.camp I chose not to use <a href=\"https://github.com/influitive/apartment\">apartment</a> partially because the name and the metaphor were too confusing. I didn&rsquo;t like tho have to get used to the idea of requests taking &ldquo;elevators&rdquo; and all that. The &ldquo;apartment&rdquo; metaphor doesn&rsquo;t work for databases even though &ldquo;tenant&rdquo; does.</p>\n<p>Always choose descriptive names over clever ones. It doesn’t matter how well you think the metaphor fits; you’ll always end up with something confusing in the end.</p>\n",
        "date_published": "2025-09-09T11:23:33+00:00",
        "url": "https://enumerator.dev/naming-things-for-what-they-do/",
        "tags": ["ruby","rails"]
      },
      {
        "id": "https://enumerator.dev/end-to-end-tests-with-minitest-and-rails/",
        "title": "End To End Tests with Minitest and Rails",
        "content_html": "<p>willow.camp has a small system test suite that runs on CI. It used to use the out-of-the-box settings that come with Rails: Capybara, Selenium, etc.</p>\n<p>I noticed a flaky test on CI, which launched a journey to replace Selenium with Cuprite.</p>\n<h2 id=\"fixing-the-flaky-test\">Fixing the Flaky Test</h2>\n<p>I worked through the flaky test with Claude Code, and as Claude was running the tests, I noticed this pop-up from Chrome.</p>\n<blockquote>\n<p>A data breach on a site or app exposed your password&hellip;</p>\n</blockquote>\n<p>I don&rsquo;t use Chrome very often, so this jumped out at me. Of course, I use the oh-so-secret password &ldquo;password&rdquo; in my test suite, and Chrome flagged it as unsafe.</p>\n<p>Disabling password leak detection fixed this for me.</p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-fallback\" data-lang=\"fallback\"><span class=\"line\"><span class=\"cl\">browser_options: { &#34;disable-features&#34;: &#34;PasswordLeakDetection&#34; }\n</span></span></code></pre></div><p>But I still got timeouts on CI that looked like the tests were running before the browser was ready.</p>\n<p>Waiting for CI is a bit painful. You can see my hilariously bad Git history with Claude&rsquo;s gleeful overconfidence:</p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-fallback\" data-lang=\"fallback\"><span class=\"line\"><span class=\"cl\">* 0c514d1 - nosandbox (15 minutes ago) &lt;Cassia Scheffer&gt;\n</span></span><span class=\"line\"><span class=\"cl\">...\n</span></span><span class=\"line\"><span class=\"cl\">* 57ac55b - bump timeout to 30 sec for ci (21 minutes ago)\n</span></span><span class=\"line\"><span class=\"cl\">* 9d6e86e - rewirite cuprite/capybara configs (26 minutes ago)\n</span></span><span class=\"line\"><span class=\"cl\">* 39f3238 - fix: simplify Cuprite configuration following best practices (10 hours ago)\n</span></span><span class=\"line\"><span class=\"cl\">* 68bfd53 - fix: simplify Cuprite configuration and fix CI timeout issues (24 hours ago)\n</span></span><span class=\"line\"><span class=\"cl\">* cebfae7 - fix: resolve Cuprite websocket timeout issues in CI (24 hours ago)\n</span></span><span class=\"line\"><span class=\"cl\">* dc288b7 - fix: make Cuprite configuration more robust for CI environments (24 hours ago)\n</span></span><span class=\"line\"><span class=\"cl\">...\n</span></span><span class=\"line\"><span class=\"cl\">* e5bb4df - fix: increase Cuprite timeout and add CI-specific settings for GitHub Acti&gt;\n</span></span><span class=\"line\"><span class=\"cl\">...\n</span></span><span class=\"line\"><span class=\"cl\">* cb36eaa - feat: migrate system tests from Selenium to Cuprite (35 hours ago)\n</span></span><span class=\"line\"><span class=\"cl\">* 39f0ff1 - disable selenium chrome password detection (35 hours ago)\n</span></span></code></pre></div><h3 id=\"that-looks-bad-cassia-why-would-you-let-claude-do-that\">That Looks Bad, Cassia. Why Would You Let Claude Do That!?</h3>\n<p>Because Claude is faster at making mistakes than I am, and I am tired of waiting for CI. Making mistakes quickly means speedier feedback. So I sent Claude off on a fool&rsquo;s errand to help me learn from the mistakes I would otherwise have made myself.</p>\n<p>In the meantime, I read the <a href=\"https://evilmartians.com/chronicles/system-of-a-test-setting-up-end-to-end-rails-testing#dockerizing-system-tests\">Evil Martians article</a> about better system tests.</p>\n<h2 id=\"using-cuprite-in-rails-with-minitest\">Using Cuprite in Rails with Minitest</h2>\n<p>The Evil Martians article on system tests uses RSpec, but willow.camp uses Minitest. Here&rsquo;s what I had to change to make it work with Minitest.</p>\n<h3 id=\"1-precompileassets-becomes-a-module-and-gets-called-when-setup-runs\">1. PrecompileAssets becomes a module and gets called when setup runs.</h3>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-rb\" data-lang=\"rb\"><span class=\"line\"><span class=\"cl\"><span class=\"c1\"># Precompile assets before running tests to avoid timeouts.</span>\n</span></span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"># Do not precompile if webpack-dev-server is running (NOTE: MUST be launched with RAILS_ENV=test)</span>\n</span></span><span class=\"line\"><span class=\"cl\">\n</span></span><span class=\"line\"><span class=\"cl\"><span class=\"k\">module</span> <span class=\"nn\">PrecompileAssets</span>\n</span></span><span class=\"line\"><span class=\"cl\">  <span class=\"k\">def</span> <span class=\"nc\">self</span><span class=\"o\">.</span><span class=\"nf\">setup</span>\n</span></span><span class=\"line\"><span class=\"cl\">    <span class=\"c1\"># Check if we&#39;re running system tests by looking at the test files being loaded</span>\n</span></span><span class=\"line\"><span class=\"cl\">    <span class=\"n\">running_system_tests</span> <span class=\"o\">=</span> <span class=\"nb\">caller</span><span class=\"o\">.</span><span class=\"n\">any?</span> <span class=\"p\">{</span> <span class=\"o\">|</span><span class=\"n\">line</span><span class=\"o\">|</span> <span class=\"n\">line</span><span class=\"o\">.</span><span class=\"n\">include?</span><span class=\"p\">(</span><span class=\"s2\">&#34;test/system&#34;</span><span class=\"p\">)</span> <span class=\"p\">}</span> <span class=\"o\">||</span>\n</span></span><span class=\"line\"><span class=\"cl\">      <span class=\"no\">ARGV</span><span class=\"o\">.</span><span class=\"n\">any?</span> <span class=\"p\">{</span> <span class=\"o\">|</span><span class=\"n\">arg</span><span class=\"o\">|</span> <span class=\"n\">arg</span><span class=\"o\">.</span><span class=\"n\">include?</span><span class=\"p\">(</span><span class=\"s2\">&#34;test/system&#34;</span><span class=\"p\">)</span> <span class=\"p\">}</span> <span class=\"o\">||</span>\n</span></span><span class=\"line\"><span class=\"cl\">      <span class=\"no\">ENV</span><span class=\"o\">[</span><span class=\"s2\">&#34;RAILS_TEST_TYPE&#34;</span><span class=\"o\">]</span> <span class=\"o\">==</span> <span class=\"s2\">&#34;system&#34;</span>\n</span></span><span class=\"line\"><span class=\"cl\">\n</span></span><span class=\"line\"><span class=\"cl\">    <span class=\"k\">unless</span> <span class=\"n\">running_system_tests</span>\n</span></span><span class=\"line\"><span class=\"cl\">      <span class=\"nb\">puts</span> <span class=\"s2\">&#34;</span><span class=\"se\">\\n</span><span class=\"s2\">🚀️️  No system test selected. Skip assets compilation.</span><span class=\"se\">\\n</span><span class=\"s2\">&#34;</span>\n</span></span><span class=\"line\"><span class=\"cl\">      <span class=\"k\">return</span>\n</span></span><span class=\"line\"><span class=\"cl\">    <span class=\"k\">end</span>\n</span></span><span class=\"line\"><span class=\"cl\">\n</span></span><span class=\"line\"><span class=\"cl\">    <span class=\"nb\">puts</span> <span class=\"s2\">&#34;</span><span class=\"se\">\\n</span><span class=\"s2\">🐢  Precompiling assets.</span><span class=\"se\">\\n</span><span class=\"s2\">&#34;</span>\n</span></span><span class=\"line\"><span class=\"cl\">    <span class=\"n\">original_stdout</span> <span class=\"o\">=</span> <span class=\"vg\">$stdout</span><span class=\"o\">.</span><span class=\"n\">clone</span>\n</span></span><span class=\"line\"><span class=\"cl\">\n</span></span><span class=\"line\"><span class=\"cl\">    <span class=\"n\">start</span> <span class=\"o\">=</span> <span class=\"no\">Time</span><span class=\"o\">.</span><span class=\"n\">current</span>\n</span></span><span class=\"line\"><span class=\"cl\">    <span class=\"k\">begin</span>\n</span></span><span class=\"line\"><span class=\"cl\">      <span class=\"vg\">$stdout</span><span class=\"o\">.</span><span class=\"n\">reopen</span><span class=\"p\">(</span><span class=\"no\">File</span><span class=\"o\">.</span><span class=\"n\">new</span><span class=\"p\">(</span><span class=\"no\">File</span><span class=\"o\">::</span><span class=\"no\">Constants</span><span class=\"o\">::</span><span class=\"no\">NULL</span><span class=\"p\">,</span> <span class=\"s2\">&#34;w&#34;</span><span class=\"p\">))</span>\n</span></span><span class=\"line\"><span class=\"cl\">\n</span></span><span class=\"line\"><span class=\"cl\">      <span class=\"nb\">require</span> <span class=\"s2\">&#34;rake&#34;</span>\n</span></span><span class=\"line\"><span class=\"cl\">      <span class=\"no\">Rails</span><span class=\"o\">.</span><span class=\"n\">application</span><span class=\"o\">.</span><span class=\"n\">load_tasks</span>\n</span></span><span class=\"line\"><span class=\"cl\">      <span class=\"no\">Rake</span><span class=\"o\">::</span><span class=\"no\">Task</span><span class=\"o\">[</span><span class=\"s2\">&#34;assets:precompile&#34;</span><span class=\"o\">].</span><span class=\"n\">invoke</span>\n</span></span><span class=\"line\"><span class=\"cl\">    <span class=\"k\">ensure</span>\n</span></span><span class=\"line\"><span class=\"cl\">      <span class=\"vg\">$stdout</span><span class=\"o\">.</span><span class=\"n\">reopen</span><span class=\"p\">(</span><span class=\"n\">original_stdout</span><span class=\"p\">)</span>\n</span></span><span class=\"line\"><span class=\"cl\">      <span class=\"nb\">puts</span> <span class=\"s2\">&#34;Finished in </span><span class=\"si\">#{</span><span class=\"p\">(</span><span class=\"no\">Time</span><span class=\"o\">.</span><span class=\"n\">current</span> <span class=\"o\">-</span> <span class=\"n\">start</span><span class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">round</span><span class=\"p\">(</span><span class=\"mi\">2</span><span class=\"p\">)</span><span class=\"si\">}</span><span class=\"s2\"> seconds&#34;</span>\n</span></span><span class=\"line\"><span class=\"cl\">    <span class=\"k\">end</span>\n</span></span><span class=\"line\"><span class=\"cl\">  <span class=\"k\">end</span>\n</span></span><span class=\"line\"><span class=\"cl\"><span class=\"k\">end</span>\n</span></span><span class=\"line\"><span class=\"cl\">\n</span></span><span class=\"line\"><span class=\"cl\"><span class=\"c1\"># Run the setup when this file is loaded</span>\n</span></span><span class=\"line\"><span class=\"cl\"><span class=\"no\">PrecompileAssets</span><span class=\"o\">.</span><span class=\"n\">setup</span> <span class=\"k\">if</span> <span class=\"n\">defined?</span><span class=\"p\">(</span><span class=\"no\">Rails</span><span class=\"p\">)</span>\n</span></span></code></pre></div><h3 id=\"2-betterrailssystemtests-becomes-a-moduel\">2. BetterRailsSystemTests Becomes a Moduel</h3>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-rb\" data-lang=\"rb\"><span class=\"line\"><span class=\"cl\"><span class=\"k\">module</span> <span class=\"nn\">BetterRailsSystemTests</span>\n</span></span><span class=\"line\"><span class=\"cl\">  <span class=\"c1\"># Make failure screenshots compatible with multi-session setup.</span>\n</span></span><span class=\"line\"><span class=\"cl\">  <span class=\"k\">def</span> <span class=\"nf\">take_screenshot</span>\n</span></span><span class=\"line\"><span class=\"cl\">    <span class=\"k\">return</span> <span class=\"k\">super</span> <span class=\"k\">unless</span> <span class=\"no\">Capybara</span><span class=\"o\">.</span><span class=\"n\">last_used_session</span>\n</span></span><span class=\"line\"><span class=\"cl\">\n</span></span><span class=\"line\"><span class=\"cl\">    <span class=\"no\">Capybara</span><span class=\"o\">.</span><span class=\"n\">using_session</span><span class=\"p\">(</span><span class=\"no\">Capybara</span><span class=\"o\">.</span><span class=\"n\">last_used_session</span><span class=\"p\">)</span> <span class=\"p\">{</span> <span class=\"k\">super</span> <span class=\"p\">}</span>\n</span></span><span class=\"line\"><span class=\"cl\">  <span class=\"k\">end</span>\n</span></span><span class=\"line\"><span class=\"cl\"><span class=\"k\">end</span>\n</span></span></code></pre></div><h3 id=\"3-modules-are-included-in-applicationsystemtestcase\">3. Modules are included in ApplicationSystemTestCase</h3>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-rb\" data-lang=\"rb\"><span class=\"line\"><span class=\"cl\"><span class=\"nb\">require</span> <span class=\"s2\">&#34;test_helper&#34;</span>\n</span></span><span class=\"line\"><span class=\"cl\"><span class=\"nb\">require</span> <span class=\"s2\">&#34;capybara/cuprite&#34;</span>\n</span></span><span class=\"line\"><span class=\"cl\"><span class=\"n\">require_relative</span> <span class=\"s2\">&#34;system/system_helper&#34;</span>\n</span></span><span class=\"line\"><span class=\"cl\">\n</span></span><span class=\"line\"><span class=\"cl\"><span class=\"k\">class</span> <span class=\"nc\">ApplicationSystemTestCase</span> <span class=\"o\">&lt;</span> <span class=\"no\">ActionDispatch</span><span class=\"o\">::</span><span class=\"no\">SystemTestCase</span>\n</span></span><span class=\"line\"><span class=\"cl\">  <span class=\"kp\">include</span> <span class=\"no\">BetterRailsSystemTests</span>\n</span></span><span class=\"line\"><span class=\"cl\">  <span class=\"kp\">include</span> <span class=\"no\">CupriteHelpers</span>\n</span></span><span class=\"line\"><span class=\"cl\">\n</span></span><span class=\"line\"><span class=\"cl\">  <span class=\"n\">driven_by</span> <span class=\"no\">Capybara</span><span class=\"o\">.</span><span class=\"n\">javascript_driver</span>\n</span></span><span class=\"line\"><span class=\"cl\">\n</span></span><span class=\"line\"><span class=\"cl\">  <span class=\"k\">def</span> <span class=\"nf\">setup</span>\n</span></span><span class=\"line\"><span class=\"cl\">    <span class=\"k\">super</span>\n</span></span><span class=\"line\"><span class=\"cl\">    <span class=\"c1\"># Use JS driver always</span>\n</span></span><span class=\"line\"><span class=\"cl\">\n</span></span><span class=\"line\"><span class=\"cl\">    <span class=\"c1\"># Store original host for cleanup</span>\n</span></span><span class=\"line\"><span class=\"cl\">    <span class=\"vi\">@original_host</span> <span class=\"o\">=</span> <span class=\"no\">Rails</span><span class=\"o\">.</span><span class=\"n\">application</span><span class=\"o\">.</span><span class=\"n\">default_url_options</span><span class=\"o\">[</span><span class=\"ss\">:host</span><span class=\"o\">]</span>\n</span></span><span class=\"line\"><span class=\"cl\">    <span class=\"c1\"># Make urls in mailers contain the correct server host.</span>\n</span></span><span class=\"line\"><span class=\"cl\">    <span class=\"c1\"># This is required for testing links in emails (e.g., via capybara-email).</span>\n</span></span><span class=\"line\"><span class=\"cl\">    <span class=\"no\">Rails</span><span class=\"o\">.</span><span class=\"n\">application</span><span class=\"o\">.</span><span class=\"n\">default_url_options</span><span class=\"o\">[</span><span class=\"ss\">:host</span><span class=\"o\">]</span> <span class=\"o\">=</span> <span class=\"no\">Capybara</span><span class=\"o\">.</span><span class=\"n\">server_host</span>\n</span></span><span class=\"line\"><span class=\"cl\">  <span class=\"k\">end</span>\n</span></span><span class=\"line\"><span class=\"cl\">\n</span></span><span class=\"line\"><span class=\"cl\">  <span class=\"k\">def</span> <span class=\"nf\">teardown</span>\n</span></span><span class=\"line\"><span class=\"cl\">    <span class=\"c1\"># Restore original host</span>\n</span></span><span class=\"line\"><span class=\"cl\">    <span class=\"no\">Rails</span><span class=\"o\">.</span><span class=\"n\">application</span><span class=\"o\">.</span><span class=\"n\">default_url_options</span><span class=\"o\">[</span><span class=\"ss\">:host</span><span class=\"o\">]</span> <span class=\"o\">=</span> <span class=\"vi\">@original_host</span>\n</span></span><span class=\"line\"><span class=\"cl\">    <span class=\"k\">super</span>\n</span></span><span class=\"line\"><span class=\"cl\">  <span class=\"k\">end</span>\n</span></span><span class=\"line\"><span class=\"cl\"><span class=\"k\">end</span>\n</span></span></code></pre></div><h2 id=\"flakiness-still-included-on-ci\">Flakiness Still Included on CI</h2>\n<p>I still see a bit of system test flakiness on CI, but far less than before. I am running on the default GitHub Actions runner, which is pretty small. So there&rsquo;s a good chance that the flakiness is due to low resources on the machine. So far, tests have consistently passed when I run these on my M1 MacBook Air.</p>\n<p>I&rsquo;ll dig into these flaky tests another day! But this looks like an improvement overall to me!</p>\n",
        "date_published": "2025-08-13T22:27:26+00:00",
        "url": "https://enumerator.dev/end-to-end-tests-with-minitest-and-rails/",
        "tags": ["willow-camp","rails","capybra","minitest"]
      },
      {
        "id": "https://enumerator.dev/use-precomputed-hashes-instead-of-bcrypt-in-test/",
        "title": "Use Precomputed Hashes Instead of BCrypt in Test",
        "content_html": "<p>I asked Claude to find the slowest test file again after my success <a href=\"/speeding-up-willow-camp-s-post_test-rb-by-99\">earlier today</a> and Claude found a call to BCrypt in my fixtures that was gobbling up time.</p>\n<p>I need user records all over the place in my test and each new user is a new call to encrypt a password.</p>\n<p>Here is Claude&rsquo;s summary, with my Grammarly edits:</p>\n<blockquote>\n<p>Problem: The <code>PreviewsControllerTest#test_should_show_preview_for_own_unpublished_post</code> test was taking 2.95 seconds due to BCrypt password hashing being computed dynamically in fixtures with <code>&lt;%= BCrypt::Password.create(&quot;password&quot;) %&gt;</code>.</p>\n<p>Solution: Replaced the dynamic BCrypt computation with a pre-computed hash <code>$2a$12$dapdHZiOTxAL8OT9yNI6e.hhTjRhWgXq7kx737/xf8tBfQbMD2bi</code> for all user fixtures.</p>\n<p>Result: The slowest test now runs in ~0.25-0.52 seconds (82% improvement) and all tests pass. The overall test suite performance has been significantly improved, as this optimization affects all tests that use user fixtures.</p>\n</blockquote>\n",
        "date_published": "2025-07-21T00:55:00+00:00",
        "url": "https://enumerator.dev/use-precomputed-hashes-instead-of-bcrypt-in-test/",
        "tags": ["willow-camp","performance","ruby","rails"]
      },
      {
        "id": "https://enumerator.dev/speeding-up-willow-camp-s-post_test-rb-by-99/",
        "title": "Speeding Up willow.camp's post_test.rb by 99%",
        "content_html": "<p>I just asked Claude to speed up the slowest test file in willow.camp. post_test.rb was taking over 1 second to execute. For a tiny Ruby app, that&rsquo;s a long time.</p>\n<p>Here&rsquo;s what Claude did. I was not expecting such a simple fix!</p>\n<blockquote>\n<p>Test: test_should_detect_mermaid_diagrams_and_set_has_mermaid_diagrams_flag in test/models/post_test.rb:82</p>\n<p>Issue: The test was using an unnecessarily large markdown string that required\nexpensive processing through the Commonmarker library.</p>\n<p>Optimization: Reduced the test markdown from a multi-section document to a minimal string that still tests the same functionality:</p>\n<ul>\n<li>Before: Large heredoc with headers, multiple code blocks, and text content</li>\n<li>After: Simple &ldquo;<code>mermaid\\ngraph TD\\n  A --&gt; B\\n</code>&rdquo; string\nPerformance Improvement:</li>\n<li>Before: 1.31 seconds</li>\n<li>After: 0.01 seconds</li>\n<li>Improvement: 99%+ faster (130x speed improvement)\nThe optimization maintains full test coverage while dramatically reducing execution time by eliminating unnecessary markdown processing overhead.</li>\n</ul>\n</blockquote>\n<p>The original test was designed to evaluate a full-fledged Markdown document that was about 10 lines long. I didn&rsquo;t need that in this case because I just needed to test that my custom <code>mermaid</code> code block worked.</p>\n<p>Thanks Claude!</p>\n",
        "date_published": "2025-07-20T14:05:00+00:00",
        "url": "https://enumerator.dev/speeding-up-willow-camp-s-post_test-rb-by-99/",
        "tags": ["willow-camp","performance","ruby","rails"]
      },
      {
        "id": "https://enumerator.dev/introducing-willow-camp/",
        "title": "Introducing willow.camp",
        "content_html": "<p>I&rsquo;ve been working on a side project and am finally ready to share it! My blog, enumerator.dev, is now hosted on my platform called <a href=\"https://willow.camp\">willow.camp</a>.</p>\n<p>willow.camp started because I wanted a platform that felt like me. I wanted a simple editing interface, the ability to import and export every post in markdown, and a pared-down reading interface.</p>\n<p>My blog was previously hosted on <a href=\"https://ghost.org/\">Ghost</a>, but the experience felt like an &ldquo;influencer&rdquo; blog to me. I&rsquo;m a regular developer sharing the projects I work on, and I don&rsquo;t need a big newsletter distribution list or metrics on open rates.</p>\n<p>I looked at <a href=\"https://bearblog.dev/\">bearblog.dev</a> and <a href=\"https://pika.page/\">pika.page</a> as alternatives to Ghost. They are both lovely platforms, and willow.camp is inspired by them, but they didn&rsquo;t quite meet my needs. So I took the plunge and typed <code>rails new willow_camp</code> in my terminal.</p>\n<p>Well, okay, I admit. First, I tried Elixir; then I read about doing it in Rust and tested out Hanami before realizing that I wanted to create something more than I wanted to learn a new language.</p>\n<p>Sometimes speed is about using a batteries-included framework more than it is about milliseconds of server render time.</p>\n<h2 id=\"the-willowcamp-stack\">The willow.camp Stack</h2>\n<p>willow.camp is built on Rails 8, Turbo, Hotwire, Stimulus, and  DaisyUI. These libraries have made it easy to spin up scaffolding and prototype ideas. A considerable part of the initial commits in willow.camp are all <code>rails scaffold</code> (not even AI!). As things took shape, I polished off features using <a href=\"https://github.com/upstash/context7\">context7</a> to pull docs into Zed hooked up to GitHub Copilot.</p>\n<p>So far, here are a few libraries I&rsquo;ve used to develop willow.camp:</p>\n<ul>\n<li><a href=\"https://github.com/mbleigh/acts-as-taggable-on\">acts-as-taggable-on</a> for tagging posts.</li>\n<li><a href=\"https://github.com/avo-hq/marksmith\">marksmith</a> for a markdown editing UI.</li>\n<li><a href=\"https://github.com/gjtorikian/commonmarker\">commonmarker</a> for parsing and rendering markdown.</li>\n<li><a href=\"https://github.com/waiting-for-dev/front_matter_parser\">front_matter_parser</a> for parsing YAML front matter on markdown files.</li>\n<li><a href=\"https://github.com/norman/friendly_id\">friendly_id</a> for post slugs.</li>\n<li><a href=\"https://github.com/ddnexus/pagy\">pagy</a> for pagination.</li>\n<li><a href=\"https://github.com/weppos/publicsuffix-ruby\">public_suffix</a> for validating custom domains without a complex regex.</li>\n</ul>\n<h2 id=\"hosting\">Hosting</h2>\n<p>willow.camp is hosted on a <a href=\"https://www.digitalocean.com/\">DigitalOcean</a> instance with a managed Postgres database. Deployments and configs are managed through <a href=\"https://hatchbox.io/\">hatchbox.io</a>.</p>\n<h2 id=\"apm-errors-emails\">APM, Errors, Emails</h2>\n<p>I hooked up <a href=\"https://www.scoutapm.com/\">Scout Monitoring</a> for APM and <a href=\"https://www.honeybadger.io/\">Honeybadger</a> for error tracking. For now, I&rsquo;m well below the usage limit for both, so I&rsquo;m on the free plan. I&rsquo;m happy with both of those, but I find the log management in Scout to be somewhat clunky on my tiny MacBook Air&rsquo;s screen. I&rsquo;m not sure I&rsquo;d pay for the logging aspects of Scout, but everything else meets my needs.</p>\n<p>Password reset emails are sent through <a href=\"https://loops.so/\">loops.so</a>. Loops was straightforward to set up, and transactional emails are free.</p>\n<h2 id=\"dashboard\">Dashboard</h2>\n<p>As mentioned above, the editing interface is <a href=\"https://github.com/avo-hq/marksmith\">marksmith</a>, an easy-to-use, plug-and-play markdown editor with a built-in preview feature. Posts are saved in both markdown and HTML formats. This makes exporting content through the API straightforward because Markdown is the base format in which posts are written and saved. HTML is only used for display.</p>\n<h2 id=\"mermaid-diagrams\">Mermaid Diagrams</h2>\n<p>You can add a code block with <code>mermaid</code> as the language, which will render a mermaid diagram on the page in place of the code block. Have a look at <a href=\"/how-rack-timeout-keeps-your-app-alive\">How Rack::Timeout Keeps Your App Alive</a> for an example.</p>\n<h2 id=\"auto-saving-posts\">Auto-saving Posts</h2>\n<p>I like to start a draft post, then walk away and think about it. While I enjoy writing in Obsidian, I often use my blog &ldquo;drafts&rdquo; as a place to store ideas.</p>\n<p>I added a stimulus controller that auto-saves draft posts. It checks if the post is &ldquo;dirty&rdquo; (i.e., changed since the last save) and saves it every 60 seconds. You can also hit Command/Control + S to save while you type.</p>\n<p>Auto-save is disabled on &ldquo;published&rdquo; posts because I wouldn&rsquo;t want a half-edited post to be saved and published.</p>\n<h2 id=\"no-image-hosting\">No Image Hosting</h2>\n<p>I don&rsquo;t host images or static content aside from what&rsquo;s in the Rails asset pipeline. This is intentional right now. I want to keep hosting complexity to a minimum, and I didn&rsquo;t want to worry about storage services, mounting volumes, or data retention. For now, I&rsquo;d like the server to be ephemeral.</p>\n<h2 id=\"favicons-themes-subdomains--custom-domains\">Favicons, Themes, Subdomains, &amp; Custom Domains</h2>\n<p>Of course, I spent the most time on the settings page.</p>\n<p>You can preview custom favicons and themes on the willow.camp home page by clicking the menu items in the top right. The themes come from DaisyUI. The favicon functionality still requires some refinement.</p>\n<h3 id=\"how-willowcamp-sort-of-generates-favicons\">How willow.camp (sort of) Generates Favicons</h3>\n<p>Users can insert any emoji into the &ldquo;favicon&rdquo; settings form. The emoji is saved to their settings, and then when the page loads, it is rendered in in-memory canvases to generate the various sizes of favicons needed and insert them into the <code>head</code> as an image in a URI scheme like so:</p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-html\" data-lang=\"html\"><span class=\"line\"><span class=\"cl\"><span class=\"p\">&lt;</span><span class=\"nt\">link</span> <span class=\"na\">type</span><span class=\"o\">=</span><span class=\"s\">&#34;image/png&#34;</span> <span class=\"na\">rel</span><span class=\"o\">=</span><span class=\"s\">&#34;icon&#34;</span> <span class=\"na\">sizes</span><span class=\"o\">=</span><span class=\"s\">&#34;16x16&#34;</span> <span class=\"na\">href</span><span class=\"o\">=</span><span class=\"s\">&#34;data:image/png;base64,...lots of characters=&#34;</span><span class=\"p\">&gt;</span>\n</span></span></code></pre></div><p>This works for most cases, but since the image doesn&rsquo;t exist until the JS renders it, some browsers may not display a favicon at all, and there are no Open Graph images available for link previews on social sites.</p>\n<h4 id=\"better-favicons-in-the-future\">Better Favicons in the Future</h4>\n<p>I want to fix the favicon functionality. I think the best way to do this reliably is to pre-generate images for 10-20 emojis that users can pick from. This way, I only have to store a set number of assets on the server and can provide real links to favicon images.</p>\n<p>Another possibility is to render a canvas in the browser when the user selects the desired emoji. Then, when they update their settings, save the favicon as an attachment and generate the correct sizes on the server. This would provide flexibility to use any emoji, but it would also require hosting more static content, which I didn&rsquo;t want to do yet.</p>\n<h3 id=\"themes\">Themes</h3>\n<p>The DaisyUI themes are fun! I borrowed their theme picker HTML and used a Stimulus controller to give instant previews of each theme. Users&rsquo; theme preferences are saved in the DB and rendered in the HTML.</p>\n<p>I experimented with allowing users to choose between a light and dark theme, but couldn&rsquo;t get it to work to my satisfaction. <a href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme\">prefers-colour-scheme</a> works great with DiasyUI&rsquo;s default <code>light</code> and <code>dark</code>, but I didn&rsquo;t see an easy way to pass a user&rsquo;s preferences from the database into the CSS without compiling the assets on each request.</p>\n<p>I did have a light/dark functionality sort of working with a Stimulus controller and some data attributes in the HTML, but there was a brief flash of the default <code>light</code> or <code>dark</code> scheme before the chosen scheme was loaded.</p>\n<p>For now, you can pick one colour scheme for your willow.camp blog.</p>\n<h3 id=\"subdomains-and-custom-domains\">Subdomains and Custom Domains</h3>\n<p>Thanks to hatchbox&rsquo;s Caddy setup, subdomains and custom domains are served over HTTPS. Each blog has a subdomain like cassia.willow.camp and an optional custom domain like enumerator.dev. Users point a CNAME record to their subdomain to set up a custom domain name.</p>\n<h2 id=\"seo\">SEO</h2>\n<p>It has been nearly a decade since I had to think about SEO. I&rsquo;ve added <a href=\"https://ogp.me/\">Open Graph</a> tags to the pages, and every site gets a <code>sitemap.xml</code> and <code>robots.txt</code>. I tried to use semantic HTML as best I could and asked Claude to audit the pages.</p>\n<h2 id=\"no-analytics\">No Analytics</h2>\n<p>I had plausible.io on my blog for a very brief period, but the analytics provided too much information for me. I just want to share what I write. I don&rsquo;t need graphs.</p>\n<p>If you want to use willow.camp for your blog and want analytics, let me know, and I can add a feature to let you insert the code you need.</p>\n<h2 id=\"accessibility\">Accessibility</h2>\n<p>I need to do another accessibility audit on willow.camp. I tried to ensure the HTML used aria labels that were clear and concise.</p>\n<p>I recently found <a href=\"https://github.com/thoughtbot/capybara_accessibility_audit\">CapybaraAccessibilityAudit</a>, which I&rsquo;ll use to audit the dashboard and public-facing pages.</p>\n<h2 id=\"about-page\">About Page</h2>\n<p>The &ldquo;about&rdquo; page is a bit of a hack. &ldquo;Pages&rdquo; and &ldquo;Posts&rdquo; are almost the same thing in the database. I used single table inheritance to create a single &ldquo;about&rdquo; page for each user. Every user always has one about page. If you delete your &ldquo;about&rdquo; page, a new draft page is created in the background.</p>\n<p>willow.camp doesn&rsquo;t use pages otherwise. Everyone gets one &ldquo;about&rdquo; page.</p>\n<h2 id=\"feeds-rss-atom-json\">Feeds: RSS, Atom, JSON</h2>\n<p>RSS and the <a href=\"https://indieweb.org/\">indie web</a> are having a comeback. I&rsquo;ve been reading RSS on <a href=\"https://feeeed.nateparrott.com/\">Feeeed</a> and <a href=\"https://feedbin.com/\">Feedbin</a> lately, so proper feeds are essential to willow.camp.</p>\n<p>My pet peeve with &ldquo;follow me on RSS&rdquo; links is that most of the time, you end up downloading a file when you click on the link. That&rsquo;s not helpful.</p>\n<p>I drew inspiration from <a href=\"https://subscribeopenly.net/\">Subscribe Openly</a> and created a helpful &ldquo;Subscribe&rdquo; page with clipboard helpers to copy the feed of your choice easily. Check it out: <a href=\"/subscribe\">enumerator.dev/subscribe</a>.</p>\n<h2 id=\"api-and-cli\">API and CLI</h2>\n<p>willow.camp comes with an <a href=\"https://willow.camp/docs\">API</a> and <a href=\"https://github.com/cassiascheffer/willow_camp_cli\">CLI tool</a> for managing content. I&rsquo;ve used the CLI to import and format my content from Ghost.</p>\n<p>The CLI converts most of the HTML from Ghost correctly, but leaves some extraneous markdown that requires manual cleanup.</p>\n<h2 id=\"todo-list\">Todo list</h2>\n<p>I have lots of things I still want to do on willow.camp. Here are a few things I have in mind:</p>\n<ul>\n<li>Write an <a href=\"https://obsidian.md/\">Obsidian</a> extension. I got Claude, Copilot, and Zed to build the CLI in Ruby. It does the job. However, what I&rsquo;d like is a full-featured Obsidian extension that allows me to write, edit, publish, and sync my blog directly within Obsidian.</li>\n<li>Re-implement syntax highlighting in code blocks. I think I messed up code syntax highlighting somewhere along the way. I want to fix this.</li>\n<li>Fix the CLI tool&rsquo;s gem publishing. Something borked here and I haven&rsquo;t had a chance to fix it. If you want to use the latest CLI, you can build it from the repo for now.</li>\n<li>Add a manual &ldquo;light&rdquo; and &ldquo;dark&rdquo; toggle to toggle between a user&rsquo;s preferred schemes. This wouldn&rsquo;t use <code>prefers-color-scheme</code> but would still allow light and dark mode.</li>\n<li>Disallow a light scheme to be paired with Dracula because Dracula lives in the dark.</li>\n</ul>\n<h3 id=\"new-features\">New Features</h3>\n<ul>\n<li>I love RSS, and I mostly love most RSS readers. But like a blogging platform, I&rsquo;ve had trouble finding one that fits me. I want to build an RSS reader functionality into willow.camp so you can read RSS in the same place you publish.</li>\n<li>Bookmarks! I love everything about <a href=\"https://raindrop.io/\">raindrop.io</a>, but I also like the challenge of building my own. So why not make willow.camp the super app of the indie web and add bookmarks? I&rsquo;m joking here. I&rsquo;d create a bookmarking feature for myself and hope that others will like it too.</li>\n<li>Maybe introduce distribution lists? I liked that people could get my posts by email on Ghost. I didn&rsquo;t want to see the open rates and graphs. I might re-introduce email distribution with a more straightforward UI.</li>\n</ul>\n<h2 id=\"thoughts-on-stimulus-hotwire-and-turbo\">Thoughts on Stimulus, Hotwire, and Turbo</h2>\n<p>Wow. I love this. Honestly, it makes me love developing web apps again. There is no heavy complexity between the frontend and backend. You don&rsquo;t need to shift mindsets to start writing in a frontend framework.</p>\n<p>I didn&rsquo;t expect to enjoy these features this much. However, I&rsquo;m back to writing HTML that maps directly to what I see in the browser, and a few extra bits of syntax in <code>data-attribute</code> fields make it a snappy and dynamic UI.</p>\n<h2 id=\"willowcamp-is-free\">willow.camp is Free</h2>\n<p><a href=\"https://willow.camp\">willow.camp</a> is free to use. If people find it useful, I might introduce a paid tier to cover hosting costs.</p>\n<p>If you want a willow.camp blog, you can sign up and try it out.</p>\n",
        "date_published": "2025-06-28T20:06:00+00:00",
        "url": "https://enumerator.dev/introducing-willow-camp/",
        "tags": ["turbo","stimulus","willow-camp","rails"]
      },
      {
        "id": "https://enumerator.dev/how-rack-timeout-keeps-your-app-alive/",
        "title": "How Rack::Timeout Keeps Your App Alive",
        "content_html": "<p><a href=\"https://github.com/zombocom/rack-timeout\">Rack::Timeout</a> is a library that monitors long-running requests in a Ruby app and kills them if they take too long. The default configuration in Rack::Timeout is to kill requests that take 15 seconds or longer.</p>\n<h2 id=\"why-requests-take-a-long-time\">Why Requests Take A Long Time?</h2>\n<p>The answer here will vary from one app to another. A frequent culprit is database transactions taking too long or outgoing requests to other resources hanging.</p>\n<h2 id=\"how-does-this-keep-my-app-alive-if-it-kills-things\">How Does This Keep My App Alive if it Kills Things?</h2>\n<p>In a typical Ruby app using Puma, you will have a few processes to manage a Ruby Thread collection. Rack::Timeout will send SIGTERM to a process if it breaches the timeout.</p>\n<p>Your Ruby app and Puma process will remain alive, but the stuck process will be terminated.</p>\n<p>Puma will notice that this process has terminated and will boot up a new one.</p>\n<h2 id=\"when-is-racktimeout-unsafe\">When is Rack::Timeout Unsafe?</h2>\n<p>Killing a running process is often unsafe. Fortunately, Rack::Timeout uses a &ldquo;polite&rdquo; kill message, SIGTERM, which requests that the process shut down and clean up its resources.</p>\n<p>SIGTERM is less severe than SIGKILL, but still poses risks.</p>\n<p>A Ruby thread could be in the middle of creating database records when it is killed, which would result in things being left in a broken state.</p>\n<h2 id=\"where-does-racktimeout-run-in-my-app\">Where Does Rack::Timeout Run in My App?</h2>\n<p>Rack::Timeout runs in Rack, an interface between web servers, like Puma, and Rails applications. Puma receives a request, hands it off to a thread, which passes it through Rack before it reaches your application code.</p>\n<p>When Rack handles the request, it monitors the request duration and kills the process if the request takes too long.</p>\n<p>Rack::Timeout keeps your application alive by killing hanging processes and allowing Puma to boot up a new process to handle the next request.</p>\n<pre><code class=\"language-mermaid\">graph TD\nClient[Client Requests] --&gt; LB[Load Balancer\nnginx/HAProxy]\nLB --&gt; Container1[Container 1\nRails App]\nLB --&gt; Container2[Container 2\nRails App]\nContainer1 --&gt; PumaServer1[Puma Server 1]\nContainer2 --&gt; PumaServer2[Puma Server 2]\nPumaServer1 --&gt; Worker1_1[Worker 1]\nPumaServer1 --&gt; Worker1_2[Worker 2]\nPumaServer2 --&gt; Worker2_1[Worker 1]\nPumaServer2 --&gt; Worker2_2[Worker 2]\nWorker1_1 --&gt; RackTimeout1_1[Rack::Timeout\nMiddleware]\nWorker1_2 --&gt; RackTimeout1_2[Rack::Timeout\nMiddleware]\nWorker2_1 --&gt; RackTimeout2_1[Rack::Timeout\nMiddleware]\nWorker2_2 --&gt; RackTimeout2_2[Rack::Timeout\nMiddleware]\nRackTimeout1_1 --&gt; Threads1_1[3 Threads\nThread Pool\n_timeout monitored per thread_]\nRackTimeout1_2 --&gt; Threads1_2[3 Threads\nThread Pool\n_timeout monitored per thread_]\nRackTimeout2_1 --&gt; Threads2_1[3 Threads\nThread Pool\n_timeout monitored per thread_]\nRackTimeout2_2 --&gt; Threads2_2[3 Threads\nThread Pool\n_timeout monitored per thread_]\nclassDef container fill:#e1f5fe\nclassDef worker fill:#f3e5f5\nclassDef thread fill:#e8f5e8\nclassDef loadbalancer fill:#fce4ec\nclassDef middleware fill:#fff3e0\nclass Container1,Container2 container\nclass Worker1_1,Worker1_2,Worker2_1,Worker2_2 worker\nclass Threads1_1,Threads1_2,Threads2_1,Threads2_2 thread\nclass LB loadbalancer\nclass RackTimeout1_1,RackTimeout1_2,RackTimeout2_1,RackTimeout2_2 middleware</code></pre>\n",
        "date_published": "2025-06-05T00:00:00+00:00",
        "url": "https://enumerator.dev/how-rack-timeout-keeps-your-app-alive/",
        "tags": ["performance","ruby","rails"]
      },
      {
        "id": "https://enumerator.dev/migrating-from-standalone-sidekiq-to-an-activejob-adapter/",
        "title": "Migrating from Standalone Sidekiq to an ActiveJob Adapter",
        "content_html": "<p>I paired on this problem with my coworker <a href=\"https://www.linkedin.com/in/hannah-yeates/\">Hannah Yeates</a>. The examples below are from this pairing session. Thanks for working on this together, Hannah!</p>\n<hr>\n<p>I am working on a project right now that uses Sidekiq without the ActiveJob adapter. This means that our Sidekiq jobs look something like this:</p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-rb\" data-lang=\"rb\"><span class=\"line\"><span class=\"cl\"><span class=\"k\">class</span> <span class=\"nc\">ExampleWorker</span>\n</span></span><span class=\"line\"><span class=\"cl\">  <span class=\"kp\">include</span> <span class=\"no\">Sidekiq</span><span class=\"o\">::</span><span class=\"no\">Worker</span>\n</span></span><span class=\"line\"><span class=\"cl\">\n</span></span><span class=\"line\"><span class=\"cl\">  <span class=\"k\">def</span> <span class=\"nf\">perform</span><span class=\"p\">(</span><span class=\"n\">arg</span><span class=\"p\">)</span>\n</span></span><span class=\"line\"><span class=\"cl\">    <span class=\"nb\">puts</span> <span class=\"s2\">&#34;Hello, the arg was </span><span class=\"si\">#{</span><span class=\"n\">arg</span><span class=\"si\">}</span><span class=\"s2\">&#34;</span>\n</span></span><span class=\"line\"><span class=\"cl\">  <span class=\"k\">end</span>\n</span></span><span class=\"line\"><span class=\"cl\"><span class=\"k\">end</span>\n</span></span></code></pre></div><p>And we call them like this:</p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-rb\" data-lang=\"rb\"><span class=\"line\"><span class=\"cl\"><span class=\"no\">ExampleWorker</span><span class=\"o\">.</span><span class=\"n\">perform_async</span><span class=\"p\">(</span><span class=\"s2\">&#34;a nice arg&#34;</span><span class=\"p\">)</span>\n</span></span></code></pre></div><p>And in dev they&rsquo;ll execute like this:</p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-rb\" data-lang=\"rb\"><span class=\"line\"><span class=\"cl\"><span class=\"mi\">2024</span><span class=\"o\">-</span><span class=\"mo\">01</span><span class=\"o\">-</span><span class=\"mi\">17</span><span class=\"ss\">T15</span><span class=\"p\">:</span><span class=\"mo\">05</span><span class=\"p\">:</span><span class=\"mi\">31</span><span class=\"o\">.</span><span class=\"mi\">455</span><span class=\"n\">Z</span> <span class=\"n\">pid</span><span class=\"o\">=</span><span class=\"mi\">45599</span> <span class=\"n\">tid</span><span class=\"o\">=</span><span class=\"mi\">18</span><span class=\"n\">gj</span>\n</span></span><span class=\"line\"><span class=\"cl\">  <span class=\"n\">class</span><span class=\"o\">=</span><span class=\"no\">TestWorker</span> \n</span></span><span class=\"line\"><span class=\"cl\">  <span class=\"n\">jid</span><span class=\"o\">=</span><span class=\"mi\">8</span><span class=\"n\">d32dcd88b1de2ed3a5419eb</span> \n</span></span><span class=\"line\"><span class=\"cl\">  <span class=\"ss\">INFO</span><span class=\"p\">:</span> <span class=\"n\">start</span>\n</span></span><span class=\"line\"><span class=\"cl\"><span class=\"no\">Hello</span><span class=\"p\">,</span> <span class=\"n\">the</span> <span class=\"n\">arg</span> <span class=\"n\">was</span> <span class=\"n\">foo</span>\n</span></span></code></pre></div><p>However, we want to switch over to <a href=\"https://github.com/bensheldon/good_job\">GoodJob</a> so that we have a transactionally safe background job processor. Our steps for doing this are:</p>\n<ol>\n<li>Set up Sidekiq as an ActiveJob adapter instead of using Sidekiq on its own.</li>\n<li>Install GoodJob and replace Sidekiq with GoodJob.</li>\n<li>Run Sidekiq and GoodJob in parallel so that Sidekiq finishes all the work in Redis.</li>\n<li>Shut down Sidekiq and remove it from the application code.</li>\n</ol>\n<p>This post is about step 1, making Sidekiq the ActiveJob adapter instead of using it on its own.</p>\n<h2 id=\"step-1--configure-activejob\">Step 1 : Configure ActiveJob</h2>\n<p>Our first step is to configure the application to use Sidekiq for ActiveJob</p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-rb\" data-lang=\"rb\"><span class=\"line\"><span class=\"cl\"><span class=\"c1\"># in application.rb</span>\n</span></span><span class=\"line\"><span class=\"cl\"><span class=\"n\">config</span><span class=\"o\">.</span><span class=\"n\">active_job</span><span class=\"o\">.</span><span class=\"n\">queue_adapter</span> <span class=\"o\">=</span> <span class=\"ss\">:sidekiq</span>\n</span></span></code></pre></div><p>Then we created an empty <code>ApplicationJob</code> (more on this later).</p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-rb\" data-lang=\"rb\"><span class=\"line\"><span class=\"cl\"><span class=\"c1\"># application_job.rb</span>\n</span></span><span class=\"line\"><span class=\"cl\"><span class=\"k\">class</span> <span class=\"nc\">ApplicationJob</span> <span class=\"o\">&lt;</span> <span class=\"no\">ActiveJob</span><span class=\"o\">::</span><span class=\"no\">Base</span>\n</span></span><span class=\"line\"><span class=\"cl\">  <span class=\"c1\"># leave this empty for now</span>\n</span></span><span class=\"line\"><span class=\"cl\"><span class=\"k\">end</span>\n</span></span></code></pre></div><h2 id=\"step-2-convert-workers-to-applicationjob\">Step 2: Convert Workers to ApplicationJob</h2>\n<p>Next we updated our jobs to inherit from application job:</p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-rb\" data-lang=\"rb\"><span class=\"line\"><span class=\"cl\"><span class=\"k\">class</span> <span class=\"nc\">ExampleWorker</span> <span class=\"o\">&lt;</span> <span class=\"no\">ApplicationJob</span> <span class=\"c1\"># Add this</span>\n</span></span><span class=\"line\"><span class=\"cl\">  <span class=\"c1\"># remove this: include Sidekiq::Worker</span>\n</span></span><span class=\"line\"><span class=\"cl\">\n</span></span><span class=\"line\"><span class=\"cl\">  <span class=\"c1\"># The rest of the code remains the same</span>\n</span></span><span class=\"line\"><span class=\"cl\"><span class=\"k\">end</span>\n</span></span></code></pre></div><p>And finally, we changed all our method calls from <code>ExampleWorker.perform_async(&quot;a nice arg&quot;)</code> to <code>ExampleWorker.perform_later(&quot;a nice arg&quot;)</code>.</p>\n<h2 id=\"step-3-see-what-breaks\">Step 3: See What Breaks</h2>\n<p>So far, so good! Next, we had a question: What happens when a job is enqueued by Sidekiq but processed by ActiveJob? To emulate this we:</p>\n<ol>\n<li>Booted a Rails console locally.</li>\n<li>Use the original code to enqueue a job.</li>\n<li>Modify the worker to use <code>ApplicationJob</code>.</li>\n<li>Boot Sidekiq locally and see what errors we get.</li>\n</ol>\n<p>In the real world, this would happen when the new code is deployed and there are jobs in the queue that were put there by Sidekiq. Here&rsquo;s the first error we got:</p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-fallback\" data-lang=\"fallback\"><span class=\"line\"><span class=\"cl\">WARN: NoMethodError: undefined method `jid=&#39; for #&lt;TestWorker:...&gt;\n</span></span></code></pre></div><p>We resolved this by creating an alias to <code>ActiveJob</code>&rsquo;s <code>provider_job_id=</code> and tested again. In the end we had three <code>NoMethodError</code> messages to resolve. We updated our <code>ApplicationJob</code> to respond to those methods.</p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-mysql\" data-lang=\"mysql\"><span class=\"line\"><span class=\"cl\"><span class=\"n\">class</span><span class=\"w\"> </span><span class=\"n\">ApplicationJob</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">  </span><span class=\"n\">def</span><span class=\"w\"> </span><span class=\"n\">jid</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"n\">arg</span><span class=\"p\">)</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">    </span><span class=\"n\">provider_job_id</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"n\">arg</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">  </span><span class=\"n\">end</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">  </span><span class=\"n\">def</span><span class=\"w\"> </span><span class=\"n\">jid</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">    </span><span class=\"n\">provider_job_id</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">  </span><span class=\"n\">end</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">  </span><span class=\"n\">def</span><span class=\"w\"> </span><span class=\"n\">bid</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"n\">arg</span><span class=\"p\">);</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">    </span><span class=\"c1\"># We are lucky to not need batching, so we let this return `nil`\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">  </span><span class=\"n\">end</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"n\">end</span><span class=\"w\">\n</span></span></span></code></pre></div><h2 id=\"wrapping-up\">Wrapping Up</h2>\n<p>Making the switch to <code>ActiveJob</code> was easier than we expected. We now have a backwards compatible way of moving from standalone Sidekiq to ActiveJob. Our next step is to swap out Sidekiq for GoodJob.</p>\n<p>If you are planning on using GoodJob, or Solid Queue, check out this article on making the switch once you&rsquo;re using ActiveJob:</p>\n<p><a href=\"https://kylekeesling.com/posts/2024/01/migrating-from-sidekiq-to-solid-queue\">Migrating from Sidekiq to Solid Queue | Kyle Keesling</a></p>\n",
        "date_published": "2024-01-17T00:00:00+00:00",
        "url": "https://enumerator.dev/migrating-from-standalone-sidekiq-to-an-activejob-adapter/",
        "tags": ["sidekiq","activejob","ruby","rails"]
      },
      {
        "id": "https://enumerator.dev/two-tools-for-diagnosing-slow-endpoints-in-rails/",
        "title": "Two Tools for Diagnosing Slow Endpoints in Rails",
        "content_html": "<h2 id=\"intro\">Intro</h2>\n<p>In general, I see two types of slow endpoints when I am doing performance work: endpoints that have bad code causing a slow response, and endpoints that have a bad query causing a slow response. This post will focus on endpoints that have bad code.</p>\n<p>Slow endpoints can be identified using an application performance monitor like NewRelic. These endpoints usually either have <a href=\"https://en.wikipedia.org/wiki/N%2B1_redundancy\">N + 1 queries</a>, or they spend lots of time in Ruby. You’ll see them in NewRelic, but if you want to hit an endpoint in real-time with production data, see the tip below about Rack MiniProfiler.</p>\n<h3 id=\"newrelic\">NewRelic</h3>\n<p>The transactions monitor is a good place to start. Pick a broad time range (7 days) and look at the “Transaction Traces” that New Relic has captured. If a transaction trace here includes a long query or lots of queries to the DB, it is likely a good transaction to look into.</p>\n<p>There will likely also be some obvious problem queries in the top “Most Time Consuming” transactions. Click through each transaction here and take a look at the transaction traces that NewRelic captured.</p>\n<p>The example below has two N + 1 problems! First you see that we hit Memcached 62 times, then we hit the relational database 47 times! Eeep! Looks like this is a good endpoint to work on.</p>\n<p><img\n    src=\"/images/two-tools-for-diagnosing-slow-endpoints-in-rails_hu_f6415f2018d4cd4e.webp\"\n    srcset=\"/images/two-tools-for-diagnosing-slow-endpoints-in-rails_hu_6721129506a4d72b.webp 576w, /images/two-tools-for-diagnosing-slow-endpoints-in-rails_hu_7a514320b2d268d8.webp 864w, /images/two-tools-for-diagnosing-slow-endpoints-in-rails_hu_f6415f2018d4cd4e.webp 978w\" sizes=\"(max-width: 36rem) 100vw, 36rem\"\n    width=\"978\"\n    height=\"367\"\n    loading=\"lazy\"\n    decoding=\"async\" alt=\"alt text\"></p>\n<p>“Most Time Consuming” is not a bad thing. If we have a really fast endpoint that is hit tens of thousands of time per minute, it is not really a problem. But if a relatively busy endpoint has a slow average response time, it likely is a problem!</p>\n<h3 id=\"rack-miniprofiler\">Rack MiniProfiler</h3>\n<p>Development and staging data can differ wildly from production data, which makes query performance differ wildly between the environments.</p>\n<p>Running Rack MiniProfiler in production gives you a real-time stack trace of live production data! Check out <a href=\"https://github.com/MiniProfiler/rack-mini-profiler#access-control-in-non-development-environments\">the Rack MiniProfiler docs on how to run it in a production environment</a></p>\n<p>I like to be able to selectively turn Rack MiniProfiler on and off, so I usually set it up so that you have to log in as an admin user and then you have to have turned it on for your session by adding <code>?rmp=on</code> to the first request.</p>\n<p>Once Rack MiniProfiler is turned on, you can hit one of the problem endpoints that you see in NewRelic and get more detailed information on what is slowing that request down.</p>\n<h3 id=\"4-possible-ways-to-resolve-slow-endpoints\">4 Possible Ways to Resolve Slow Endpoints</h3>\n<p>This is a non-exhaustive list of possible solutions.</p>\n<ul>\n<li>Find an N + 1? Solve it! You’ll see these in either NewRelic or in Rack Mini Profiler. Solving an N + 1 can mean either eager-loading data using <a href=\"https://devdocs.io/rails~5.2/activerecord/querymethods#method-i-includes\"><code>includes</code></a>, or loading the necessary data in a separate query. It’s usually best to start with using <code>includes</code>, and if causes performance issues, try using separate queries.</li>\n<li>Add or improve caching\n<ul>\n<li>Can the data being queried be cached, or can you use Russian Doll Caching to cache view partials?</li>\n<li>Can you improve the cache usage by selecting multiple records from the cache rather than doing N + 1 queries to the cache? <a href=\"https://devdocs.io/rails~5.2/activesupport/cache/store#method-i-fetch_multi\">Check out the docs on <code>select_multi</code></a> to see how we might resolve the 62 queries to the cache in the example above.</li>\n</ul>\n</li>\n<li>Here&rsquo;s an odd one that you&rsquo;ll find in older applications: Do you even need to display this data? Or do you to display this data with the resolution you are showing? Sometimes older pages slow down because we are trying to show a count of all data from the beginning of time!</li>\n</ul>\n<p>Remember, performance work can take a few passes to get it right. Try one strategy at a time, deploy, and monitor until your response time is back to an acceptable level.</p>\n<p>I&rsquo;d love to hear your favourite strategies for tackling slow endpoints.</p>\n",
        "date_published": "2021-03-29T12:00:00+00:00",
        "url": "https://enumerator.dev/two-tools-for-diagnosing-slow-endpoints-in-rails/",
        "tags": ["performance","ruby","rails"]
      }
  ]
}
