<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://elijahrogers.dev/elijahrogers.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://elijahrogers.dev/elijahrogers.github.io/" rel="alternate" type="text/html" /><updated>2026-06-24T06:32:32+00:00</updated><id>https://elijahrogers.dev/elijahrogers.github.io/feed.xml</id><title type="html">Elijah Rogers</title><subtitle>Full Stack Ruby on Rails Developer</subtitle><entry><title type="html">bundle_alphabetically</title><link href="https://elijahrogers.dev/elijahrogers.github.io/2026/01/01/bundle-alphabetically.html" rel="alternate" type="text/html" title="bundle_alphabetically" /><published>2026-01-01T01:31:00+00:00</published><updated>2026-01-01T01:31:00+00:00</updated><id>https://elijahrogers.dev/elijahrogers.github.io/2026/01/01/bundle-alphabetically</id><content type="html" xml:base="https://elijahrogers.dev/elijahrogers.github.io/2026/01/01/bundle-alphabetically.html"><![CDATA[<p>You know what’s nice? When things go where they’re supposed to. Mise en place. I know keeping everything neat and tidy is an uphill battle but a little hygiene goes a long way.</p>

<p>This is especially true with the oft neglected Gemfile. Dependencies are important and they shouldn’t be shoved in a junk drawer. Besides keeping around commented out gems, there is one sin that seems to be ubiquitous: keeping gems out of order.</p>

<!--more-->

<p>“I wasn’t aware that there was a correct order” you might be thinking but, of course, that’s wrong. Clearly gems should be in alphabetical order.</p>

<p>Why? Well, just look at this mess:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">source</span> <span class="s2">"https://rubygems.org"</span>
<span class="n">git_source</span><span class="p">(</span><span class="ss">:github</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">repo</span><span class="o">|</span> <span class="s2">"https://github.com/</span><span class="si">#{</span><span class="n">repo</span><span class="si">}</span><span class="s2">.git"</span> <span class="p">}</span>

<span class="n">ruby</span> <span class="s2">"3.2.2"</span>

<span class="n">gem</span> <span class="s2">"rails"</span><span class="p">,</span> <span class="s2">"~&gt; 7.1.5"</span>
<span class="n">gem</span> <span class="s2">"sprockets-rails"</span>
<span class="n">gem</span> <span class="s1">'pg'</span>
<span class="c1"># Build JSON APIs with ease [https://github.com/rails/jbuilder]</span>
<span class="n">gem</span> <span class="s2">"jbuilder"</span>
<span class="n">gem</span> <span class="s1">'devise'</span>
<span class="n">gem</span> <span class="s2">"puma"</span><span class="p">,</span> <span class="s2">"~&gt; 6.0"</span>
<span class="n">gem</span> <span class="s1">'jsbundling-rails'</span>
<span class="n">gem</span> <span class="s2">"turbo-rails"</span>
<span class="n">gem</span> <span class="s2">"stimulus-rails"</span>
<span class="n">gem</span> <span class="s2">"jbuilder"</span>
<span class="n">gem</span> <span class="s2">"redis"</span><span class="p">,</span> <span class="s2">"~&gt; 4.0"</span>

<span class="c1"># Windows does not include zoneinfo files, so bundle the tzinfo-data gem</span>
<span class="n">gem</span> <span class="s2">"tzinfo-data"</span><span class="p">,</span> <span class="ss">platforms: </span><span class="sx">%i[ mingw mswin x64_mingw jruby ]</span>

<span class="c1"># Reduces boot times through caching; required in config/boot.rb</span>
<span class="n">gem</span> <span class="s2">"bootsnap"</span><span class="p">,</span> <span class="ss">require: </span><span class="kp">false</span>
<span class="n">gem</span> <span class="s2">"image_processing"</span><span class="p">,</span> <span class="s2">"~&gt; 1.2"</span>
<span class="n">gem</span> <span class="s2">"good_job"</span><span class="p">,</span> <span class="s2">"~&gt; 3.23"</span>
<span class="n">gem</span> <span class="s2">"cssbundling-rails"</span><span class="p">,</span> <span class="s2">"~&gt; 1.4"</span>
<span class="n">gem</span> <span class="s2">"font-awesome-sass"</span><span class="p">,</span> <span class="s2">"~&gt; 6.5"</span>
<span class="n">gem</span> <span class="s2">"fuzzy_match"</span><span class="p">,</span> <span class="s2">"~&gt; 2.1"</span>
<span class="n">gem</span> <span class="s1">'google-cloud-storage'</span>
<span class="n">gem</span> <span class="s2">"kamal"</span><span class="p">,</span> <span class="s2">"~&gt; 2.2"</span>
</code></pre></div></div>

<p>Now look at this pristine Gemfile:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">source</span> <span class="s2">"https://rubygems.org"</span>
<span class="n">git_source</span><span class="p">(</span><span class="ss">:github</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">repo</span><span class="o">|</span> <span class="s2">"https://github.com/</span><span class="si">#{</span><span class="n">repo</span><span class="si">}</span><span class="s2">.git"</span> <span class="p">}</span>

<span class="n">ruby</span> <span class="s2">"3.2.2"</span>

<span class="c1"># Reduces boot times through caching; required in config/boot.rb</span>
<span class="n">gem</span> <span class="s2">"bootsnap"</span><span class="p">,</span> <span class="ss">require: </span><span class="kp">false</span>
<span class="n">gem</span> <span class="s2">"cssbundling-rails"</span><span class="p">,</span> <span class="s2">"~&gt; 1.4"</span>
<span class="n">gem</span> <span class="s1">'devise'</span>
<span class="n">gem</span> <span class="s2">"font-awesome-sass"</span><span class="p">,</span> <span class="s2">"~&gt; 6.5"</span>
<span class="n">gem</span> <span class="s2">"fuzzy_match"</span><span class="p">,</span> <span class="s2">"~&gt; 2.1"</span>
<span class="n">gem</span> <span class="s2">"good_job"</span><span class="p">,</span> <span class="s2">"~&gt; 3.23"</span>
<span class="n">gem</span> <span class="s1">'google-cloud-storage'</span>
<span class="n">gem</span> <span class="s2">"image_processing"</span><span class="p">,</span> <span class="s2">"~&gt; 1.2"</span>

<span class="c1"># Build JSON APIs with ease [https://github.com/rails/jbuilder]</span>
<span class="n">gem</span> <span class="s2">"jbuilder"</span>
<span class="n">gem</span> <span class="s1">'jsbundling-rails'</span>
<span class="n">gem</span> <span class="s2">"kamal"</span><span class="p">,</span> <span class="s2">"~&gt; 2.2"</span>
<span class="n">gem</span> <span class="s1">'pg'</span>
<span class="n">gem</span> <span class="s2">"puma"</span><span class="p">,</span> <span class="s2">"~&gt; 6.0"</span>
<span class="n">gem</span> <span class="s2">"rails"</span><span class="p">,</span> <span class="s2">"~&gt; 7.1.5"</span>
<span class="n">gem</span> <span class="s2">"redis"</span><span class="p">,</span> <span class="s2">"~&gt; 4.0"</span>
<span class="n">gem</span> <span class="s2">"sprockets-rails"</span>
<span class="n">gem</span> <span class="s2">"stimulus-rails"</span>
<span class="n">gem</span> <span class="s2">"turbo-rails"</span>

<span class="c1"># Windows does not include zoneinfo files, so bundle the tzinfo-data gem</span>
<span class="n">gem</span> <span class="s2">"tzinfo-data"</span><span class="p">,</span> <span class="ss">platforms: </span><span class="sx">%i[ mingw mswin x64_mingw jruby ]</span>

</code></pre></div></div>

<p>Which one sparks joy? Which one makes you excited to work on the project? I could argue that this makes the gems “easily scannable” but the reality is that it’s just aesthetically pleasing. It feels nice.</p>

<p>The trouble is that maintaining this order is annoying. I personally love to just <code class="language-plaintext highlighter-rouge">bundle add gem</code> and install but this quickly becomes messy. Why should I have to eschew the niceties of bundler to keep things looking good?</p>

<p>Of course, this class of problem has been solved many times over. We have all kinds of linters and formatters that enforce consistent styles automatically (shout out RuboCop) but this seems like the responsibility of bundler.</p>

<p>So I built a bundler plugin. It’s not polished but it should work for most setups. Once installed, it hooks into bundler’s install command to keep everything where it belongs.</p>

<p>Give it a try and keep your Gemfile pretty: <code class="language-plaintext highlighter-rouge">bundle plugin install bundle_alphabetically</code></p>

<p>Or check out the source code if you’re into that kind of thing: <a href="https://github.com/elijahrogers/bundle_alphabetically">bundle_alphabetically</a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[You know what’s nice? When things go where they’re supposed to. Mise en place. I know keeping everything neat and tidy is an uphill battle but a little hygiene goes a long way. This is especially true with the oft neglected Gemfile. Dependencies are important and they shouldn’t be shoved in a junk drawer. Besides keeping around commented out gems, there is one sin that seems to be ubiquitous: keeping gems out of order.]]></summary></entry><entry><title type="html">I spent 200 hours building a Postgres chat client</title><link href="https://elijahrogers.dev/elijahrogers.github.io/2025/10/03/i-spent-200-hours-building-a-postgres-chat-client.html" rel="alternate" type="text/html" title="I spent 200 hours building a Postgres chat client" /><published>2025-10-03T22:19:00+00:00</published><updated>2025-10-03T22:19:00+00:00</updated><id>https://elijahrogers.dev/elijahrogers.github.io/2025/10/03/i-spent-200-hours-building-a-postgres-chat-client</id><content type="html" xml:base="https://elijahrogers.dev/elijahrogers.github.io/2025/10/03/i-spent-200-hours-building-a-postgres-chat-client.html"><![CDATA[<p>“Can you tell me how many…”</p>

<p>“Can you build me a report that shows…”</p>

<p>“Can you run another report for August…”</p>

<p>I know I’m not the only one who’s received these requests. Most of the time they’re not even sure what they want. Usually the answer to their question won’t actually change anything. Does it matter whether we have 1000 or 1001 customers? Does it change our priorities? Our strategy?</p>

<!--more-->

<p>I can’t really blame them though. Everyone has hunches they want to follow without any real outcome in mind. I do the same thing myself. The problem is that their curiosity is breaking my flow.</p>

<p>The answer to my problem was obvious: get out of the way. If I could build a way for non-technical users to access the same data then there wouldn’t be any reason to interrupt me. I could set it up once and let them indulge their every guilty curiosity.</p>

<h2 id="architecture">Architecture</h2>

<p>We can probably just skip this, right? It should be this easy:</p>

<p><img src="https://elijahrogers.dev/assets/images/so_easy.png" class="image" style="max-height: 700px;" /></p>

<p>Well, actually, no. It turns out that every great prompt has one critical piece: context. In this case, the database schema. Postgres’s <code class="language-plaintext highlighter-rouge">pg_dump</code> can be used to generate a nice structured SQL schema that LLMs are already familiar with (I’m sure there are more than a few examples in their training data).</p>

<p>The problem is <code class="language-plaintext highlighter-rouge">pg_dump</code> is a CLI tool that we can’t invoke with a database connection. Unless we want to have a user manually dump their schema every time it changes and paste it into the prompt, this won’t work. Of course we could just build the schema by querying the database itself.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">fetch_tables</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span>
  <span class="n">conn</span><span class="p">.</span><span class="nf">exec</span><span class="p">(</span><span class="o">&lt;&lt;~</span><span class="no">SQL</span><span class="p">)</span><span class="sh">
    SELECT schemaname, tablename, tableowner
    FROM pg_tables
    WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
    AND schemaname NOT LIKE 'pg_temp%'
    AND schemaname NOT LIKE 'pg_toast%'
    ORDER BY schemaname, tablename;
</span><span class="no">  SQL</span>
<span class="k">end</span>
</code></pre></div></div>

<p>First, fetch all the tables, indexes, primary keys, foreign keys, and the Postgres version. Then fetch the columns for each table and format it just like <code class="language-plaintext highlighter-rouge">pg_dump</code> would. This took a while to get right but with enough SELECT statements we can make it work.</p>

<p>I can already hear somebody screaming though: “Okay but there’s no way you should be giving an LLM access to the production database!” Agreed. That’s a terrible idea. Unless…</p>

<h2 id="guardrails">Guardrails</h2>

<p>By far the biggest objection to doing something like this is that <a href="https://x.com/jasonlk/status/1946065483653910889">LLMs don’t have a pristine track record when it comes to not wiping out production data</a>.</p>

<p><img src="https://elijahrogers.dev/assets/images/deleting_prod.png" class="image" style="max-height: 700px;" /></p>

<p>Really this is an <a href="https://www.reddit.com/r/cscareerquestions/comments/6ez8ag/accidentally_destroyed_production_database_on/">age</a> <a href="https://www.reddit.com/r/SQL/comments/dq40a3/help_deleted_a_table_in_production_seriously/">old</a> <a href="https://www.reddit.com/r/programming/comments/64idzl/digitalocean_accidentally_deleted_production/">problem</a> remastered for modern audiences. Since this is a problem that people have had before, that means there are existing solutions.</p>

<blockquote>
  <p>PROMPT: Pls don’t delete anything.</p>
</blockquote>

<p>Asking nicely doesn’t hurt but this is basically thoughts and prayers for your data. Luckily, Postgres has a tidy solution for this: read-only transactions.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">BEGIN</span> <span class="n">TRANSACTION</span> <span class="k">READ</span> <span class="k">ONLY</span><span class="p">;</span>
</code></pre></div></div>

<p>I know it’s not perfect. A malicious user could probably bypass this by committing the transaction early and committing a second transaction that’s destructive. But the user isn’t malicious. They’re on your team (presumably).</p>

<p>In any case, I think there’s a much more credible threat: resource exhaustion. This is also a problem that’s existed since databases were created so we have the tools to solve it. Setting a connection timeout is a simple way to prevent the LLM from hogging all of the resources. I also found this aptly named <a href="https://github.com/mperham/connection_pool">connection_pool</a> gem that I configured to limit the number of concurrent connections to the database and reap idle ones.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">connection_pool_for</span><span class="p">(</span><span class="n">database</span><span class="p">)</span>
  <span class="vi">@mutex</span><span class="p">.</span><span class="nf">synchronize</span> <span class="k">do</span>
    <span class="vi">@connection_pools</span><span class="p">[</span><span class="n">database</span><span class="p">.</span><span class="nf">id</span><span class="p">]</span> <span class="o">||=</span> <span class="n">create_connection_pool</span><span class="p">(</span><span class="n">database</span><span class="p">)</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="k">def</span> <span class="nf">create_connection_pool</span><span class="p">(</span><span class="n">database</span><span class="p">)</span>
  <span class="no">ConnectionPool</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span>
    <span class="ss">size: </span><span class="no">POOL_SIZE</span><span class="p">,</span>
    <span class="ss">timeout: </span><span class="no">POOL_TIMEOUT</span><span class="p">,</span>
    <span class="ss">check: </span><span class="o">-&gt;</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span> <span class="p">{</span> <span class="n">validate_connection</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span> <span class="p">},</span>
    <span class="ss">reaper: </span><span class="kp">true</span><span class="p">,</span>
    <span class="ss">reaper_interval: </span><span class="mi">60</span>  <span class="c1"># Check for stale connections every 60 seconds</span>
  <span class="p">)</span> <span class="p">{</span> <span class="n">establish_connection</span><span class="p">(</span><span class="n">database</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>

<span class="k">def</span> <span class="nf">validate_connection</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span>
  <span class="n">conn</span><span class="p">.</span><span class="nf">exec</span><span class="p">(</span><span class="s2">"SELECT 1"</span><span class="p">)</span>
  <span class="kp">true</span>
<span class="k">rescue</span> <span class="no">PG</span><span class="o">::</span><span class="no">Error</span> <span class="o">=&gt;</span> <span class="n">e</span>
  <span class="no">Rails</span><span class="p">.</span><span class="nf">logger</span><span class="p">.</span><span class="nf">warn</span><span class="p">(</span><span class="s2">"Connection validation failed: </span><span class="si">#{</span><span class="n">e</span><span class="p">.</span><span class="nf">message</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
  <span class="kp">false</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Since the connections needed to be shared between users from the same organization, there was the added complexity of managing shared resources across threads. A dedicated <code class="language-plaintext highlighter-rouge">DatabaseConnectionManager</code> class with a mutex was enough to handle this. Would this scale to thousands of connections? No, but premature optimization is a temptation best avoided.</p>

<h2 id="improving-the-results">Improving the results</h2>

<p>Look, if you guessed that language models trained on the entire corpus of human knowledge with boundless resources are good at turning English sentences into SQL statements you’d be correct. For all their shortcomings, this sort of task is something they’re actually pretty good at. Not perfect, but at least as good as a junior developer. They still make mistakes though.</p>

<p>For more complex queries I found a little encouragement to be helpful. For some reason the models tried to hero shot queries without taking advantage of generating intermediate results. Anecdotally, this seemed to help quite a bit:</p>

<blockquote>
  <p>If you need to join tables, do so. If you need to use a CTE to generate intermediate results, do so.</p>
</blockquote>

<p>It also shouldn’t be surprising that providing tools with explicit schema is critical to avoiding the response preamble. This was not the case with providing the schema, however. When asked to generate an answer using SQL the model will reasonably and without fail invoke the <code class="language-plaintext highlighter-rouge">get_schema</code> tool. This just slows down the response and with prompt caching it’s actually detrimental.</p>

<h2 id="the-result">The Result</h2>

<p>I won’t drone on about all the other hurdles there were to clear (like handling streaming JSON responses with Hotwire updates) but I will say that creating a “ChatGPT wrapper” was more involved than I thought it would be. It turns out that building software with ✨AI✨ is just as fraught with pitfalls as building normal software.</p>

<p>Feel free to <a href="https://mahoganychat.com">try it out</a> if you want. Let me know if you’re able to break anything.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[“Can you tell me how many…” “Can you build me a report that shows…” “Can you run another report for August…” I know I’m not the only one who’s received these requests. Most of the time they’re not even sure what they want. Usually the answer to their question won’t actually change anything. Does it matter whether we have 1000 or 1001 customers? Does it change our priorities? Our strategy?]]></summary></entry><entry><title type="html">Postmark Gaslit Me</title><link href="https://elijahrogers.dev/elijahrogers.github.io/2025/10/01/postmark-gaslit-me.html" rel="alternate" type="text/html" title="Postmark Gaslit Me" /><published>2025-10-01T16:18:00+00:00</published><updated>2025-10-01T16:18:00+00:00</updated><id>https://elijahrogers.dev/elijahrogers.github.io/2025/10/01/postmark-gaslit-me</id><content type="html" xml:base="https://elijahrogers.dev/elijahrogers.github.io/2025/10/01/postmark-gaslit-me.html"><![CDATA[<p>RTFM. I’m sure you’ve heard it before. Usually it serves me well. When something isn’t working it’s best to start from the assumption that you’ve overlooked something simple.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;</span> <span class="no">TestMailer</span><span class="p">.</span><span class="nf">hello</span><span class="p">.</span><span class="nf">deliver_now</span>
<span class="o">=&gt;</span> <span class="kp">true</span>
</code></pre></div></div>

<p>This was my assumption when I ran into this issue with Postmark. A 200 response but no email sent. If Postmark said the message was sent then it must be somewhere, right? I tried different streams, used different to and from addresses, triple checked the dashboard but my messages were nowhere to be found.</p>

<!--more-->

<p><img src="https://elijahrogers.dev/assets/images/unused_streams.png" alt="Unused Streams" style="margin: 2rem auto;" /></p>

<p>Maybe the docs were outdated? I dug into the source code for the Postmark gem and found nothing.<br />
Maybe it was an ActionMailer configuration issue? I read the docs and checked that everything was set up exactly as it should be.</p>

<p>Finally, I decided the best option was to simplify. cURL always works right?</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">MESSAGE_ID</span><span class="o">=</span><span class="si">$(</span>curl <span class="s2">"https://api.postmarkapp.com/email"</span> <span class="se">\</span>
  <span class="nt">-X</span> POST <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Accept: application/json"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"X-Postmark-Server-Token: </span><span class="nv">$POSTMARK_API_TOKEN</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{
    "From": "hello@mahoganychat.com",
    "To": "hello@mahoganychat.com",
    "Subject": "Hello from Postmark",
    "HtmlBody": "Hello dear Postmark user.",
    "MessageStream": "outbound"
    }'</span> <span class="se">\</span>
  | jq <span class="nt">-r</span> <span class="s1">'.MessageID'</span><span class="si">)</span><span class="p">;</span> <span class="nb">echo</span> <span class="s2">"MessageID: </span><span class="nv">$MESSAGE_ID</span><span class="s2">"</span><span class="p">;</span> <span class="se">\</span>
  curl <span class="s2">"https://api.postmarkapp.com/messages/outbound/</span><span class="nv">$MESSAGE_ID</span><span class="s2">/details"</span> <span class="nt">-s</span> <span class="nt">-H</span> <span class="s2">"Accept: application/json"</span> <span class="nt">-H</span> <span class="s2">"X-Postmark-Server-Token: </span><span class="nv">$POSTMARK_API_TOKEN</span><span class="s2">"</span>

<span class="o">=&gt;</span> MessageID: 1c075232-1f9a-4b99-bd79-7be6cc3a2499
<span class="o">=&gt;</span> ErrorCode 701: Message doesn’t exist
</code></pre></div></div>

<p>Wrong. The message you created doesn’t exist. Huh?</p>

<p>After fighting this for hours I caved and bugged support. To their credit, they resolved it immediately:</p>

<blockquote>

  <p>Hi Elijah,</p>

  <p>Thanks for reaching out and happy to help here.</p>

  <p>Your Postmark account had been inactive for a handful of months, not sending out any outbound messages.</p>

  <p>As a measure to help prevent abuse from being sent from the account, <strong>we paused sending and queued any new messages.</strong></p>

  <p>To help here, I’ve now gone ahead and restored sending to your Postmark account. Any queued messages have now been sent out.</p>
</blockquote>

<p>Surely I just missed something in the dashboard that could’ve saved me the hassle.</p>

<blockquote>
  <p>Just curious, did I miss something in Postmark that shows this status?</p>
</blockquote>

<p>Nope.</p>

<blockquote>
  <p>Hi Elijah –
No you did not miss anything! We are working on updating this so you and other customers are informed in the future about inactive accounts. It’s high on our list as something to enable. Apologies for the confusion in the meantime!</p>
</blockquote>

<p>I figured it’s worth documenting in case anyone else runs into this issue. Sometimes reading the manual isn’t enough. Sometimes the API gaslights you.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[RTFM. I’m sure you’ve heard it before. Usually it serves me well. When something isn’t working it’s best to start from the assumption that you’ve overlooked something simple. &gt;&gt; TestMailer.hello.deliver_now =&gt; true This was my assumption when I ran into this issue with Postmark. A 200 response but no email sent. If Postmark said the message was sent then it must be somewhere, right? I tried different streams, used different to and from addresses, triple checked the dashboard but my messages were nowhere to be found.]]></summary></entry><entry><title type="html">Migrating to Kamal</title><link href="https://elijahrogers.dev/elijahrogers.github.io/2024/12/01/migrating-to-kamal.html" rel="alternate" type="text/html" title="Migrating to Kamal" /><published>2024-12-01T03:22:00+00:00</published><updated>2024-12-01T03:22:00+00:00</updated><id>https://elijahrogers.dev/elijahrogers.github.io/2024/12/01/migrating-to-kamal</id><content type="html" xml:base="https://elijahrogers.dev/elijahrogers.github.io/2024/12/01/migrating-to-kamal.html"><![CDATA[<p>When I created SnippetStash I did some research on the easiest way to deploy a new Rails app. The default answer used to be Heroku but times have changed.
Based on my research, Heroku had become expensive and there were a few viable alternatives. Many suggested Fly.io or Render with various tradeoffs.</p>

<p>I decided that Render would be the easiest to set up, and automatic deploys were ideal for a hobby project. This was mostly true but I ran into some issues.</p>

<!--more-->

<blockquote>
  <p>“We do these things not because they are easy but because we thought they would be easy.”</p>
</blockquote>

<p>Generating a <code class="language-plaintext highlighter-rouge">render.yaml</code> file and setting up a basic Postgres database was straightforward. Pleasant almost. The issue was setting up background workers. This required a separate service with a custom Dockerfile.</p>

<p>Despite my limited experience with Docker, I think this would have been simple enough to figure out. There are a lot of base images available for common setups and this is more or less a solved problem.</p>

<p>Unfortunately, I also needed a working Python environment. This meant I needed to either</p>

<ol>
  <li>Start from a base Python image and install Ruby/Rails or</li>
  <li>Start from a base Ruby image and install Python.</li>
</ol>

<p>Neither of these options seemed great but I figured I’d have better luck grafting a Rails environment onto a Python image than vice versa. I still think this was right approach but the end result was not what I hoped for.</p>

<p>Instead of a simple deploy setup, I had a two YAML files (one in a proprietary format) and three different services. It was also expensive for the minimal amount of compute I was using. It’s not the end of the world but ~$250/year for a hobby project running a combined ~1 vCPU and less than 2GB of memory seems excessive.</p>

<p>Of course, there was also the backup solution: Kamal. I had neglected this path initially because I thought it was going to be more difficult to setup.</p>

<p>While it also had a learning curve, Kamal wasn’t any harder to figure out than Render and the flexibility is amazing. I was reminded of something DHH himself said on a <a href="https://www.youtube.com/watch?v=tWduT9ygUQ4">recent podcast</a></p>

<blockquote>
  <p>“It is so easy in your quest to try to completely erase an area of complexity that the solution you come up with eventually becomes more complicated. […]
because if you’re not willing to understand that [inherent complexity], you’re going to be forced into understanding essentially the same thing, wrapped in usually a commercial package.”</p>
</blockquote>

<p>In the end, Kamal will save me almost $200/year and I’ll be able to move providers whenever I choose. Win-win.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[When I created SnippetStash I did some research on the easiest way to deploy a new Rails app. The default answer used to be Heroku but times have changed. Based on my research, Heroku had become expensive and there were a few viable alternatives. Many suggested Fly.io or Render with various tradeoffs. I decided that Render would be the easiest to set up, and automatic deploys were ideal for a hobby project. This was mostly true but I ran into some issues.]]></summary></entry><entry><title type="html">Sleeping Policeman</title><link href="https://elijahrogers.dev/elijahrogers.github.io/2024/08/29/sleeping-policeman.html" rel="alternate" type="text/html" title="Sleeping Policeman" /><published>2024-08-29T15:17:00+00:00</published><updated>2024-08-29T15:17:00+00:00</updated><id>https://elijahrogers.dev/elijahrogers.github.io/2024/08/29/sleeping-policeman</id><content type="html" xml:base="https://elijahrogers.dev/elijahrogers.github.io/2024/08/29/sleeping-policeman.html"><![CDATA[<h2 id="the-problem">The Problem</h2>

<p>There are a plethora of articles and discussion on the pernicious effects of news feeds. They are so pervasive now that it’s easy to forget they’re even there. But time and time again they quietly suck you into a tightly controlled feedback loop design to “maximize user engagement.”</p>

<p>Limiting their effects can be difficult. Designed and perfected through millions of man hours over the last decade or so, these infinitely scrolling, attention draining nightmares are so effective at pulling you in that you often waste time - sometimes hours - before waking up from your daze of consumption.</p>

<!--more-->

<p>The only 100% effective way to avoid their pull is to avoid them altogether but who wants to do that? For all their downsides, there are plenty of times where the time on them is well spent. Limiting screen time might work for some people but I’ve found it to be broadly ineffective. Moderation is difficult to achieve for the same reason that people develop gambling addictions - the incentives are not aligned and the system is designed in an adverserial way.</p>

<h2 id="the-solution">The Solution</h2>

<p><img src="https://elijahrogers.dev/assets/images/load_time.jpg" alt="Description of the image" style="margin: 2rem auto;" /></p>

<p>Of course, the real way to beat the feed is to use its own tactics against it. There are several attack vectors here but the simplest is speed. Anyone who’s optimized for user engagement would tell you that speed is key - when things slow down the feedback loop breaks down and users quickly lose interest.</p>

<p>You may have even noticed this yourself if you’ve even used slow public Wifi. Below a certain speed, using the internet becomes boring. You can find a number of different statistics on this from various website monitoring, SEO, and ecommerce companies. Gooogle (ostensibly an authoratative source) estimates that an increase in the loading time from <a href="https://www.thinkwithgoogle.com/marketing-strategies/app-and-mobile/page-load-time-statistics/">1 to 3 seconds increases the probability of a bounce by 32%</a>.</p>

<p><img src="https://elijahrogers.dev/assets/images/sleeping_policeman.png" alt="Description of the image" style="float: left; margin: 2rem;max-width: 40%;" /></p>

<p>Whatever source you use though the relationship is the same - load times and bounce rates are strongly positively correlated. This means that artificially increasing the load time of a news feed over time should slowly ramp up the probability that you stop scrolling.</p>

<p>To test my theory I built Sleeping Policeman. It’s a gentle speed bump for the feed. By counting the number of requests and gradually adding a delay to each subsequent request it slow nudges you in the right direction. It’s just a tool to help you get back the time that the feed has taken from you.</p>

<p>There’s no lockout, no paternalism, no admonition for wasting time. Force is rarely effective for influencing the behaviour of adults with autonomy and often ilicits pushback. You can turn it off at any time if you like with a simple toggle. It’s your decision.</p>

<p>You can try out the extension right now for free if you’re using <a href="https://addons.mozilla.org/en-US/firefox/addon/sleeping-policeman/">Firefox</a> or see the <a href="https://github.com/elijahrogers/sleeping_policeman">source code</a> for yourself.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[The Problem There are a plethora of articles and discussion on the pernicious effects of news feeds. They are so pervasive now that it’s easy to forget they’re even there. But time and time again they quietly suck you into a tightly controlled feedback loop design to “maximize user engagement.” Limiting their effects can be difficult. Designed and perfected through millions of man hours over the last decade or so, these infinitely scrolling, attention draining nightmares are so effective at pulling you in that you often waste time - sometimes hours - before waking up from your daze of consumption.]]></summary></entry><entry><title type="html">SnippetStash: Extract highlights from photos and view them in context</title><link href="https://elijahrogers.dev/elijahrogers.github.io/2024/07/26/snippetstash-extract-highlights-from-photos-and-view-them-in-context.html" rel="alternate" type="text/html" title="SnippetStash: Extract highlights from photos and view them in context" /><published>2024-07-26T16:18:00+00:00</published><updated>2024-07-26T16:18:00+00:00</updated><id>https://elijahrogers.dev/elijahrogers.github.io/2024/07/26/snippetstash-extract-highlights-from-photos-and-view-them-in-context</id><content type="html" xml:base="https://elijahrogers.dev/elijahrogers.github.io/2024/07/26/snippetstash-extract-highlights-from-photos-and-view-them-in-context.html"><![CDATA[<p>I’m not sure exactly where I picked it up but I have a habit of highlighting books that I read. It’s easy to forget some key insights after reading a book and highlighting provides a way to mark something that I want to revisit. The issue is I don’t revist. At best I might remember some connection a few months later and flip through the entire book looking for the section I was thinking of.</p>

<p>After repeating this process several times it was clear that I was wasting my time. This shouldn’t be so difficult. I realized that if I could digitize these highlights I could search, organize, and easily reference them whenever I wanted - No more flipping pages! I figured I should be able to build an app where I submit a photo and the text is OCR’d and any highlighted texts are extracted (that is at least once I learned the term OCR).</p>

<!--more-->

<p>I knew I needed to begin by validating the key feature first before building an entire app. I built a small python script to perform some preprocessing on an image and OCR it using <code class="language-plaintext highlighter-rouge">opencv</code> to test it out. The basic process was:</p>

<ol>
  <li>Find the highlighted areas using some assumptions about the range of HSV values for a typical highlighter</li>
  <li>OCR the text</li>
  <li>Find any overlap between the bounding boxes of the highlights and text</li>
  <li>Reconstruct the highlighted parts into strings with some fuzzing to account for inconsistent highlighting</li>
</ol>

<p>Unfortunately, the initial results of step 2 were bad. Discouragingly bad:</p>

<blockquote>
website that produces counts

gh time of search queries on the Internet beginning in 2004 REP:
DL O11). 2 UAE - ho » ivf... Sj Q be bble 7 1) : and t e ceare aa

sradually trailed off to almost nothing by 2014.
</blockquote>

<p>OpenCV is really cool open source library but I was unable to twist it into doing what I wanted in this case. I decided to give the Google Vision API a shot and found that the output was high quality and allowed me to create a working demo. Since this seemed to work, I moved on to building a new Rails app from the ground up. There were a few key challenges at this phase:</p>

<ol>
  <li>Interoperability between Rails and my python scripts</li>
  <li>Designing an intuitive mechanism for editing highlighted text</li>
  <li>Improving accuracy</li>
</ol>

<p>Challenge 1 ended up being easy to solve because I could pass the image path to my python script and receive JSON back.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">def</span> <span class="nf">ocr_text</span><span class="p">(</span><span class="n">image</span><span class="p">)</span>
    <span class="n">path</span> <span class="o">=</span> <span class="n">path_for</span><span class="p">(</span><span class="n">image</span><span class="p">)</span>
    <span class="sb">`python3 </span><span class="si">#{</span><span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">root</span><span class="si">}</span><span class="sb">/app/services/extract.py "</span><span class="si">#{</span><span class="n">path</span><span class="si">}</span><span class="sb">"`</span>
  <span class="k">end</span>
</code></pre></div></div>

<p>Challenge 2 was much more difficult, but I was able to create a form that tracked the offsets of text the user highlighted to update the position with a live preview.</p>

<p>Challenge 3 was - unsurprisingly - the most difficult. There were several sources of error that seemed to compound and few different edge cases. For example, a highlight doesn’t always cover an entire word or skips one entirely.</p>

<p><img src="https://elijahrogers.dev/assets/images/highlighted.jpg" alt="highlights" />
<em>The words “show” and “spike” are almost skipped entirely</em></p>

<p>Adding some fuzzing (with the wonderful <code class="language-plaintext highlighter-rouge">fuzzy_match</code> gem) and word boundary detection to the matching algorithm helped quite a bit. This was particularly helpful for handling punctuation and whitespace related edge cases.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">fuzzy_match</span><span class="p">(</span><span class="n">full_text</span><span class="p">,</span> <span class="n">position</span><span class="p">,</span> <span class="n">text</span><span class="p">)</span>
  <span class="n">candidates</span> <span class="o">=</span> <span class="p">[]</span>
  <span class="n">start_indx</span> <span class="o">=</span> <span class="n">position</span>
  <span class="n">end_indx</span> <span class="o">=</span> <span class="n">text</span><span class="p">.</span><span class="nf">length</span>

  <span class="k">while</span> <span class="n">end_indx</span> <span class="o">&lt;=</span> <span class="n">full_text</span><span class="p">.</span><span class="nf">length</span>
    <span class="n">adj_start</span> <span class="o">=</span> <span class="n">nearest_word_boundary</span><span class="p">(</span><span class="n">full_text</span><span class="p">[</span><span class="n">position</span><span class="o">..</span><span class="p">],</span> <span class="n">start_indx</span><span class="p">)</span>
    <span class="n">adj_end</span> <span class="o">=</span> <span class="n">nearest_word_boundary</span><span class="p">(</span><span class="n">full_text</span><span class="p">[</span><span class="n">position</span><span class="o">..</span><span class="p">],</span> <span class="n">end_indx</span><span class="p">)</span>
    <span class="n">candidates</span> <span class="o">&lt;&lt;</span> <span class="n">full_text</span><span class="p">[</span><span class="n">adj_start</span><span class="o">...</span><span class="n">adj_end</span><span class="p">].</span><span class="nf">strip</span> <span class="c1"># Don't match on spaces</span>
    <span class="n">start_indx</span> <span class="o">+=</span> <span class="mi">1</span>
    <span class="n">end_indx</span> <span class="o">+=</span> <span class="mi">1</span>
  <span class="k">end</span>

  <span class="n">match</span> <span class="o">=</span> <span class="no">FuzzyMatch</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">candidates</span><span class="p">).</span><span class="nf">find</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="ss">find_with_score: </span><span class="kp">true</span><span class="p">)</span>

  <span class="k">return</span> <span class="k">unless</span> <span class="n">match</span> <span class="o">&amp;&amp;</span> <span class="n">match</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mf">0.95</span>

  <span class="n">match</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="k">end</span>
</code></pre></div></div>

<p>This wasn’t enough though. The results from the Vision API were still inconsistent and after a number of different updates and adjustments things weren’t improving enough.</p>

<p>Eventually I stumbled upon <a href="https://www.reddit.com/r/ChatGPTCoding/comments/1812r4n/vision_api_giving_much_worse_replies_than_the/">this reddit post</a>. Apparently ChatGPT is really good at this sort of thing! I tested it out with the chat interface first and was astounded - ChatGPT-4o made the task look easy and it only required a single API call.</p>

<p>I quickly created a new backend service for this extraction method and finally got the accuracy I was looking for. The final step was adding some more polish and marketing before launching <a href="https://www.snippetstash.app/">the app</a>.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[I’m not sure exactly where I picked it up but I have a habit of highlighting books that I read. It’s easy to forget some key insights after reading a book and highlighting provides a way to mark something that I want to revisit. The issue is I don’t revist. At best I might remember some connection a few months later and flip through the entire book looking for the section I was thinking of. After repeating this process several times it was clear that I was wasting my time. This shouldn’t be so difficult. I realized that if I could digitize these highlights I could search, organize, and easily reference them whenever I wanted - No more flipping pages! I figured I should be able to build an app where I submit a photo and the text is OCR’d and any highlighted texts are extracted (that is at least once I learned the term OCR).]]></summary></entry><entry><title type="html">Irb’s Easter Egg</title><link href="https://elijahrogers.dev/elijahrogers.github.io/2023/11/27/irbs-easter-egg.html" rel="alternate" type="text/html" title="Irb’s Easter Egg" /><published>2023-11-27T21:57:05+00:00</published><updated>2023-11-27T21:57:05+00:00</updated><id>https://elijahrogers.dev/elijahrogers.github.io/2023/11/27/irbs-easter-egg</id><content type="html" xml:base="https://elijahrogers.dev/elijahrogers.github.io/2023/11/27/irbs-easter-egg.html"><![CDATA[<p>If you’ve used Ruby for more than 5 minutes, you’ve probably come across the <a href="https://github.com/ruby/irb">IRB</a> gem or it’s popular counterpart <a href="https://github.com/pry/pry">Pry</a>. Both of these provide a REPL to explore and execute Ruby code.</p>

<p>IRB is old - nearly as old as the language itself in fact. For a mature library like this, I would expect a slow development cycle mostly comprised of incremental improvements, bug fixes, and performance enhancements. Imagine my surprise then when I stumbled on the following <a href="https:///github.com/ruby/irb/pull/69/files">Easter egg</a> that was added in 2020:</p>

<!--more-->

<p><img src="https://elijahrogers.dev/assets/images/easter_egg.png" alt="easter egg" /></p>

<p>Instead of generating autocomplete suggestions for any module or class in the <code class="language-plaintext highlighter-rouge">RubyVM</code> namespace, IRB shows an ASCII easter egg.</p>

<p>This makes sense when you realize that the <code class="language-plaintext highlighter-rouge">RubyVM</code> class is a special one. It’s only defined in MRI ruby and provides access to the internals of the interpreter itself. This is dangerous territory and the <a href="https://ruby-doc.org/3.2.1/RubyVM.html">offical docs</a> warn: “Normal users must not use it.” In order to disable this behavior, you can set the aptly named environment variable <code class="language-plaintext highlighter-rouge">RUBY_YES_I_AM_NOT_A_NORMAL_USER</code>.</p>

<p>The <code class="language-plaintext highlighter-rouge">RubyVM</code> class itself only defines one method - <code class="language-plaintext highlighter-rouge">stat</code> - which keeps track of some counters like the number of global constants defined. The only other contents of this namespace are a few constants and the <code class="language-plaintext highlighter-rouge">AbstractSyntaxTree</code> and <code class="language-plaintext highlighter-rouge">InstructionSequence</code> classes used to turn Ruby code into instructions for the VM.</p>

<p>It turns out that the ASCII ruby is just a fun way to ward off unsuspecting users from Ruby internals. There is still one mystery that remains unsolved however: there’s an animated version which (as far as I can tell) isn’t used anywhere:</p>

<p><img src="https://elijahrogers.dev/assets/images/easter_egg.gif" alt="easter egg" /></p>]]></content><author><name></name></author><category term="ruby" /><summary type="html"><![CDATA[If you’ve used Ruby for more than 5 minutes, you’ve probably come across the IRB gem or it’s popular counterpart Pry. Both of these provide a REPL to explore and execute Ruby code. IRB is old - nearly as old as the language itself in fact. For a mature library like this, I would expect a slow development cycle mostly comprised of incremental improvements, bug fixes, and performance enhancements. Imagine my surprise then when I stumbled on the following Easter egg that was added in 2020:]]></summary></entry><entry><title type="html">Index with</title><link href="https://elijahrogers.dev/elijahrogers.github.io/2022/06/27/index-with.html" rel="alternate" type="text/html" title="Index with" /><published>2022-06-27T16:38:15+00:00</published><updated>2022-06-27T16:38:15+00:00</updated><id>https://elijahrogers.dev/elijahrogers.github.io/2022/06/27/index-with</id><content type="html" xml:base="https://elijahrogers.dev/elijahrogers.github.io/2022/06/27/index-with.html"><![CDATA[<p>Rail’s <code class="language-plaintext highlighter-rouge">Enumerable</code> module provides a number of time saving methods for dealing with array-like structures. The one that’s provided the most utility (for me at least) is <code class="language-plaintext highlighter-rouge">index_with</code>. I often find myself with an array of items I’d like to use as keys for a new nested hash. With <code class="language-plaintext highlighter-rouge">index_with</code> this is as simple as:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">months</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'Jan'</span><span class="p">,</span> <span class="s1">'Feb'</span><span class="p">,</span> <span class="s1">'Mar'</span><span class="p">]</span>
<span class="n">months</span><span class="p">.</span><span class="nf">index_with</span><span class="p">({})</span>
<span class="o">=&gt;</span> <span class="p">{</span> <span class="s1">'Jan'</span> <span class="o">=&gt;</span> <span class="p">{},</span> <span class="s1">'Feb'</span> <span class="o">=&gt;</span> <span class="p">{},</span> <span class="s1">'Mar'</span> <span class="o">=&gt;</span> <span class="p">{}</span> <span class="p">}</span>
</code></pre></div></div>

<p>This pattern has generally served me quite well, but there is a bug hidden in plain sight.</p>

<!--more-->

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">months</span><span class="p">[</span><span class="s1">'Jan'</span><span class="p">][</span><span class="ss">:days</span><span class="p">]</span> <span class="o">=</span> <span class="mi">31</span>
<span class="o">=&gt;</span> <span class="p">?</span>
</code></pre></div></div>

<p>If you, like me, assumed that the above snippet will set the value of <code class="language-plaintext highlighter-rouge">:days</code> in the ‘Jan’ hash to 31 you would only be partially correct. In fact, it sets the <code class="language-plaintext highlighter-rouge">:days</code> for ~every~ month to 31.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">months</span><span class="p">[</span><span class="s1">'Jan'</span><span class="p">][</span><span class="ss">:days</span><span class="p">]</span> <span class="o">=</span> <span class="mi">31</span>
<span class="nb">puts</span> <span class="n">months</span>
<span class="o">=&gt;</span> <span class="p">{</span> <span class="s1">'Jan'</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="ss">days: </span><span class="mi">31</span> <span class="p">},</span> <span class="s1">'Feb'</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="ss">days: </span><span class="mi">31</span> <span class="p">},</span> <span class="s1">'Mar'</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="ss">days: </span><span class="mi">31</span> <span class="p">}</span> <span class="p">}</span>
</code></pre></div></div>

<p>So how do you get the intended result? You must use a new hash each time.
Beneath the terse syntax, <code class="language-plaintext highlighter-rouge">index_with</code> is a simple <code class="language-plaintext highlighter-rouge">each</code> loop that builds a new hash with our provided default value:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">index_with</span><span class="p">(</span><span class="n">default</span> <span class="o">=</span> <span class="p">(</span><span class="n">no_default</span> <span class="o">=</span> <span class="kp">true</span><span class="p">))</span>
<span class="o">...</span>
  <span class="n">result</span> <span class="o">=</span> <span class="p">{}</span>
  <span class="n">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">elem</span><span class="o">|</span> <span class="n">result</span><span class="p">[</span><span class="n">elem</span><span class="p">]</span> <span class="o">=</span> <span class="n">default</span> <span class="p">}</span>
  <span class="n">result</span>
<span class="o">...</span>
<span class="k">end</span>
</code></pre></div></div>

<p>When the provided default is a literal like <code class="language-plaintext highlighter-rouge">{}</code>, <code class="language-plaintext highlighter-rouge">index_with</code> reuses the same exact object each time. The result is that each of the <code class="language-plaintext highlighter-rouge">month</code> values are the exact same object:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">month</span><span class="p">.</span><span class="nf">values</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:object_id</span><span class="p">)</span>
<span class="o">=&gt;</span> <span class="p">[</span><span class="mi">29000</span><span class="p">,</span> <span class="mi">29000</span><span class="p">,</span> <span class="mi">29000</span><span class="p">]</span>
</code></pre></div></div>

<p>Avoiding the literal here gives you the intended behavior:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">months</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'Jan'</span><span class="p">,</span> <span class="s1">'Feb'</span><span class="p">,</span> <span class="s1">'Mar'</span><span class="p">]</span>
<span class="n">months</span><span class="p">.</span><span class="nf">index_with</span><span class="p">(</span><span class="no">Array</span><span class="p">.</span><span class="nf">new</span><span class="p">)</span>
<span class="n">month</span><span class="p">.</span><span class="nf">values</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:object_id</span><span class="p">)</span>
<span class="o">=&gt;</span> <span class="p">[</span><span class="mi">29001</span><span class="p">,</span> <span class="mi">29002</span><span class="p">,</span> <span class="mi">29003</span><span class="p">]</span>
</code></pre></div></div>]]></content><author><name></name></author><category term="rails" /><category term="enumerable" /><summary type="html"><![CDATA[Rail’s Enumerable module provides a number of time saving methods for dealing with array-like structures. The one that’s provided the most utility (for me at least) is index_with. I often find myself with an array of items I’d like to use as keys for a new nested hash. With index_with this is as simple as: months = ['Jan', 'Feb', 'Mar'] months.index_with({}) =&gt; { 'Jan' =&gt; {}, 'Feb' =&gt; {}, 'Mar' =&gt; {} } This pattern has generally served me quite well, but there is a bug hidden in plain sight.]]></summary></entry></feed>