<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
  <channel>
    <title>Hashrocket</title>
    <description>Knowledge &amp; news from us to you.</description>
    <link>https://hashrocket.com/blog.rss</link>
<item>
  <title>
    <![CDATA[Testing Readonly Models]]>
  </title>
  <link>https://hashrocket.com/blog/posts/testing-readonly-models</link>
  <description>
    <![CDATA[<p>I was working with a readonly model in Rails the other day and ran into an issue whilst testing it. Here&#39;s what I ran into and the solution I came up with.</p>

<p>Readonly models are a great way to signal that, well, you should only ever read them, not write them. Maybe you have some external system that connects to the database for writes, or maybe your Rails app connects to some data warehouse for some queries or reports. It&#39;s can be useful to have a safeguard to prevent accidental errant writes. It&#39;s actually really simple to make a model readonly, you just need to override the <a href="https://api.rubyonrails.org/classes/ActiveRecord/Core.html#method-i-readonly-3F" target="_blank"><code>readonly?</code></a> method:</p>
<pre class="highlight ruby" data-language="ruby"><code><span class="k">class</span> <span class="nc">ReadOnlyPost</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>&#x000A;  <span class="k">def</span> <span class="nf">readonly?</span> <span class="o">=</span> <span class="kp">true</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre>
<p>Now any attempt to create/save/update/delete a <code>ReadOnlyPost</code> will raise a friendly <code>ActiveRecord::ReadOnlyRecord</code> exception.</p>
<a href="#the-problem"><h2 id="the-problem">The Problem</h2></a>

<p>You might be able to see where this is going. </p>

<p>For any tests that can avoid saving this readonly model to the database, (i.e. using <code>ReadOnlyPost.new</code> or <code>FactoryBot.build</code>), then we&#39;re all good. But often tests need to persist some records to the test database. And if I try to create a <code>ReadOnlyPost</code>, I&#39;m going to have a bad time.</p>
<pre class="highlight ruby" data-language="ruby"><code><span class="no">RSpec</span><span class="p">.</span><span class="nf">describe</span> <span class="no">ReadOnlyPost</span><span class="p">,</span> <span class="ss">type: :model</span> <span class="k">do</span>&#x000A;  <span class="n">let</span><span class="p">(</span><span class="ss">:post</span><span class="p">)</span> <span class="p">{</span> <span class="no">ReadOnlyPost</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">title: </span><span class="s2">"Title"</span><span class="p">,</span> <span class="ss">body: </span><span class="s2">"body"</span><span class="p">)</span> <span class="p">}</span>&#x000A;&#x000A;  <span class="n">it</span> <span class="s2">"can create a post"</span> <span class="k">do</span>&#x000A;    <span class="n">expect</span><span class="p">(</span><span class="n">post</span><span class="p">).</span><span class="nf">to</span> <span class="n">be_a</span><span class="p">(</span><span class="no">ReadOnlyPost</span><span class="p">)</span>&#x000A;  <span class="k">end</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre><pre class="highlight shell" data-language="shell"><code>% bundle <span class="nb">exec </span>rspec&#x000A;F&#x000A;&#x000A;Failures:&#x000A;&#x000A;  1<span class="o">)</span> ReadOnlyPost can create a post&#x000A;     Failure/Error: <span class="nb">let</span><span class="o">(</span>:post<span class="o">)</span> <span class="o">{</span> ReadOnlyPost.create<span class="o">(</span>title: <span class="s2">"Title"</span>, body: <span class="s2">"body"</span><span class="o">)</span> <span class="o">}</span>&#x000A;&#x000A;     ActiveRecord::ReadOnlyRecord:&#x000A;       ReadOnlyPost is marked as <span class="nb">readonly</span>&#x000A;     <span class="c"># ./spec/models/read_only_post_spec.rb:4:in `block (2 levels) in &lt;top (required)&gt;'</span>&#x000A;     <span class="c"># ./spec/models/read_only_post_spec.rb:7:in `block (2 levels) in &lt;top (required)&gt;'</span>&#x000A;</code></pre>
<p>This makes sense, the readonly property of the model doesn&#39;t go away in test - creating a record is creating a record is writing to the database, so it will fail.</p>
<a href="#the-solution"><h2 id="the-solution">The Solution</h2></a>

<p>What we want to do is override this constraint to temporarily allow us to persist data. Ideally, we do this very narrowly to not impact the system under test. If the model is writable during test execution, then the behavior in the test is different from production behavior and that can lead to bugs in production that we cannot capture in a test. So ideally we can override this during test setup only and revert back to readonly by the time the test executes.</p>

<p>Given those constraints, a nice API would look like this:</p>
<pre class="highlight ruby" data-language="ruby"><code><span class="no">RSpec</span><span class="p">.</span><span class="nf">describe</span> <span class="no">ReadOnlyPost</span><span class="p">,</span> <span class="ss">type: :model</span> <span class="k">do</span>&#x000A;  <span class="c1"># In a let</span>&#x000A;  <span class="n">let</span><span class="p">(</span><span class="ss">:post</span><span class="p">)</span> <span class="p">{</span> <span class="n">with_writable</span><span class="p">(</span><span class="no">ReadOnlyPost</span><span class="p">)</span> <span class="p">{</span> <span class="no">ReadOnlyPost</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">title: </span><span class="s2">"Title"</span><span class="p">,</span> <span class="ss">body: </span><span class="s2">"body"</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span>&#x000A;&#x000A;  <span class="c1"># Or in a before block</span>&#x000A;  <span class="n">before</span> <span class="k">do</span>&#x000A;    <span class="n">with_writable</span><span class="p">(</span><span class="no">ReadOnlyPost</span><span class="p">)</span> <span class="k">do</span>&#x000A;      <span class="no">ReadOnlyPost</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">title: </span><span class="s2">"Title"</span><span class="p">,</span> <span class="ss">body: </span><span class="s2">"body"</span><span class="p">)</span>&#x000A;    <span class="k">end</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="n">it</span> <span class="s2">"can create a post"</span> <span class="k">do</span>&#x000A;    <span class="c1"># Even inline in a test</span>&#x000A;    <span class="n">with_writable</span><span class="p">(</span><span class="no">ReadOnlyPost</span><span class="p">)</span> <span class="p">{</span> <span class="no">ReadOnlyPost</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">title: </span><span class="s2">"Title"</span><span class="p">,</span> <span class="ss">body: </span><span class="s2">"body"</span><span class="p">)</span> <span class="p">}</span>&#x000A;&#x000A;    <span class="n">expect</span><span class="p">(</span><span class="n">post</span><span class="p">).</span><span class="nf">to</span> <span class="n">be_a</span><span class="p">(</span><span class="no">ReadOnlyPost</span><span class="p">)</span>&#x000A;  <span class="k">end</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre>
<p>The model would be writable only inside the block of <code>with_writable</code>, and outside the normal readonly value would hold true. Specifying the model that we want to override as the argument allows us to be intentional about which model we&#39;re overriding rather than blanket making everything writable (which could lead to some unexpected consequences).</p>

<p>Ruby&#39;s malleable nature allows us to make this override a reality with relative ease. We can open up the class to modification with <a href="https://docs.ruby-lang.org/en/4.0/Module.html#method-i-class_eval" target="_blank"><code>class_eval</code></a> and override <code>readonly?</code>, execute the block, and then revert <code>readonly?</code> back to it&#39;s original value.</p>
<pre class="highlight ruby" data-language="ruby"><code><span class="c1"># spec/support/readonly_helper.rb</span>&#x000A;<span class="k">def</span> <span class="nf">with_writable</span><span class="p">(</span><span class="n">klass</span><span class="p">)</span>&#x000A;  <span class="n">klass</span><span class="p">.</span><span class="nf">class_eval</span> <span class="k">do</span>&#x000A;    <span class="kp">alias_method</span> <span class="ss">:_original_readonly?</span><span class="p">,</span> <span class="ss">:readonly?</span>&#x000A;    <span class="n">define_method</span><span class="p">(</span><span class="ss">:readonly?</span><span class="p">)</span> <span class="p">{</span> <span class="kp">false</span> <span class="p">}</span>&#x000A;  <span class="k">end</span>&#x000A;  <span class="k">yield</span>&#x000A;<span class="k">ensure</span>&#x000A;  <span class="n">klass</span><span class="p">.</span><span class="nf">class_eval</span> <span class="k">do</span>&#x000A;    <span class="kp">alias_method</span> <span class="ss">:readonly?</span><span class="p">,</span> <span class="ss">:_original_readonly?</span>&#x000A;    <span class="n">remove_method</span> <span class="ss">:_original_readonly?</span>&#x000A;  <span class="k">end</span>&#x000A;<span class="k">end</span>&#x000A;&#x000A;</code></pre>
<p>With this definition living in <code>spec/support</code>, it can be used in any test, but makes it inconvenient to accidentally use in app code (nothing is impossible in ruby - you could still require it in app code, but doing so would be a code smell and hopefully convince you that&#39;s not the way to go).</p>

<p>You could extend this helper to accept an array of classes and override <code>readonly?</code> for each if you had multiple readonly models for which you needed to create data.</p>
<a href="#other-options"><h2 id="other-options">Other Options</h2></a>

<p>I think the above solution provides the clearest intent and doesn&#39;t change the model&#39;s behavior during test execution. There are other ways to implement this but I think the drawbacks make them less desirable.</p>
<a href="#as-an-rspec-example-group-helper"><h3 id="as-an-rspec-example-group-helper">As an RSpec Example Group Helper</h3></a>
<pre class="highlight ruby" data-language="ruby"><code><span class="n">describe</span> <span class="s2">"example group"</span><span class="p">,</span> <span class="ss">writable: </span><span class="no">ReadOnlyPost</span> <span class="k">do</span>&#x000A;  <span class="c1"># ...</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre>
<p>You could make a <code>writable</code> helper to add to RSpec example groups or individual tests. This is arguably a cleaner interface than my preferred solution, but I couldn&#39;t find a way to make the override apply only during test setup, not during execution. That makes this a no go for me, since I lose confidence the test will behave the same way the application does in production - meaning I can&#39;t rely on this test.</p>
<a href="#write-sql-directly"><h3 id="write-sql-directly">Write SQL Directly</h3></a>
<pre class="highlight ruby" data-language="ruby"><code><span class="no">RSpec</span><span class="p">.</span><span class="nf">describe</span> <span class="no">ReadOnlyPost</span><span class="p">,</span> <span class="ss">type: :model</span> <span class="k">do</span>&#x000A;  <span class="n">before</span> <span class="k">do</span>&#x000A;    <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="p">.</span><span class="nf">connection</span><span class="p">.</span><span class="nf">execute</span><span class="p">(</span><span class="s2">"INSERT INTO read_only_posts VALUES ('Title', 'Body'"</span><span class="p">)</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="n">it</span> <span class="s2">"can create a post"</span> <span class="k">do</span>&#x000A;    <span class="n">expect</span><span class="p">(</span><span class="no">ReadOnlyPost</span><span class="p">.</span><span class="nf">last</span><span class="p">).</span><span class="nf">to</span> <span class="n">be_a</span><span class="p">(</span><span class="no">ReadOnlyPost</span><span class="p">)</span>&#x000A;  <span class="k">end</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre>
<p>You could write SQL statements directly to insert/update records. But this bypasses all of the ORM features of ActiveRecord and really feels like fighting Rails rather than extending it.</p>
<a href="#conclusion"><h2 id="conclusion">Conclusion</h2></a>

<p>So there&#39;s a way to test readonly models effectively. Hope it&#39;s helpful, and let me know if you use this or a different solution yourself!</p>

<p>Photo by <a href="https://unsplash.com/@jocallen" target="_blank">John Cardamone</a> on <a href="https://unsplash.com/photos/a-close-up-of-a-lock-on-a-red-and-white-door-Evr4B9JTa94" target="_blank">Unsplash</a></p>
]]>
  </description>
  <author>
  </author>
  <guid>https://hashrocket.com/blog/posts/testing-readonly-models</guid>
  <pubDate>Tue, 17 Mar 2026 09:00:00 -0400</pubDate>
  <category>Testing</category>
  <category>Ruby on Rails</category>
</item>
<item>
  <title>
    <![CDATA[Crafting Code: Building a Ruby Pattern Generator for a Crochet Circle]]>
  </title>
  <link>https://hashrocket.com/blog/posts/crafting-code-building-a-ruby-pattern-generator-for-a-crochet-circle</link>
  <description>
    <![CDATA[<p>In my time as a developer, I have noticed that one of the most common ways my coworkers spend time coding outside of work is by developing little code snippets or apps that solve problems in their everyday lives. From household budgeting, to managing workouts on rowing machines, to generating a Taco Bell order, these projects allow devs to explore different coding styles and learn new technologies.</p>

<p>For a long time, most of my side projects have been for the sole purpose of learning a new technology. When I wanted to start building mobile apps with React Native, I wrote a small to-do app that, once finished, I abandoned. The same thing happened when I wanted to try to use PostgreSQL&#39;s listen and notify feature to build a live updating chat app. So, when I was thinking about a new side project, I decided it was time to work on something that could be long lived and help me with one of my favorite hobbies: crocheting.</p>
<a href="#the-premise"><h1 id="the-premise">The Premise</h1></a>

<p>Recently, I&#39;ve been making a lot of small projects that have started with a base shape that then gets built upon. Often, this shape is a circle. After running through several projects, I started to notice a pattern of increases and repetitions for each row. It occurred to me that if the shape followed a specific pattern, I could probably build a ruby class to generate that pattern. Thus began this side project!</p>
<a href="#breaking-down-a-simplified-pattern"><h2 id="breaking-down-a-simplified-pattern">Breaking Down a Simplified Pattern</h2></a>

<p>To begin, we have to inspect the pattern. Crochet patterns follow a specific format, and use abbreviations for the types of stitches being used.</p>

<p>The example pattern uses the following abbreviations and rules:</p>

<table><thead>
<tr>
<th style="text-align: center">Abbreviation</th>
<th>Meaning</th>
<th>Use</th>
<th style="text-align: center">Stitch Count</th>
</tr>
</thead><tbody>
<tr>
<td style="text-align: center"><strong>sc</strong></td>
<td>single crochet</td>
<td>adds a stitch to the round</td>
<td style="text-align: center">1</td>
</tr>
<tr>
<td style="text-align: center"><strong>inc</strong></td>
<td>increase <em>(two single crochets in the same stitch)</em></td>
<td>adds an extra stitch to the round</td>
<td style="text-align: center">2</td>
</tr>
</tbody></table>

<p>With those abbreviations in mind, we can start parsing the pattern.</p>
<pre class="highlight plaintext" data-language="plaintext"><code>R1: 6sc in magic ring (6)&#x000A;R2: [inc] x6 (12)&#x000A;R3: [sc, inc] x6 (18)&#x000A;R4: sc, inc, [2sc, inc] x5, sc (24)&#x000A;R5: [3sc, inc] x6 (30)&#x000A;R6: 2sc, inc, [4sc, inc] x5, 2sc (36)&#x000A;R7: [5sc, inc] x6 (42)&#x000A;R8: 3sc, inc, [6sc, inc] x5, 3sc (48)&#x000A;</code></pre>
<p>Without knowing the details of how crochet patterns work, we can still get an idea of the format here. First, we can see that each line starts with <code>R{n}:</code>. This is the indicator of the round we are working on. We can see here that the pattern ends on <code>R8</code>, so we know that we&#39;re going to have exactly 8 rounds to the circle.</p>

<p>Second, we can see that each line ends with a number in parentheses. This number represents the final stitch count for the round. Here is where we may begin to see the beginnings of a pattern. Looking at the stitch counts, we see that we&#39;re starting with 6 stitches, and then increasing by 6 in each round. Given that behavior, the equation for determining the stitch count based on a given row would be <code>6 + 6(R - 1)</code>, where <code>R</code> is the row number.</p>

<p>In between the round number and stitch count are the actual directions for the round. In some cases, such as round 3, there are square brackets around a stitch or set of stitches, followed by <code>x{n}</code>. This is an indicator that the stitch or stitches within the brackets are going to be repeated <code>n</code> times. So in round 4, where the directions are <code>sc, inc, [2sc, inc] x5, sc</code>, what we&#39;re actually doing is:</p>
<pre class="highlight plaintext" data-language="plaintext"><code>sc, inc, sc, sc, inc, sc, sc, inc, sc, sc, inc, sc, sc, inc, sc, sc, inc, sc&#x000A;</code></pre><a href="#starting-the-ruby-class"><h2 id="starting-the-ruby-class">Starting the Ruby Class</h2></a>

<p>Now that the stitch count equation has been determined, a small ruby class can start to be built up. The class will expect an input of how many rows, and output an array of strings in the correct format for each row. Also, given that we have an example pattern, an rspec test expecting the correct output can be written.</p>
<pre class="highlight ruby" data-language="ruby"><code><span class="k">class</span> <span class="nc">CirclePatternGenerator</span>&#x000A;  <span class="nb">attr_reader</span> <span class="ss">:row_number</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">generate</span><span class="p">(</span><span class="n">row_count</span><span class="p">:)</span>&#x000A;    <span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="n">row_count</span><span class="p">).</span><span class="nf">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">row_number</span><span class="o">|</span>&#x000A;      <span class="n">new</span><span class="p">(</span><span class="n">row_number</span><span class="p">:).</span><span class="nf">pattern</span>&#x000A;    <span class="k">end</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">row_number</span><span class="p">:)</span>&#x000A;    <span class="vi">@row_number</span> <span class="o">=</span> <span class="n">row_number</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">pattern</span>&#x000A;    <span class="s2">"</span><span class="si">#{</span><span class="n">row_title</span><span class="si">}</span><span class="s2">: </span><span class="si">#{</span><span class="n">instructions</span><span class="si">}</span><span class="s2"> (</span><span class="si">#{</span><span class="n">stitch_count</span><span class="si">}</span><span class="s2">)"</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="kp">private</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">row_title</span>&#x000A;    <span class="s2">"R</span><span class="si">#{</span><span class="n">row_number</span><span class="si">}</span><span class="s2">"</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">stitch_count</span>&#x000A;    <span class="mi">6</span> <span class="o">+</span> <span class="mi">6</span> <span class="o">*</span> <span class="p">(</span><span class="n">row_number</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">instructions</span>&#x000A;  <span class="k">end</span>&#x000A;<span class="k">end</span>&#x000A;&#x000A;<span class="no">RSpec</span><span class="p">.</span><span class="nf">describe</span> <span class="no">CirclePatternGenerator</span> <span class="k">do</span>&#x000A;  <span class="n">describe</span> <span class="s2">".generate"</span> <span class="k">do</span>&#x000A;    <span class="n">let</span><span class="p">(</span><span class="ss">:expected_output</span><span class="p">)</span> <span class="k">do</span>&#x000A;      <span class="p">[</span>&#x000A;        <span class="s2">"R1: 6sc in magic ring (6)"</span><span class="p">,</span>&#x000A;        <span class="s2">"R2: [inc] x6 (12)"</span><span class="p">,</span>&#x000A;        <span class="s2">"R3: [sc, inc] x6 (18)"</span><span class="p">,</span>&#x000A;        <span class="s2">"R4: sc, inc, [2sc, inc] x5, sc (24)"</span><span class="p">,</span>&#x000A;        <span class="s2">"R5: [3sc, inc] x6 (30)"</span><span class="p">,</span>&#x000A;        <span class="s2">"R6: 2sc, inc, [4sc, inc] x5, 2sc (36)"</span><span class="p">,</span>&#x000A;        <span class="s2">"R7: [5sc, inc] x6 (42)"</span><span class="p">,</span>&#x000A;        <span class="s2">"R8: 3sc, inc, [6sc, inc] x5, 3sc (48)"</span><span class="p">,</span>&#x000A;      <span class="p">]</span>&#x000A;    <span class="k">end</span>&#x000A;&#x000A;    <span class="n">it</span> <span class="s2">"outputs the correct pattern"</span> <span class="k">do</span>&#x000A;      <span class="n">expect</span><span class="p">(</span><span class="n">described_class</span><span class="p">.</span><span class="nf">generate</span><span class="p">(</span><span class="ss">row_count: </span><span class="mi">8</span><span class="p">)).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="n">expected_output</span><span class="p">)</span>&#x000A;    <span class="k">end</span>&#x000A;  <span class="k">end</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre>
<p>Running the specs, we can confirm that the row titles and stitch counts are correct, and the instructions are the last thing to figure out.</p>
<pre class="highlight shell" data-language="shell"><code><span class="nv">$ </span>rspec circle_pattern_generator.rb&#x000A;&#x000A;Randomized with seed 47053&#x000A;F&#x000A;&#x000A;Failures:&#x000A;&#x000A;  1<span class="o">)</span> CirclePatternGenerator.generate outputs the correct pattern&#x000A;     Failure/Error:&#x000A;       expect<span class="o">(</span>described_class.generate<span class="o">(</span>row_count: 8<span class="o">))</span>.to eq<span class="o">(</span>expected_output<span class="o">)</span>&#x000A;&#x000A;       expected: <span class="o">[</span><span class="s2">"R1: 6sc in magic ring (6)"</span>, <span class="s2">"R2: [inc] x6 (12)"</span>, <span class="s2">"R3: [sc, inc] x6 (18)"</span>, <span class="s2">"R4: sc, inc, [2sc, inc] ...c, inc, [4sc, inc] x5, 2sc (36)"</span>, <span class="s2">"R7: [5sc, inc] x6 (42)"</span>, <span class="s2">"R8: 3sc, inc, [6sc, inc] x5, 3sc (48)"</span><span class="o">]</span>&#x000A;            got: <span class="o">[</span><span class="s2">"R1:  (6)"</span>, <span class="s2">"R2:  (12)"</span>, <span class="s2">"R3:  (18)"</span>, <span class="s2">"R4:  (24)"</span>, <span class="s2">"R5:  (30)"</span>, <span class="s2">"R6:  (36)"</span>, <span class="s2">"R7:  (42)"</span>, <span class="s2">"R8:  (48)"</span><span class="o">]</span>&#x000A;</code></pre><a href="#examining-the-instructions"><h2 id="examining-the-instructions">Examining the Instructions</h2></a>

<p>The core instructions for each row will be the most involved part of this pattern generator. Knowing that each row is made up of single crochets and increases, we can break down the instructions for the rows into the totals for each kind of stitch:</p>

<table><thead>
<tr>
<th style="text-align: center">Row Number</th>
<th>Instructions</th>
<th style="text-align: center">Single Crochets</th>
<th style="text-align: center">Increases</th>
</tr>
</thead><tbody>
<tr>
<td style="text-align: center">1</td>
<td>6sc in magic ring</td>
<td style="text-align: center">6</td>
<td style="text-align: center">0</td>
</tr>
<tr>
<td style="text-align: center">2</td>
<td>[inc] x6</td>
<td style="text-align: center">0</td>
<td style="text-align: center">6</td>
</tr>
<tr>
<td style="text-align: center">3</td>
<td>[sc, inc] x6</td>
<td style="text-align: center">6</td>
<td style="text-align: center">6</td>
</tr>
<tr>
<td style="text-align: center">4</td>
<td>sc, inc, [2sc, inc] x5, sc</td>
<td style="text-align: center">12</td>
<td style="text-align: center">6</td>
</tr>
<tr>
<td style="text-align: center">5</td>
<td>[3sc, inc] x6</td>
<td style="text-align: center">18</td>
<td style="text-align: center">6</td>
</tr>
<tr>
<td style="text-align: center">6</td>
<td>2sc, inc, [4sc, inc] x5, 2sc</td>
<td style="text-align: center">24</td>
<td style="text-align: center">6</td>
</tr>
<tr>
<td style="text-align: center">7</td>
<td>[5sc, inc] x6</td>
<td style="text-align: center">30</td>
<td style="text-align: center">6</td>
</tr>
<tr>
<td style="text-align: center">8</td>
<td>3sc, inc, [6sc, inc] x5, 3sc</td>
<td style="text-align: center">36</td>
<td style="text-align: center">6</td>
</tr>
</tbody></table>

<p>Looking at the table, we can see that the number of increases is 6 starting from the second row, and doesn&#39;t change as the rows progress. It&#39;s also apparent that the number of single crochets increases by 6 per row after starting from 0 on the second row. The first row, therefore, appears to be an outlier to the pattern evident from lines 2 - 8. The first row is also the only row that has special instructions (&quot;in magic ring&quot;).</p>

<p>If we treat the first row like a special case, we can add a guard clause to the <code>instructions</code> method on the ruby class.</p>
<pre class="highlight ruby" data-language="ruby"><code><span class="k">class</span> <span class="nc">CirclePatternGenerator</span>&#x000A;  <span class="c1"># ...</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">instructions</span>&#x000A;    <span class="k">return</span> <span class="n">first_row_instructions</span> <span class="k">if</span> <span class="n">first_row?</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">first_row_instructions</span>&#x000A;    <span class="s2">"6sc in magic ring"</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">first_row?</span>&#x000A;    <span class="n">row_number</span> <span class="o">==</span> <span class="mi">1</span>&#x000A;  <span class="k">end</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre>
<p>Running the specs again, we can see that the first row now matches the expected output.</p>
<pre class="highlight shell" data-language="shell"><code><span class="nv">$ </span>rspec circle_pattern_generator.rb&#x000A;&#x000A;Randomized with seed 47053&#x000A;F&#x000A;&#x000A;Failures:&#x000A;&#x000A;  1<span class="o">)</span> CirclePatternGenerator.generate outputs the correct pattern&#x000A;     Failure/Error:&#x000A;       expect<span class="o">(</span>described_class.generate<span class="o">(</span>row_count: 8<span class="o">))</span>.to eq<span class="o">(</span>expected_output<span class="o">)</span>&#x000A;&#x000A;       expected: <span class="o">[</span><span class="s2">"R1: 6sc in magic ring (6)"</span>, <span class="s2">"R2: [inc] x6 (12)"</span>, <span class="s2">"R3: [sc, inc] x6 (18)"</span>, <span class="s2">"R4: sc, inc, [2sc, inc] ...c, inc, [4sc, inc] x5, 2sc (36)"</span>, <span class="s2">"R7: [5sc, inc] x6 (42)"</span>, <span class="s2">"R8: 3sc, inc, [6sc, inc] x5, 3sc (48)"</span><span class="o">]</span>&#x000A;            got: <span class="o">[</span><span class="s2">"R1: 6sc in magic ring (6)"</span>, <span class="s2">"R2:  (12)"</span>, <span class="s2">"R3:  (18)"</span>, <span class="s2">"R4:  (24)"</span>, <span class="s2">"R5:  (30)"</span>, <span class="s2">"R6:  (36)"</span>, <span class="s2">"R7:  (42)"</span>, <span class="s2">"R8:  (48)"</span><span class="o">]</span>&#x000A;</code></pre>
<p>With the special case out of the way, it&#39;s time to look into the rows that adhere to the pattern of single crochets and increases.</p>

<p>The most noticeable thing in the row instructions is the number of single crochets that are used to space out the increases. In row 2, there are none, but from there, we can see that row 3 has 1 single crochet between increases, followed by 2 in row 4, 3 in row 5, and so on. We have ourselves another small equation -- the number of single crochets spacing out the increases is <code>R - 2</code>, where <code>R</code> is our row number. We can add that as a method to use for the instructions in the ruby class.</p>
<pre class="highlight ruby" data-language="ruby"><code><span class="k">def</span> <span class="nf">sc_count</span>&#x000A;  <span class="n">row_number</span> <span class="o">-</span> <span class="mi">2</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre>
<p>Returing to the pattern, it appears that the only difference in the way the instructions work is that in the odd numbered rows, we repeat the same thing 6 times, and in the even numbered rows, the repeat is only 5 times. The reason the repeat on the even rows is 5 times is because the single crochet spacing appears to be split between the beginning and end of the row. We again have a bit of an outlier with the second row, since it doesn&#39;t have any single crochets spacing out the increases.</p>
<pre class="highlight shell" data-language="shell"><code><span class="c"># no sc</span>&#x000A;R2: <span class="o">[</span>inc] x6 <span class="o">(</span>12<span class="o">)</span>&#x000A;&#x000A;<span class="c"># sc in the beginning and end, totaling 2sc</span>&#x000A;R4: sc, inc, <span class="o">[</span>2sc, inc] x5, sc <span class="o">(</span>24<span class="o">)</span>&#x000A;&#x000A;<span class="c"># 2sc in the beginning and end, totaling 4sc</span>&#x000A;R6: 2sc, inc, <span class="o">[</span>4sc, inc] x5, 2sc <span class="o">(</span>36<span class="o">)</span>&#x000A;&#x000A;<span class="c"># 3sc in the beginning and end, totaling 6sc</span>&#x000A;R8: 3sc, inc, <span class="o">[</span>6sc, inc] x5, 3sc <span class="o">(</span>48<span class="o">)</span>&#x000A;</code></pre>
<p>Since we have another outlier, it seems easiest to add a second guard clause to the class.</p>
<pre class="highlight ruby" data-language="ruby"><code><span class="k">class</span> <span class="nc">CirclePatternGenerator</span>&#x000A;  <span class="c1"># ...</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">instructions</span>&#x000A;    <span class="k">return</span> <span class="n">first_row_instructions</span> <span class="k">if</span> <span class="n">first_row?</span>&#x000A;    <span class="k">return</span> <span class="n">second_row_instructions</span> <span class="k">if</span> <span class="n">second_row?</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="c1"># ...</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">second_row_instructions</span>&#x000A;    <span class="s2">"[inc] x6"</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">second_row?</span>&#x000A;    <span class="n">row_number</span> <span class="o">==</span> <span class="mi">2</span>&#x000A;  <span class="k">end</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre>
<p>Now, we can build out the logic for the even and odd rows, starting with the easier option: the odd rows.</p>
<a href="#programming-the-odd-rows"><h2 id="programming-the-odd-rows">Programming the Odd Rows</h2></a>

<p>Using the <code>sc_count</code> method determined earlier, the odd numbered rows should follow the pattern of <code>&quot;[#{sc_count}sc, inc] x6&quot;</code>.</p>
<pre class="highlight ruby" data-language="ruby"><code><span class="k">class</span> <span class="nc">CirclePatternGenerator</span>&#x000A;  <span class="c1"># ...</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">instructions</span>&#x000A;    <span class="k">return</span> <span class="n">first_row_instructions</span> <span class="k">if</span> <span class="n">first_row?</span>&#x000A;    <span class="k">return</span> <span class="n">second_row_instructions</span> <span class="k">if</span> <span class="n">second_row?</span>&#x000A;&#x000A;    <span class="k">if</span> <span class="n">row_number</span><span class="p">.</span><span class="nf">odd?</span>&#x000A;      <span class="n">odd_row_instructions</span>&#x000A;    <span class="k">end</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">odd_row_instructions</span>&#x000A;    <span class="s2">"[</span><span class="si">#{</span><span class="n">sc_count</span><span class="si">}</span><span class="s2">sc, inc] x6"</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="c1"># ...</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre>
<p>Running the tests again, we see that this <em>almost</em> looks right, except that there shouldn&#39;t be a <code>1</code> in front of the <code>sc</code> for row 3 (<code>&quot;R3: [1sc, inc] x6 (18)&quot;</code>).</p>
<pre class="highlight shell" data-language="shell"><code><span class="nv">$ </span>rspec circle_pattern_generator.rb&#x000A;&#x000A;Randomized with seed 37350&#x000A;F&#x000A;&#x000A;Failures:&#x000A;&#x000A;  1<span class="o">)</span> CirclePatternGenerator.generate outputs the correct pattern&#x000A;     Failure/Error: expect<span class="o">(</span>described_class.generate<span class="o">(</span>row_count: 8<span class="o">))</span>.to eq<span class="o">(</span>expected_output<span class="o">)</span>&#x000A;&#x000A;       expected: <span class="o">[</span><span class="s2">"R1: 6sc in magic ring (6)"</span>, <span class="s2">"R2: [inc] x6 (12)"</span>, <span class="s2">"R3: [sc, inc] x6 (18)"</span>, <span class="s2">"R4: sc, inc, [2sc, inc] ...c, inc, [4sc, inc] x5, 2sc (36)"</span>, <span class="s2">"R7: [5sc, inc] x6 (42)"</span>, <span class="s2">"R8: 3sc, inc, [6sc, inc] x5, 3sc (48)"</span><span class="o">]</span>&#x000A;            got: <span class="o">[</span><span class="s2">"R1: 6sc in magic ring (6)"</span>, <span class="s2">"R2: [inc] x6 (12)"</span>, <span class="s2">"R3: [1sc, inc] x6 (18)"</span>, <span class="s2">"R4:  (24)"</span>, <span class="s2">"R5: [3sc, inc] x6 (30)"</span>, <span class="s2">"R6:  (36)"</span>, <span class="s2">"R7: [5sc, inc] x6 (42)"</span>, <span class="s2">"R8:  (48)"</span><span class="o">]</span>&#x000A;</code></pre>
<p>While we can fix this for row 3, this isn&#39;t the only case where there&#39;s just one single crochet. Perhaps it would be worth creating a method to handle the display of the single crochets.</p>
<pre class="highlight ruby" data-language="ruby"><code><span class="k">class</span> <span class="nc">CirclePatternGenerator</span>&#x000A;  <span class="c1"># ...</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">odd_row_instructions</span>&#x000A;    <span class="s2">"[</span><span class="si">#{</span><span class="n">sc_pattern</span><span class="p">(</span><span class="n">sc_count</span><span class="p">)</span><span class="si">}</span><span class="s2">, inc] x6"</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">sc_pattern</span><span class="p">(</span><span class="n">number</span><span class="p">)</span>&#x000A;    <span class="p">(</span><span class="n">number</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">?</span> <span class="s2">"sc"</span> <span class="p">:</span> <span class="s2">"</span><span class="si">#{</span><span class="n">number</span><span class="si">}</span><span class="s2">sc"</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="c1"># ...</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre>
<p>Rerunning the tests, we can see that this solves the issue for row 3.</p>
<pre class="highlight shell" data-language="shell"><code><span class="nv">$ </span>rspec circle_pattern_generator.rb&#x000A;&#x000A;Randomized with seed 45147&#x000A;F&#x000A;&#x000A;Failures:&#x000A;&#x000A;  1<span class="o">)</span> CirclePatternGenerator.generate outputs the correct pattern&#x000A;     Failure/Error: expect<span class="o">(</span>described_class.generate<span class="o">(</span>row_count: 8<span class="o">))</span>.to eq<span class="o">(</span>expected_output<span class="o">)</span>&#x000A;&#x000A;       expected: <span class="o">[</span><span class="s2">"R1: 6sc in magic ring (6)"</span>, <span class="s2">"R2: [inc] x6 (12)"</span>, <span class="s2">"R3: [sc, inc] x6 (18)"</span>, <span class="s2">"R4: sc, inc, [2sc, inc] ...c, inc, [4sc, inc] x5, 2sc (36)"</span>, <span class="s2">"R7: [5sc, inc] x6 (42)"</span>, <span class="s2">"R8: 3sc, inc, [6sc, inc] x5, 3sc (48)"</span><span class="o">]</span>&#x000A;            got: <span class="o">[</span><span class="s2">"R1: 6sc in magic ring (6)"</span>, <span class="s2">"R2: [inc] x6 (12)"</span>, <span class="s2">"R3: [sc, inc] x6 (18)"</span>, <span class="s2">"R4:  (24)"</span>, <span class="s2">"R5: [3sc, inc] x6 (30)"</span>, <span class="s2">"R6:  (36)"</span>, <span class="s2">"R7: [5sc, inc] x6 (42)"</span>, <span class="s2">"R8:  (48)"</span><span class="o">]</span>&#x000A;</code></pre>
<p>Now we can address the even rows.</p>
<a href="#programming-the-even-rows"><h2 id="programming-the-even-rows">Programming the Even Rows</h2></a>

<p>As noted before, the even rows repeat 5 times, and have the 6th repetition split across the beginning and end of the row.</p>
<pre class="highlight ruby" data-language="ruby"><code><span class="k">class</span> <span class="nc">CirclePatternGenerator</span>&#x000A;  <span class="c1"># ...</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">instructions</span>&#x000A;    <span class="k">return</span> <span class="n">first_row_instructions</span> <span class="k">if</span> <span class="n">first_row?</span>&#x000A;    <span class="k">return</span> <span class="n">second_row_instructions</span> <span class="k">if</span> <span class="n">second_row?</span>&#x000A;&#x000A;    <span class="n">row_number</span><span class="p">.</span><span class="nf">odd?</span> <span class="p">?</span> <span class="n">odd_row_instructions</span> <span class="p">:</span> <span class="n">even_row_instructions</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">even_row_instructions</span>&#x000A;    <span class="n">split_pattern</span> <span class="o">=</span> <span class="n">sc_pattern</span><span class="p">(</span><span class="n">sc_count</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span>&#x000A;    <span class="s2">"</span><span class="si">#{</span><span class="n">split_pattern</span><span class="si">}</span><span class="s2">, inc, [</span><span class="si">#{</span><span class="n">sc_pattern</span><span class="p">(</span><span class="n">sc_count</span><span class="p">)</span><span class="si">}</span><span class="s2">, inc] x5, </span><span class="si">#{</span><span class="n">split_pattern</span><span class="si">}</span><span class="s2">"</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="c1"># ...</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre>
<p>Running the test again, we have green! We have created a pattern generator for crochet circles based on a specified row count.</p>
<pre class="highlight ruby" data-language="ruby"><code><span class="k">class</span> <span class="nc">CirclePatternGenerator</span>&#x000A;  <span class="nb">attr_reader</span> <span class="ss">:row_number</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">generate</span><span class="p">(</span><span class="n">row_count</span><span class="p">:)</span>&#x000A;    <span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="n">row_count</span><span class="p">).</span><span class="nf">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">row_number</span><span class="o">|</span>&#x000A;      <span class="n">new</span><span class="p">(</span><span class="n">row_number</span><span class="p">:).</span><span class="nf">pattern</span>&#x000A;    <span class="k">end</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">row_number</span><span class="p">:)</span>&#x000A;    <span class="vi">@row_number</span> <span class="o">=</span> <span class="n">row_number</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">pattern</span>&#x000A;    <span class="s2">"</span><span class="si">#{</span><span class="n">row_title</span><span class="si">}</span><span class="s2">: </span><span class="si">#{</span><span class="n">instructions</span><span class="si">}</span><span class="s2"> (</span><span class="si">#{</span><span class="n">stitch_count</span><span class="si">}</span><span class="s2">)"</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="kp">private</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">row_title</span>&#x000A;    <span class="s2">"R</span><span class="si">#{</span><span class="n">row_number</span><span class="si">}</span><span class="s2">"</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">stitch_count</span>&#x000A;    <span class="mi">6</span> <span class="o">+</span> <span class="mi">6</span> <span class="o">*</span> <span class="p">(</span><span class="n">row_number</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">instructions</span>&#x000A;    <span class="k">return</span> <span class="n">first_row_instructions</span> <span class="k">if</span> <span class="n">first_row?</span>&#x000A;    <span class="k">return</span> <span class="n">second_row_instructions</span> <span class="k">if</span> <span class="n">second_row?</span>&#x000A;&#x000A;    <span class="n">row_number</span><span class="p">.</span><span class="nf">odd?</span> <span class="p">?</span> <span class="n">odd_row_instructions</span> <span class="p">:</span> <span class="n">even_row_instructions</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">odd_row_instructions</span>&#x000A;    <span class="s2">"[</span><span class="si">#{</span><span class="n">sc_pattern</span><span class="p">(</span><span class="n">sc_count</span><span class="p">)</span><span class="si">}</span><span class="s2">, inc] x6"</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">even_row_instructions</span>&#x000A;    <span class="n">split_pattern</span> <span class="o">=</span> <span class="n">sc_pattern</span><span class="p">(</span><span class="n">sc_count</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span>&#x000A;    <span class="s2">"</span><span class="si">#{</span><span class="n">split_pattern</span><span class="si">}</span><span class="s2">, inc, [</span><span class="si">#{</span><span class="n">sc_pattern</span><span class="p">(</span><span class="n">sc_count</span><span class="p">)</span><span class="si">}</span><span class="s2">, inc] x5, </span><span class="si">#{</span><span class="n">split_pattern</span><span class="si">}</span><span class="s2">"</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">sc_pattern</span><span class="p">(</span><span class="n">number</span><span class="p">)</span>&#x000A;    <span class="p">(</span><span class="n">number</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">?</span> <span class="s2">"sc"</span> <span class="p">:</span> <span class="s2">"</span><span class="si">#{</span><span class="n">number</span><span class="si">}</span><span class="s2">sc"</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">sc_count</span>&#x000A;    <span class="n">row_number</span> <span class="o">-</span> <span class="mi">2</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">first_row_instructions</span>&#x000A;    <span class="s2">"6sc in magic ring"</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">first_row?</span>&#x000A;    <span class="n">row_number</span> <span class="o">==</span> <span class="mi">1</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">second_row_instructions</span>&#x000A;    <span class="s2">"[inc] x6"</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">def</span> <span class="nf">second_row?</span>&#x000A;    <span class="n">row_number</span> <span class="o">==</span> <span class="mi">2</span>&#x000A;  <span class="k">end</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre><a href="#takeaways"><h2 id="takeaways">Takeaways</h2></a>

<p>This was a fun little experiment, but not entirely useful on its own. However, what if 3D shapes in crochet also follow a similar pattern? Would I be able to generate spheres? Or cubes? Would a row count really be the ideal entry point for such shapes? It seems that more research and experimentation are needed!</p>
]]>
  </description>
  <author>
  </author>
  <guid>https://hashrocket.com/blog/posts/crafting-code-building-a-ruby-pattern-generator-for-a-crochet-circle</guid>
  <pubDate>Tue, 20 Jan 2026 09:00:00 -0500</pubDate>
  <category>Ruby</category>
</item>
<item>
  <title>
    <![CDATA[Building a (Very) Simple Responsive Search with Rails & Stimulus]]>
  </title>
  <link>https://hashrocket.com/blog/posts/building-a-simple-search-with-rails-stimulus</link>
  <description>
    <![CDATA[<p>Here&#39;s a simple and responsive search form I put together for a recent side project, using Hotwire&#39;s Stimulus framework and Rails with Turbo.</p>
<a href="#the-form"><h2 id="the-form">The Form</h2></a>

<p>Here&#39;s how the search input looks. It&#39;s a simple search form connected to a Stimulus controller. The input triggers the search function upon every input event. The debounce logic occurs in the Stimulus controller to make sure not too many requests are made to the server:</p>
<pre class="highlight erb" data-language="erb"><code><span class="cp">&lt;%=</span> <span class="n">form_with</span> <span class="ss">url: </span><span class="n">items_path</span><span class="p">,</span> <span class="ss">method: :get</span><span class="p">,</span>&#x000A;    <span class="ss">data: </span><span class="p">{</span> <span class="ss">controller: </span><span class="s2">"search"</span><span class="p">,</span> <span class="ss">turbo_frame: </span><span class="s2">"items_list"</span> <span class="p">}</span> <span class="k">do</span> <span class="o">|</span><span class="n">f</span><span class="o">|</span> <span class="cp">%&gt;</span>&#x000A;  <span class="cp">&lt;%=</span> <span class="n">f</span><span class="p">.</span><span class="nf">text_field</span> <span class="ss">:query</span><span class="p">,</span>&#x000A;      <span class="ss">placeholder: </span><span class="s2">"Search..."</span><span class="p">,</span>&#x000A;      <span class="ss">data: </span><span class="p">{</span>&#x000A;        <span class="ss">search_target: </span><span class="s2">"input"</span><span class="p">,</span>&#x000A;        <span class="ss">action: </span><span class="s2">"input-&gt;search#search"</span>&#x000A;      <span class="p">}</span> <span class="cp">%&gt;</span>&#x000A;  <span class="nt">&lt;button</span> <span class="na">type=</span><span class="s">"button"</span>&#x000A;      <span class="na">class=</span><span class="s">"hidden"</span>&#x000A;      <span class="na">data-search-target=</span><span class="s">"clearButton"</span>&#x000A;      <span class="na">data-action=</span><span class="s">"click-&gt;search#clear"</span><span class="nt">&gt;</span>&#x000A;    ×&#x000A;  <span class="nt">&lt;/button&gt;</span>&#x000A;<span class="cp">&lt;%</span> <span class="k">end</span> <span class="cp">%&gt;</span>&#x000A;</code></pre>
<p>There&#39;s an additional feature here: a button which appears in the search field whenever the field has a value. The button has a data-action attribute pointing to the clear action. If the button is clicked, the input value is cleared out.</p>
<a href="#the-stimulus-controller"><h2 id="the-stimulus-controller">The Stimulus Controller</h2></a>

<p>All we need to do here is submit the form with a debounce timer, handle the action for clicking the clear input button, and handle hiding the clear button if the input field has a value or not.</p>
<pre class="highlight javascript" data-language="javascript"><code><span class="k">export</span> <span class="k">default</span> <span class="kd">class</span> <span class="nc">extends</span> <span class="nx">Controller</span> <span class="p">{</span>&#x000A;  <span class="kd">static</span> <span class="nx">targets</span> <span class="o">=</span> <span class="p">[</span><span class="dl">"</span><span class="s2">input</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">clearButton</span><span class="dl">"</span><span class="p">]</span>&#x000A;&#x000A;  <span class="nf">connect</span><span class="p">()</span> <span class="p">{</span>&#x000A;    <span class="k">this</span><span class="p">.</span><span class="nf">toggleClear</span><span class="p">()</span>&#x000A;  <span class="p">}</span>&#x000A;&#x000A;  <span class="nf">search</span><span class="p">()</span> <span class="p">{</span>&#x000A;    <span class="k">this</span><span class="p">.</span><span class="nf">toggleClear</span><span class="p">()</span>&#x000A;    <span class="nf">clearTimeout</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">timeout</span><span class="p">)</span>&#x000A;&#x000A;    <span class="k">this</span><span class="p">.</span><span class="nx">timeout</span> <span class="o">=</span> <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>&#x000A;      <span class="k">this</span><span class="p">.</span><span class="nx">element</span><span class="p">.</span><span class="nf">requestSubmit</span><span class="p">()</span>&#x000A;    <span class="p">},</span> <span class="mi">300</span><span class="p">)</span>&#x000A;  <span class="p">}</span>&#x000A;&#x000A;  <span class="nf">clear</span><span class="p">()</span> <span class="p">{</span>&#x000A;    <span class="k">this</span><span class="p">.</span><span class="nx">inputTarget</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="dl">""</span>&#x000A;    <span class="k">this</span><span class="p">.</span><span class="nf">toggleClear</span><span class="p">()</span>&#x000A;    <span class="k">this</span><span class="p">.</span><span class="nx">element</span><span class="p">.</span><span class="nf">requestSubmit</span><span class="p">()</span>&#x000A;  <span class="p">}</span>&#x000A;&#x000A;  <span class="nf">toggleClear</span><span class="p">()</span> <span class="p">{</span>&#x000A;    <span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">inputTarget</span><span class="p">.</span><span class="nx">value</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>&#x000A;      <span class="k">this</span><span class="p">.</span><span class="nx">clearButtonTarget</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nf">remove</span><span class="p">(</span><span class="dl">"</span><span class="s2">hidden</span><span class="dl">"</span><span class="p">)</span>&#x000A;    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>&#x000A;      <span class="k">this</span><span class="p">.</span><span class="nx">clearButtonTarget</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="dl">"</span><span class="s2">hidden</span><span class="dl">"</span><span class="p">)</span>&#x000A;    <span class="p">}</span>&#x000A;  <span class="p">}</span>&#x000A;<span class="p">}</span>&#x000A;&#x000A;</code></pre><a href="#the-turbo-frame"><h2 id="the-turbo-frame">The Turbo Frame</h2></a>

<p>You&#39;ll notice the form targets an <code>items_list</code> turbo frame, which contains your search results in the view. When the Stimulus controller submits the form,  the content inside this frame gets replaced with the new search results. This is a pretty simple way to update the search results without full page reloads. I also wanted to avoid turbo streams for this implementation, because I find them a tad awkward sometimes.</p>
<pre class="highlight erb" data-language="erb"><code><span class="cp">&lt;%=</span> <span class="n">turbo_frame_tag</span> <span class="s2">"items_list"</span> <span class="k">do</span> <span class="cp">%&gt;</span>&#x000A;  <span class="cp">&lt;%</span> <span class="vi">@items</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span> <span class="cp">%&gt;</span>&#x000A;    <span class="cp">&lt;%=</span> <span class="n">render</span> <span class="n">item</span> <span class="cp">%&gt;</span>&#x000A;  <span class="cp">&lt;%</span> <span class="k">end</span> <span class="cp">%&gt;</span>&#x000A;<span class="cp">&lt;%</span> <span class="k">end</span> <span class="cp">%&gt;</span>&#x000A;</code></pre><a href="#the-rails-controller"><h2 id="the-rails-controller">The Rails Controller</h2></a>

<p>Standard index action with optional query filtering. Works with or without search params:</p>
<pre class="highlight ruby" data-language="ruby"><code><span class="k">def</span> <span class="nf">index</span>&#x000A;  <span class="c1"># you will probably paginate your results</span>&#x000A;  <span class="k">if</span> <span class="n">params</span><span class="p">[</span><span class="ss">:query</span><span class="p">].</span><span class="nf">present?</span>&#x000A;    <span class="vi">@items</span> <span class="o">=</span> <span class="no">Item</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="s2">"name LIKE ?"</span><span class="p">,</span> <span class="s2">"%</span><span class="si">#{</span><span class="n">params</span><span class="p">[</span><span class="ss">:query</span><span class="p">]</span><span class="si">}</span><span class="s2">%"</span><span class="p">)</span>&#x000A;  <span class="k">else</span>&#x000A;    <span class="vi">@items</span> <span class="o">=</span> <span class="no">Item</span><span class="p">.</span><span class="nf">all</span>&#x000A;  <span class="k">end</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre><a href="#conclusion"><h2 id="conclusion">Conclusion</h2></a>

<p>This is a nice simple starting point to build out some more complicated search features from. I&#39;d recommend using a pagination gem like <a href="https://github.com/ddnexus/pagy" target="_blank">pagy</a> to handle the search results while keep your view manageable.</p>
]]>
  </description>
  <author>
  </author>
  <guid>https://hashrocket.com/blog/posts/building-a-simple-search-with-rails-stimulus</guid>
  <pubDate>Tue, 09 Dec 2025 09:00:00 -0500</pubDate>
  <category>rails</category>
  <category>Hotwire</category>
  <category>Turbo</category>
  <category>Stimulus</category>
</item>
<item>
  <title>
    <![CDATA[Creating a Custom Mobile Integration for a Board Game Using Ruby on Rails]]>
  </title>
  <link>https://hashrocket.com/blog/posts/creating-a-custom-mobile-integration-for-a-board-game-using-ruby-on-rails</link>
  <description>
    <![CDATA[<p>Picture this: you find a charming old board game at a garage sale, bring it home, gather some friends—and snap. The 50-year-old plastic components break instantly. You search the web for help to replace this fun and unique game mechanic but there’s nothing to be found. So naturally, you roll up your sleeves and build your own mobile version. No one else does this? Just me? Well, in case you ever find yourself in a similar boat, I figured I would walk you through what I did when building my own mobile integration to the 1973 Parker Brothers classic <a href="https://boardgamegeek.com/boardgame/1436/billionaire" target="_blank">Billionare</a>.</p>

<p>As I said, right when I went to try out this cool &quot;new&quot; board game for the first time, the plastic ends of the <code>Analyzer</code> snapped. The analyzer is basically a plastic rod with 2  floating spinners on them. The spinners, when rotated, will either land with a red face or a green face. You then refer to the chart to see what action to take. I thought this was such a unique way to give a random effect in a game, without relying on dice. It essentially is just a random binary spinner that counts up to 4. Here is what it looks like for reference:</p>

<p><img src="https://i.imgur.com/rKJADkM.png" alt="image"></p>

<p>So, without any 4 sided die lying around, and not wanting to use a boring random number generator, my brain went right to what it knows best---Ruby on Rails.</p>

<p>So without further ado, here&#39;s what I did and how you can do something similar yourself.</p>
<a href="#creating-the-app"><h1 id="creating-the-app">Creating the app</h1></a>

<p>Sure, Rails could be considered overkill for such a simple app. But my idea is to allow this project to scale into a whole library of board game helpers that can all live within the same app. So, to start off I ran the old trusty <code>rails new</code> command, with a couple preferences I like to pass in to make my life easier as a developer.</p>
<pre class="highlight ruby" data-language="ruby"><code><span class="n">rails</span> <span class="n">new</span> <span class="n">board_game_library</span> <span class="o">--</span><span class="n">css</span> <span class="n">tailwind</span> <span class="o">--</span><span class="n">database</span><span class="o">=</span><span class="n">postgresql</span>&#x000A;</code></pre>
<p>One of the main tools I leveraged for this project besides Rails was <a href="https://www.tailwindcss.com" target="_blank">Tailwind</a>. Tailwind makes styling so easy. Here at Hashrocket Tailwind is pretty standard for all of us, so if you&#39;re interested in any tips or tricks, we have plenty of <a href="https://hashrocket.com/blog/search?utf8=%E2%9C%93&search_term=tailwind" target="_blank">blog posts</a> and <a href="https://til.hashrocket.com/?q=tailwind" target="_blank">TILs</a> worth checking out!</p>

<p>The app itself is very simple. Here is everything I needed to do:</p>

<ul>
<li>Create a view to host the Analyzer</li>
<li>Create a stimulus controller to &quot;scramble&quot; the Analyzer</li>
<li>Expose <code>ngrok</code> as a host in the config so I could access the Analyzer from my phone for free</li>
</ul>
<a href="#the-view"><h1 id="the-view">The View</h1></a>

<p>The view is nothing revolutionary. I just wanted a simple interface that gave me all the capabilities of the Analyzer from the board game. So, I needed 2 &quot;spinners&quot;, a way to spin them, and a chart that let you know what action to take.</p>
<a href="#spinners"><h2 id="spinners">Spinners</h2></a>

<p>Here is the html for the spinners. Simply 2 boxes side by side that have some <code>data-analyzer-target</code>s that are used by the stimulus controller. I also give them some default colors here to give the user an idea of what to expect when the app opens up.</p>
<pre class="highlight html" data-language="html"><code><span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"flex space-x-4"</span><span class="nt">&gt;</span>&#x000A;  <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"bg-red-400 w-1/2 h-24"</span> <span class="na">data-analyzer-target=</span><span class="s">"left"</span><span class="nt">&gt;</span>&#x000A;  <span class="nt">&lt;/div&gt;</span>&#x000A;&#x000A;  <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"bg-green-400 w-1/2 h-24"</span> <span class="na">data-analyzer-target=</span><span class="s">"right"</span><span class="nt">&gt;</span>&#x000A;  <span class="nt">&lt;/div&gt;</span>&#x000A;<span class="nt">&lt;/div&gt;</span>&#x000A;</code></pre><a href="#spinner-button"><h2 id="spinner-button">Spinner button</h2></a>

<p>Next we have the call to action, the &quot;spinner&quot; button. This is simply a nicely styled button that will call the <code>randomize</code> method on the stimulus controller.</p>
<pre class="highlight html" data-language="html"><code><span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"w-fit mx-auto mt-12"</span><span class="nt">&gt;</span>&#x000A;  <span class="nt">&lt;button</span> <span class="na">class=</span><span class="s">"border border-black rounded-md shadow-lg p-2"</span> <span class="na">data-action=</span><span class="s">"click-&gt;analyzer#randomize"</span> <span class="na">data-analyzer-target=</span><span class="s">"analyzer"</span><span class="nt">&gt;</span>&#x000A;    ANALYZE&#x000A;  <span class="nt">&lt;/button&gt;</span>&#x000A;<span class="nt">&lt;/div&gt;</span>&#x000A;</code></pre><a href="#actions-chart"><h2 id="actions-chart">Actions Chart</h2></a>

<p>The actions chart is just simply a bunch of elements of text, styled with html, along with a visual representation of the matching result. I&#39;ll give an example of one, but to save screen space on this post, I won&#39;t post the whole thing. Remember, you can always check out the source code <a href="https://github.com/vanillaHafer/billionare-analyzer/tree/main" target="_blank">here</a>.</p>
<pre class="highlight html" data-language="html"><code><span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"mt-12"</span><span class="nt">&gt;</span>&#x000A;  <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"w-fit mx-auto border border-black rounded-lg p-4 space-y-4"</span><span class="nt">&gt;</span>&#x000A;    <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"flex space-x-6"</span><span class="nt">&gt;</span>&#x000A;      <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"flex space-x-1 my-auto"</span><span class="nt">&gt;</span>&#x000A;        <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"w-8 h-4 bg-red-400 border border-black"</span><span class="nt">&gt;&lt;/div&gt;</span>&#x000A;        <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"w-8 h-4 bg-red-400 border border-black"</span><span class="nt">&gt;&lt;/div&gt;</span>&#x000A;      <span class="nt">&lt;/div&gt;</span>&#x000A;&#x000A;      <span class="nt">&lt;div&gt;</span>&#x000A;        Take investment you are analyzing.&#x000A;      <span class="nt">&lt;/div&gt;</span>&#x000A;    <span class="nt">&lt;/div&gt;</span>&#x000A;&#x000A;    <span class="c">&lt;!-- Other divs for the other outcomes --&gt;</span>&#x000A;<span class="nt">&lt;/div&gt;</span>&#x000A;</code></pre><a href="#the-controller"><h1 id="the-controller">The Controller</h1></a>

<p>For my stimulus controller I gave it the targets you saw in the examples above, as well as some functions to help us randomize. Here is a high-level view of a few of the methods. If you want to see the code for yourself, please check out the <a href="https://github.com/vanillaHafer/billionare-analyzer/tree/main" target="_blank">repo here</a>:</p>

<ul>
<li><code>randomize()</code> <strong>Called via the Analyze button. Kicks off the randomization, or *spinning</strong>*</li>
<li><code>disableButton()</code> <strong>Disables the Analyze button</strong></li>
<li><code>randomAnimation()</code> <strong>Starts animating the <em>left</em> and <em>right</em> spinners on the page</strong></li>
<li><code>makeDecision()</code> <strong>Determine via 50% chance for each spinner whether it will be red or green</strong></li>
<li><code>enableButton()</code> <strong>Enable the Analyze button when everything is finished</strong></li>
</ul>
<a href="#accessing-from-my-phone"><h1 id="accessing-from-my-phone">Accessing from my phone</h1></a>

<p>For the final piece of the puzzle, I wanted a way to easily view this from my phone so I didn&#39;t need to bring a big laptop or something to the table. So, for a simple and easy way to host this &quot;on the web&quot; without actually paying for a domain and setting all that up, I used the free version of <a href="https://ngrok.com/" target="_blank">ngrok</a>. Now, for this to work properly with your Rails app, you will need to add one line to your <code>config/application.rb</code> file.</p>
<pre class="highlight ruby" data-language="ruby"><code><span class="c1">#config/application.rb</span>&#x000A;&#x000A;<span class="c1"># ...</span>&#x000A;<span class="n">config</span><span class="p">.</span><span class="nf">hosts</span> <span class="o">&lt;&lt;</span> <span class="s2">".ngrok-free.app"</span>&#x000A;<span class="c1"># ...</span>&#x000A;</code></pre>
<p>This allows any hosts that end with <code>.ngrok-free.app</code> to have access to the application. This way you can start and stop ngrok as much as you wish, and you don&#39;t need to manually input the random host in every time.</p>

<p>Now simply start your rails server, start up ngrok, and access it from your phone or any other device and you should have something like this as your final product:</p>

<p><img src="https://i.imgur.com/9MZ63Vf.gif" width="250px"></p>
<a href="#conclusion"><h1 id="conclusion">Conclusion</h1></a>

<p>So, this ended up being a fun way to play the game with some friends. It worked exactly as expected and it was also really cool to be playing a 50 year old board game that had its own custom mobile integration, even if it was small and simple.</p>

<p>Don&#39;t want to go through all the work to make your own Rails app? Feel free to reach out to Hashrocket for your next project! Big or small, we&#39;d love to hear about how we can help you make the app you want to get the results you need!</p>

<p><hr /></p>

<p>Cover Photo by <a href="https://unsplash.com/@markusspiske" target="_blank">Thomas Buchhol</a> on <a href="https://unsplash.com/photos/a-close-up-of-a-bunch-of-buttons-on-a-table-0n7_eiAQZwA?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText"" target="_blank">Unsplash</a>. </p>

<p>Analyzer image provided by <a href="https://boardgamegeek.com/profile/mithras" target="_blank">Thomas Melinsky</a> on <a href="https://boardgamegeek.com/image/776001/billionaire" target="_blank">BoardGameGeek</a>.</p>
]]>
  </description>
  <author>
  </author>
  <guid>https://hashrocket.com/blog/posts/creating-a-custom-mobile-integration-for-a-board-game-using-ruby-on-rails</guid>
  <pubDate>Thu, 04 Dec 2025 09:00:00 -0500</pubDate>
  <category>Ruby</category>
  <category>Mobile</category>
  <category>rails</category>
  <category>board game app</category>
</item>
<item>
  <title>
    <![CDATA[Why Ruby is the Best Language for Advent of Code]]>
  </title>
  <link>https://hashrocket.com/blog/posts/why-ruby-is-the-best-language-for-advent-of-code</link>
  <description>
    <![CDATA[<p>It&#39;s the most wonderful time of the year - <del>Christmas</del> Advent of Code time! <a href="https://adventofcode.com/" target="_blank">Advent of Code</a> is an Advent Calendar style series of programming puzzles put out each year, starting on December 1st leading up to Christmas. The puzzles are super festive and ramp up in difficulty over the course of the month. Programmers of every level can participate, and in researching some of the more difficult problems you&#39;ll probably learn something cool! It&#39;s a great way to finish out the year.</p>

<p>I&#39;ve been taking part in Advent of Code since 2019 (I&#39;ve never completed a full year - and that&#39;s ok! You can participate for as long as it&#39;s fun and have the time) and have tried solving in multiple different languages - Advent is a great way to learn/skill up in a new language. But Ruby remains my favorite language in which to solve these puzzles.</p>

<p>Many of Ruby&#39;s strengths - its flexibility, robust standard library, and tooling make it the ideal language for Advent of Code.</p>
<a href="#flexibility"><h2 id="flexibility">Flexibility</h2></a>

<p>Ruby doesn&#39;t enforce any one way of writing code. Want to solve a problem with a procedural script? Go for it! Want to leverage object-oriented programming and send messages between classes? Can do! Want to write in a functional style and <code>map</code> and <code>zip</code> a data structure in one long chain? You can do that too! And you can mix and match paradigms between problems - whatever models each problem best.</p>

<p>Ruby&#39;s data structures are super flexible as well. In many of the problems, <code>Array</code> and <code>Hash</code> allow you to very quickly model solutions. But if you find a hash isn&#39;t quite cutting it and you don&#39;t want to upgrade it to a full class, you can use the <a href="https://docs.ruby-lang.org/en/master/Data.html" target="_blank"><code>Data</code></a> class to create value objects. This will lend you a bit more structure than a hash, and allow you to encapsulate some logic inside it without having to bring in the overhead of a <code>Class</code>. </p>

<p>Some of Ruby&#39;s flexibility - metaprogramming in particular - can be...unpopular... in larger production codebases. It&#39;s the &quot;magic&quot; that can lead to some nasty bugs. But Advent is a great place to play with those features. So go ahead, open up <code>String</code> and redefine some methods! Live life dangerously!</p>
<a href="#standard-library"><h2 id="standard-library">Standard Library</h2></a>

<p>The Ruby standard library is well-suited for Advent of Code. </p>

<p><code>Array</code> and <code>Hash</code> will probably be your best friends, and can get you pretty far in Advent. Because they both implement <a href="https://docs.ruby-lang.org/en/master/Enumerable.html" target="_blank"><code>Enumerable</code></a>, you can easily slice, dice, and transform these data structures with higher order functions like <code>map</code>, <code>reduce</code>, <code>zip</code>, and <code>each</code>. Convenience methods like <code>sum</code> and <code>chunk</code> mean you don&#39;t have to reinvent the wheel over and over again.</p>

<p>Set operations are built in - union (<code>|</code>), intersection (<code>&amp;</code>), difference (<code>-</code>), and XOR (<code>^</code>). I&#39;ve had to define these in other languages and while not difficult, it takes away from the actual solving of the puzzle.</p>

<p>The built in <a href="https://github.com/ruby/matrix" target="_blank"><code>matrix</code></a> gem can be a nicer way to represent grids than using arrays of arrays. It also includes some utilities to <code>transpose</code>, <code>inverse</code> and check if <code>symmetric</code> or upper or lower triangular. </p>

<p>The <a href="https://github.com/ruby/prime" target="_blank"><code>prime</code></a> gem (included in Ruby) has utilities for listing prime numbers, determining if a number is prime, and decomposing integers into their prime factorization. It&#39;s up to you if you want to write this kind of thing yourself or skip ahead to the more exciting parts of the puzzle.</p>
<a href="#tooling"><h2 id="tooling">Tooling</h2></a>

<p>Ruby&#39;s tooling makes it very easy to run, debug, and test your Advent solutions. Drop into an <a href="https://ruby.github.io/irb/" target="_blank"><code>irb</code></a> session, load in your code and play around with it. Within the REPL you can run the whole solution, or poke around at different methods to see if they&#39;re behaving as expected. Use <a href="https://github.com/ruby/debug" target="_blank"><code>debug</code></a> or <a href="https://github.com/pry/pry" target="_blank"><code>pry</code></a> to pause execution and check the current state of your variables. When you&#39;re exploring solutions and not quite sure where to go or what&#39;s going wrong, a REPL is a great way to think, try things out, and iterate.</p>

<p>You can also write tests for Advent of Code! (I do!) RSpec or Minitest are easy to add into a project. You can write unit tests for various helper methods. And you can write tests for each solution&#39;s output - great if you want to refactor or try out a performance optimization and ensure you still get the same solution.</p>
<a href="#summing-it-up"><h2 id="summing-it-up">Summing it up</h2></a>

<p>Sure, other languages have some or all of these features. But I don&#39;t know of any other language that has them all and is as pleasant to use as in Ruby.</p>

<p>Ruby was designed for programmer happiness. So wouldn&#39;t that make it the best choice for a fun programming challenge? The two were made for each other.</p>

<p>Photo by <a href="https://unsplash.com/@jocallen" target="_blank">Jocelyn Allen</a> on <a href="https://unsplash.com/photos/a-snowy-street-lined-with-wooden-buildings-N6B1DVpP36Q" target="_blank">Unsplash</a></p>
]]>
  </description>
  <author>
  </author>
  <guid>https://hashrocket.com/blog/posts/why-ruby-is-the-best-language-for-advent-of-code</guid>
  <pubDate>Tue, 02 Dec 2025 09:00:00 -0500</pubDate>
  <category>Ruby</category>
</item>
<item>
  <title>
    <![CDATA[How To Rev Up Your Rails Development with MCP]]>
  </title>
  <link>https://hashrocket.com/blog/posts/how-to-rev-up-your-rails-development-with-mcp</link>
  <description>
    <![CDATA[<p>Shipping new features on legacy Rails applications requires deep codebase context. The rails-mcp-server gem closes the gap between AI agents and your Rails projects, enabling more relevant code analysis and context aware refactoring suggestions. Whether you&#39;re dealing with tech debt in a brownfield application or building new greenfield features, this tool can help you move faster with confidence.</p>

<p>The Model Context Protocol (MCP) is a way to allow LLM models to interact with development environments and external tools. The rails-mcp-server gem is a Ruby implementation that enables LLMs to interact directly with Rails projects through MCP; Once you have it set up with an agent like claude or copilot, the model will have way more context about your app&#39;s architecture and removes a lot of the nonsense and guesswork associated with AI driven development. Check out the <a href="https://github.com/maquina-app/rails-mcp-server" target="_blank">repo here</a></p>

<p>I&#39;ll walk you through setting up the rails-mcp-server gem for your Rails projects.</p>
<a href="#installation"><h2 id="installation">Installation</h2></a>

<p>Installing the rails-mcp-server gem is simple like any ruby gem. Open your terminal and run. </p>

<p>Don&#39;t install it to your project&#39;s directory or add it to a Rails gemfile, this gem is meant to be installed globally and configured to run with multiple projects.</p>
<pre class="highlight shell" data-language="shell"><code>gem <span class="nb">install </span>rails-mcp-server&#x000A;</code></pre><a href="#config"><h2 id="config">Config</h2></a>
<a href="#setting-up-your-projects"><h3 id="setting-up-your-projects">Setting Up Your Projects</h3></a>

<p>Once you run the server for the first time, you can configure the gem to access your rails projects. The configuration location depends on your operating system:</p>

<ul>
<li><strong>macOS</strong>: <code>$XDG_CONFIG_HOME/rails-mcp</code> or <code>~/.config/rails-mcp</code> if <code>XDG_CONFIG_HOME</code> is not set</li>
<li><strong>Windows</strong>: <code>%APPDATA%\rails-mcp</code></li>
</ul>

<p>The first time the server runs, these directories will be created.</p>
<a href="#running-the-rails-mcp-server"><h2 id="running-the-rails-mcp-server">Running the Rails MCP Server</h2></a>

<p>The server can be ran in two modes, but for the purposes of this article we will stick to http mode, if you want to find out about STDIO mode check out the <a href="https://github.com/maquina-app/rails-mcp-server" target="_blank">docs here</a>. Running the server will create the config directory.</p>
<a href="#http-mode"><h3 id="http-mode">HTTP Mode</h3></a>

<p>HTTP mode runs as an HTTP server with JSON-RPC and Server-Sent Events (SSE) endpoints, perfect for web applications. Lets start it up.</p>
<pre class="highlight shell" data-language="shell"><code><span class="c"># Start on the default port (6029)</span>&#x000A;rails-mcp-server <span class="nt">--mode</span> http&#x000A;&#x000A;<span class="c"># Starting on a custom port</span>&#x000A;rails-mcp-server <span class="nt">--mode</span> http <span class="nt">-p</span> 8080&#x000A;</code></pre>
<p>When running in HTTP mode, the server can be accessed at these endpoints:</p>

<ul>
<li><strong>JSON-RPC endpoint</strong>: <code>http://localhost:&lt;port&gt;/mcp/messages</code></li>
<li><strong>SSE endpoint</strong>: <code>http://localhost:&lt;port&gt;/mcp/sse</code></li>
</ul>
<a href="#configuring-your-rails-projects"><h3 id="configuring-your-rails-projects">Configuring Your Rails Projects</h3></a>

<p>The server will also create a  <code>projects.yml</code> file in your config directory when you run it; to include your Rails projects, just provide a project name and a path to the directory:</p>
<pre class="highlight yaml" data-language="yaml"><code><span class="c1"># ~/.config/rails-mcp/projects.yml</span>&#x000A;<span class="na">test_app</span><span class="pi">:</span> <span class="s2">"</span><span class="s">~/projects/test_app"</span>&#x000A;</code></pre><a href="#integrating-with-claude-code"><h2 id="integrating-with-claude-code">Integrating with Claude Code</h2></a>

<p>Add the following to your <code>claude/config.json</code>:</p>
<pre class="highlight json" data-language="json"><code><span class="p">{</span><span class="w">&#x000A;  </span><span class="nl">"mcpServers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">&#x000A;    </span><span class="nl">"railsMcpServer"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">&#x000A;      </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ruby"</span><span class="p">,</span><span class="w">&#x000A;      </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"/full/path/to/rails-mcp-server/exe/rails-mcp-server"</span><span class="p">]</span><span class="w">&#x000A;    </span><span class="p">}</span><span class="w">&#x000A;  </span><span class="p">}</span><span class="w">&#x000A;</span><span class="p">}</span><span class="w">&#x000A;</span></code></pre>
<p>To find the full path to your rails-mcp-server executable:</p>
<pre class="highlight shell" data-language="shell"><code>which rails-mcp-server&#x000A;</code></pre>
<p>Restart the claude code session to refresh the new config. </p>

<p>With a new claude code session started, use the command <code>/mcp</code> to see that the session has access to the rails-mcp-server.</p>
<a href="#integrating-with-copilot"><h2 id="integrating-with-copilot">Integrating with Copilot</h2></a>

<p>Add the following to your <code>.vscode/mcp.json</code>:</p>
<pre class="highlight json" data-language="json"><code><span class="p">{</span><span class="w">&#x000A;  </span><span class="nl">"servers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">&#x000A;    </span><span class="nl">"railsMcpServer"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">&#x000A;      </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ruby"</span><span class="p">,</span><span class="w">&#x000A;      </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"/full/path/to/rails-mcp-server/exe/rails-mcp-server"</span><span class="p">]</span><span class="w">&#x000A;    </span><span class="p">}</span><span class="w">&#x000A;  </span><span class="p">}</span><span class="w">&#x000A;</span><span class="p">}</span><span class="w">&#x000A;</span></code></pre>
<p>To find the full path to your rails-mcp-server executable:</p>
<pre class="highlight shell" data-language="shell"><code>which rails-mcp-server&#x000A;</code></pre><a href="#using-the-rails-mcp-server"><h2 id="using-the-rails-mcp-server">Using The Rails MCP Server</h2></a>

<p>Once configured, your AI assistant can interact with your Rails projects using the provided tools, check out the docs too see the <a href="https://github.com/maquina-app/rails-mcp-server" target="_blank">provided tools</a>.</p>
<a href="#analyzing-your-code"><h3 id="analyzing-your-code">Analyzing Your Code</h3></a>

<p>A helpful thing to note is that you dont need to use the tools names specifically in LLM chats. You can simply refrence them in plain english. </p>
<pre class="highlight plaintext" data-language="plaintext"><code>load the Turbo guides and then show me how to refactor my blog feed with turbo streams&#x000A;</code></pre>
<p>Before you can use the MCP tools on your project, you will have to tell the mcp to switch to your project. <em>it will have to be a project name that is included in <code>projects.yml</code></em></p>

<p>Here&#39;s some of the tools can use in your LLM chats and how you might use them in a prompt:</p>
<pre class="highlight plaintext" data-language="plaintext"><code>project_info&#x000A;&#x000A;=&gt; break down each of the 3rd party integrations used in the project&#x000A;</code></pre><pre class="highlight plaintext" data-language="plaintext"><code>get_routes&#x000A;&#x000A;=&gt; which routes are being used for the messaging endpoints?&#x000A;</code></pre><pre class="highlight plaintext" data-language="plaintext"><code>analyze_models&#x000A;&#x000A;=&gt; how is the user model associated with the blogpost model?&#x000A;&#x000A;</code></pre>
<p>See how the prompt above says <code>load the turbo guides</code>, the LLM is smart enough to know that it will use the <code>load_guide</code> tool to respond to the prompt.</p>
<a href="#provide-even-more-context"><h2 id="provide-even-more-context">Provide Even More Context</h2></a>

<p>While the MCP server gives your AI assistants access to your code structure, providing more context about what you&#39;re working on helps generate better suggestions. Be sure to mention:</p>

<ul>
<li>The specific files you want to work with</li>
<li>A clear description of the feature or bug you&#39;re addressing</li>
<li>Any architectural or logistical constraints (obviously)</li>
</ul>
<a href="#conclusion"><h2 id="conclusion">Conclusion</h2></a>

<p>The rails-mcp-server gem fills in the context gap between AI and your Rails development workflow.</p>

<p>Whether you&#39;re working on a single application or switching between multiple Rails projects, the MCP server provides a way for AI, and hopefully you, to understand your codebase better.</p>
<a href="#extras"><h2 id="extras">Extras</h2></a>

<ul>
<li><a href="https://github.com/maquina-app/rails-mcp-server" target="_blank">GitHub Repository</a></li>
<li><a href="https://modelcontextprotocol.io" target="_blank">MCP Specification</a></li>
</ul>

<p>Reach out to Hashrocket if you need help modernizing your Rails Project! 🚀</p>
]]>
  </description>
  <author>
  </author>
  <guid>https://hashrocket.com/blog/posts/how-to-rev-up-your-rails-development-with-mcp</guid>
  <pubDate>Thu, 27 Nov 2025 09:00:00 -0500</pubDate>
  <category>Ruby</category>
  <category>rails</category>
  <category>mcp</category>
  <category>AI</category>
</item>
<item>
  <title>
    <![CDATA[Building a MCP Server in Elixir]]>
  </title>
  <link>https://hashrocket.com/blog/posts/building-a-mcp-server-in-elixir</link>
  <description>
    <![CDATA[<p>We’ve been working with MCP servers for a while, and this use case was a perfect opportunity to build out another one.</p>
<a href="#what-is-an-mcp-server-"><h2 id="what-is-an-mcp-server-">What is an MCP Server?</h2></a>

<p>A very simple way to put it is that <a href="https://modelcontextprotocol.io/docs/getting-started/intro" target="_blank">Model Context Protocol</a> is an &quot;API&quot; that your AI tooling can use to get external data or perform actions by interacting with your application. If it&#39;s just an API, that seems very easy to implement. Let&#39;s think about our use case then.</p>
<a href="#the-use-case"><h2 id="the-use-case">The Use Case</h2></a>

<p>The project is the TIL <a href="https://til.hashrocket.com/">https://til.hashrocket.com/</a> website where we developers usually write about our own learning experiences throughout small TIL posts. So our idea with the MCP server was to provide a way to simply create a TIL post from inside our AI tooling, and then maybe go to the TIL site and refine that idea.</p>

<p>These days we spend a lot of time inside our AI tools asking the most variety of questions, and we end up learning something from those interactions. Eventually, if we learn from an AI chat interaction, we&#39;d like to just grab that content and maybe <strong>scaffold</strong> it into a <strong>new TIL post</strong>.</p>

<p>This was the starting point of the project, and with that we started to take a look into libraries to achieve that. We found out that there were 2 libraries that were both forks of each other:</p>

<ul>
<li><a href="https://hexdocs.pm/hermes_mcp" target="_blank">Hermes MCP</a></li>
<li><a href="https://hexdocs.pm/anubis_mcp" target="_blank">Anubis MCP</a></li>
</ul>

<p>We played around a bit with Hermes but we ended up using Anubis in the end. I have to say it was a bit of a bumpy road. The documentation for both was not the best - we had some situations where the documentation was outdated or just simply not working - so follow our steps here if you want to setup an MCP server yourself.</p>
<a href="#mcp-server"><h2 id="mcp-server">MCP Server</h2></a>

<p>The first component to write is an MCP server, which is very simple. For now it&#39;s just:</p>
<pre class="highlight elixir" data-language="elixir"><code><span class="k">defmodule</span> <span class="no">Tilex</span><span class="o">.</span><span class="no">MCP</span><span class="o">.</span><span class="no">Server</span> <span class="k">do</span>&#x000A;  <span class="kn">use</span> <span class="no">Anubis</span><span class="o">.</span><span class="no">Server</span><span class="p">,</span> <span class="ss">name:</span> <span class="s2">"TIL"</span><span class="p">,</span> <span class="ss">version:</span> <span class="s2">"1.0.0"</span><span class="p">,</span> <span class="ss">capabilities:</span> <span class="p">[</span><span class="ss">:tools</span><span class="p">]</span>&#x000A;&#x000A;  <span class="n">component</span><span class="p">(</span><span class="no">Tilex</span><span class="o">.</span><span class="no">MCP</span><span class="o">.</span><span class="no">NewPost</span><span class="p">)</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre><a href="#mcp-tools"><h2 id="mcp-tools">MCP Tools</h2></a>

<p>So the first tool we made was to create a TIL Post:</p>
<pre class="highlight elixir" data-language="elixir"><code><span class="k">defmodule</span> <span class="no">Tilex</span><span class="o">.</span><span class="no">MCP</span><span class="o">.</span><span class="no">NewPost</span> <span class="k">do</span>&#x000A;  <span class="nv">@moduledoc</span> <span class="sd">"""&#x000A;  Create a new TIL ("Today I Learned") post.&#x000A;&#x000A;  TIL is a place for sharing something you've learned today with others.&#x000A;  """</span>&#x000A;&#x000A;  <span class="kn">use</span> <span class="no">Anubis</span><span class="o">.</span><span class="no">Server</span><span class="o">.</span><span class="no">Component</span><span class="p">,</span> <span class="ss">type:</span> <span class="ss">:tool</span>&#x000A;&#x000A;  <span class="n">schema</span> <span class="k">do</span>&#x000A;    <span class="n">field</span> <span class="ss">:title</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span>&#x000A;      <span class="ss">required:</span> <span class="no">true</span><span class="p">,</span>&#x000A;      <span class="ss">description:</span> <span class="s2">"Max 50 chars."</span>&#x000A;&#x000A;    <span class="n">field</span> <span class="ss">:body</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span>&#x000A;      <span class="ss">required:</span> <span class="no">true</span><span class="p">,</span>&#x000A;      <span class="ss">description:</span> <span class="s2">"Max 200 words in a Markdown format."</span>&#x000A;&#x000A;    <span class="n">field</span> <span class="ss">:channel</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span>&#x000A;      <span class="ss">required:</span> <span class="no">true</span><span class="p">,</span>&#x000A;      <span class="ss">description:</span> <span class="s2">"Post channel."</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="nv">@impl</span> <span class="no">true</span>&#x000A;  <span class="k">def</span> <span class="n">execute</span><span class="p">(%{</span><span class="ss">title:</span> <span class="n">title</span><span class="p">,</span> <span class="ss">body:</span> <span class="n">body</span><span class="p">,</span> <span class="ss">channel:</span> <span class="n">channel</span><span class="p">},</span> <span class="n">frame</span><span class="p">)</span> <span class="k">do</span>&#x000A;    <span class="o">...</span>&#x000A;  <span class="k">end</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre>
<p>The first bit of learning from here is that we need to be very generous in the description of what the tool does. In this case, we do that by adding a proper <code>@moduledoc</code>, and also by describing each field that we are accepting - which type they are, what validation applies, and more description. The idea with all these description fields is to give more information so if your AI tool decides to call this tool, it can break down those args properly.</p>

<p>With that in place, I expect to say something like:</p>

<blockquote>
<p>write me a TIL about the new React 19.2 Activity component</p>
</blockquote>

<p>and I want my AI tooling to find out that I mean to use our new MCP server tool to perform that operation. And after that, I want that same AI tooling to break down the information into the proper schema fields that we are expecting as our input for that tool. So far it seems a bit like magic, so let&#39;s keep going.</p>
<a href="#providing-data"><h2 id="providing-data">Providing Data</h2></a>

<p>So title and body I guess the AI could infer from my prompt or from the chat history. But the <strong>channel</strong> I think should be inferred from that too. The only issue is that we have a finite set of channels we could pick from. So can we expose which channels AI could use in order to create a new TIL post? The response was to provide a new tool for that. In this case, as we are just providing data to the AI, we decided to use a <code>resource</code> instead of a <code>tool</code> per se. So we changed our Server to:</p>
<pre class="highlight elixir" data-language="elixir"><code><span class="k">defmodule</span> <span class="no">Tilex</span><span class="o">.</span><span class="no">MCP</span><span class="o">.</span><span class="no">Server</span> <span class="k">do</span>&#x000A;  <span class="kn">use</span> <span class="no">Anubis</span><span class="o">.</span><span class="no">Server</span><span class="p">,</span> <span class="ss">name:</span> <span class="s2">"TIL"</span><span class="p">,</span> <span class="ss">version:</span> <span class="s2">"1.0.0"</span><span class="p">,</span> <span class="ss">capabilities:</span> <span class="p">[</span><span class="ss">:resources</span><span class="p">,</span> <span class="ss">:tools</span><span class="p">]</span>&#x000A;&#x000A;  <span class="n">component</span><span class="p">(</span><span class="no">Tilex</span><span class="o">.</span><span class="no">MCP</span><span class="o">.</span><span class="no">ListChannels</span><span class="p">)</span>&#x000A;  <span class="n">component</span><span class="p">(</span><span class="no">Tilex</span><span class="o">.</span><span class="no">MCP</span><span class="o">.</span><span class="no">NewPost</span><span class="p">)</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre>
<p>And we created our new resource as:</p>
<pre class="highlight elixir" data-language="elixir"><code><span class="k">defmodule</span> <span class="no">Tilex</span><span class="o">.</span><span class="no">MCP</span><span class="o">.</span><span class="no">ListChannels</span> <span class="k">do</span>&#x000A;  <span class="nv">@moduledoc</span> <span class="sd">"""&#x000A;  List channels of TIL posts.&#x000A;&#x000A;  Channels are used to group posts by the same topic.&#x000A;  """</span>&#x000A;&#x000A;  <span class="kn">use</span> <span class="no">Anubis</span><span class="o">.</span><span class="no">Server</span><span class="o">.</span><span class="no">Component</span><span class="p">,</span> <span class="ss">type:</span> <span class="ss">:resource</span><span class="p">,</span> <span class="ss">uri:</span> <span class="s2">"til:///channels"</span><span class="p">,</span> <span class="ss">name:</span> <span class="s2">"list_channels"</span><span class="p">,</span> <span class="ss">mime_type:</span> <span class="s2">"application/json"</span>&#x000A;&#x000A;  <span class="n">alias</span> <span class="no">Anubis</span><span class="o">.</span><span class="no">Server</span><span class="o">.</span><span class="no">Response</span>&#x000A;&#x000A;  <span class="nv">@impl</span> <span class="no">true</span>&#x000A;  <span class="k">def</span> <span class="n">read</span><span class="p">(</span><span class="n">_input</span><span class="p">,</span> <span class="n">frame</span><span class="p">)</span> <span class="k">do</span>&#x000A;    <span class="n">channels</span> <span class="o">=</span> <span class="n">list_channels</span><span class="p">()</span>&#x000A;    <span class="n">resp</span> <span class="o">=</span> <span class="no">Response</span><span class="o">.</span><span class="n">json</span><span class="p">(</span><span class="no">Response</span><span class="o">.</span><span class="n">resource</span><span class="p">(),</span> <span class="n">channels</span><span class="p">)</span>&#x000A;    <span class="p">{</span><span class="ss">:reply</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="n">frame</span><span class="p">}</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">defp</span> <span class="n">list_channels</span><span class="p">()</span> <span class="k">do</span>&#x000A;    <span class="o">...</span>&#x000A;  <span class="k">end</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre>
<p>The implementation of the <code>list_channels</code> function is just an Ecto Repo query, nothing more than that. The trick here is to tell the AI how to relate that resource with the input to be used in the other tool. This way we changed our schema definition in the <code>NewPost</code> to:</p>
<pre class="highlight elixir" data-language="elixir"><code><span class="k">defmodule</span> <span class="no">Tilex</span><span class="o">.</span><span class="no">MCP</span><span class="o">.</span><span class="no">NewPost</span> <span class="k">do</span>&#x000A;  <span class="nv">@moduledoc</span> <span class="sd">"""&#x000A;  Create a new TIL ("Today I Learned") post.&#x000A;&#x000A;  TIL is a place for sharing something you've learned today with others.&#x000A;  """</span>&#x000A;&#x000A;  <span class="kn">use</span> <span class="no">Anubis</span><span class="o">.</span><span class="no">Server</span><span class="o">.</span><span class="no">Component</span><span class="p">,</span> <span class="ss">type:</span> <span class="ss">:tool</span>&#x000A;&#x000A;  <span class="n">schema</span> <span class="k">do</span>&#x000A;    <span class="n">field</span> <span class="ss">:title</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span>&#x000A;      <span class="ss">required:</span> <span class="no">true</span><span class="p">,</span>&#x000A;      <span class="ss">description:</span> <span class="s2">"Max 50 chars."</span>&#x000A;&#x000A;    <span class="n">field</span> <span class="ss">:body</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span>&#x000A;      <span class="ss">required:</span> <span class="no">true</span><span class="p">,</span>&#x000A;      <span class="ss">description:</span> <span class="s2">"Max 200 words in a Markdown format."</span>&#x000A;&#x000A;    <span class="n">field</span> <span class="ss">:channel</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span>&#x000A;      <span class="ss">required:</span> <span class="no">true</span><span class="p">,</span>&#x000A;      <span class="ss">description:</span> <span class="s2">"Channel is given by the list_channels MCP resource from this same server."</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="nv">@impl</span> <span class="no">true</span>&#x000A;  <span class="k">def</span> <span class="n">execute</span><span class="p">(%{</span><span class="ss">title:</span> <span class="n">title</span><span class="p">,</span> <span class="ss">body:</span> <span class="n">body</span><span class="p">,</span> <span class="ss">channel:</span> <span class="n">channel</span><span class="p">},</span> <span class="n">frame</span><span class="p">)</span> <span class="k">do</span>&#x000A;    <span class="o">...</span>&#x000A;  <span class="k">end</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre>
<p>As you can see, this is pretty much what we had before, but we are telling the AI to look for channels in the <code>list_channels MCP resource</code>. That&#39;s it, that simple.</p>
<a href="#authenticating-into-your-mcp-server"><h2 id="authenticating-into-your-mcp-server">Authenticating into Your MCP Server</h2></a>

<p>This was the topic we had the most difficulty with (if any...) in this whole process, and this is because the docs were not great - at least they were not at the time we were doing this research. We followed the <a href="https://hexdocs.pm/anubis_mcp/recipes.html#authentication-authorization" target="_blank">Authentication Recipe</a> from the library we are using, and that did not work well.</p>

<p>The <strong>main issue</strong> we had was that the Server instance was shared across all users. This way, if 2 users connect to their own AI tools configured with that MCP at the same time, then all posts coming from those users would be assigned to one of these users. The last one to connects takes the credits. That&#39;s not good. So we realized that we should not assign the authenticated user in the Server module, but get the current user from the authentication header directly in the tools/resources executions. So now we have:</p>
<pre class="highlight elixir" data-language="elixir"><code><span class="k">defmodule</span> <span class="no">Tilex</span><span class="o">.</span><span class="no">MCP</span><span class="o">.</span><span class="no">NewPost</span> <span class="k">do</span>&#x000A;  <span class="nv">@moduledoc</span> <span class="sd">"""&#x000A;  Create a new TIL ("Today I Learned") post.&#x000A;&#x000A;  TIL is a place for sharing something you've learned today with others.&#x000A;  """</span>&#x000A;&#x000A;  <span class="kn">use</span> <span class="no">Anubis</span><span class="o">.</span><span class="no">Server</span><span class="o">.</span><span class="no">Component</span><span class="p">,</span> <span class="ss">type:</span> <span class="ss">:tool</span>&#x000A;&#x000A;  <span class="kn">import</span> <span class="no">Ecto</span><span class="o">.</span><span class="no">Query</span><span class="p">,</span> <span class="ss">only:</span> <span class="p">[</span><span class="ss">from:</span> <span class="mi">2</span><span class="p">]</span>&#x000A;&#x000A;  <span class="n">alias</span> <span class="no">Anubis</span><span class="o">.</span><span class="no">Server</span><span class="o">.</span><span class="no">Response</span>&#x000A;  <span class="n">alias</span> <span class="no">Tilex</span><span class="o">.</span><span class="no">Blog</span><span class="o">.</span><span class="no">User</span>&#x000A;  <span class="n">alias</span> <span class="no">Tilex</span><span class="o">.</span><span class="no">Blog</span><span class="o">.</span><span class="no">Post</span>&#x000A;&#x000A;  <span class="n">schema</span> <span class="k">do</span>&#x000A;    <span class="n">field</span> <span class="ss">:title</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span>&#x000A;      <span class="ss">required:</span> <span class="no">true</span><span class="p">,</span>&#x000A;      <span class="ss">description:</span> <span class="s2">"Max 50 chars."</span>&#x000A;&#x000A;    <span class="n">field</span> <span class="ss">:body</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span>&#x000A;      <span class="ss">required:</span> <span class="no">true</span><span class="p">,</span>&#x000A;      <span class="ss">description:</span> <span class="s2">"Max 200 words in a Markdown format."</span>&#x000A;&#x000A;    <span class="n">field</span> <span class="ss">:channel</span><span class="p">,</span> <span class="ss">:string</span><span class="p">,</span>&#x000A;      <span class="ss">required:</span> <span class="no">true</span><span class="p">,</span>&#x000A;      <span class="ss">description:</span> <span class="s2">"Channel is given by the list_channels MCP resource from this same server."</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="nv">@impl</span> <span class="no">true</span>&#x000A;  <span class="k">def</span> <span class="n">execute</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">frame</span><span class="p">)</span> <span class="k">do</span>&#x000A;    <span class="n">resp</span> <span class="o">=</span> <span class="no">Response</span><span class="o">.</span><span class="n">tool</span><span class="p">()</span>&#x000A;&#x000A;    <span class="n">resp</span> <span class="o">=</span>&#x000A;      <span class="n">with</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">current_user</span><span class="p">}</span> <span class="o">&lt;-</span> <span class="n">get_current_user</span><span class="p">(</span><span class="n">frame</span><span class="p">),</span>&#x000A;           <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">channel</span><span class="p">}</span> <span class="o">&lt;-</span> <span class="n">get_channel</span><span class="p">(</span><span class="n">input</span><span class="p">),</span>&#x000A;           <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="p">%</span><span class="no">Post</span><span class="p">{}</span> <span class="o">=</span> <span class="n">post</span><span class="p">}</span> <span class="o">&lt;-</span> <span class="n">create_til_post</span><span class="p">(</span><span class="n">current_user</span><span class="p">,</span> <span class="n">channel</span><span class="p">,</span> <span class="n">input</span><span class="p">)</span> <span class="k">do</span>&#x000A;&#x000A;        <span class="no">Response</span><span class="o">.</span><span class="n">resource_link</span><span class="p">(</span><span class="n">resp</span><span class="p">,</span> <span class="sx">~p"/post/#{post}"</span><span class="p">,</span> <span class="s2">"til-post"</span><span class="p">,</span>&#x000A;          <span class="ss">description:</span> <span class="s2">"Open this link in order to review the TIL and publish it!"</span>&#x000A;        <span class="p">)</span>&#x000A;      <span class="k">else</span>&#x000A;        <span class="p">{</span><span class="ss">:error</span><span class="p">,</span> <span class="n">reason</span><span class="p">}</span> <span class="o">-&gt;</span>&#x000A;          <span class="no">Response</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="n">resp</span><span class="p">,</span> <span class="s2">"ERROR =&gt; </span><span class="si">#{</span><span class="n">reason</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>&#x000A;      <span class="k">end</span>&#x000A;&#x000A;    <span class="p">{</span><span class="ss">:reply</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="n">frame</span><span class="p">}</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">defp</span> <span class="n">get_current_user</span><span class="p">(</span><span class="n">frame</span><span class="p">)</span> <span class="k">do</span>&#x000A;    <span class="n">headers</span> <span class="o">=</span> <span class="no">Enum</span><span class="o">.</span><span class="n">into</span><span class="p">(</span><span class="n">frame</span><span class="o">.</span><span class="n">transport</span><span class="o">.</span><span class="n">req_headers</span><span class="p">,</span> <span class="p">%{})</span>&#x000A;    <span class="n">signed_token</span> <span class="o">=</span> <span class="n">headers</span><span class="p">[</span><span class="s2">"x-api-key"</span><span class="p">]</span>&#x000A;&#x000A;    <span class="n">with</span> <span class="s2">""</span> <span class="o">&lt;&gt;</span> <span class="n">_</span> <span class="o">&lt;-</span> <span class="n">signed_token</span><span class="p">,</span>&#x000A;         <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">mcp_api_key</span><span class="p">}</span> <span class="o">&lt;-</span> <span class="no">User</span><span class="o">.</span><span class="n">verify_mcp_api_key</span><span class="p">(</span><span class="n">signed_token</span><span class="p">)</span> <span class="k">do</span>&#x000A;      <span class="no">Tilex</span><span class="o">.</span><span class="no">Repo</span><span class="o">.</span><span class="n">one</span><span class="p">(</span><span class="n">from</span> <span class="n">d</span> <span class="ow">in</span> <span class="no">User</span><span class="p">,</span> <span class="ss">where:</span> <span class="n">d</span><span class="o">.</span><span class="n">mcp_api_key</span> <span class="o">==</span> <span class="o">^</span><span class="n">mcp_api_key</span><span class="p">)</span>&#x000A;    <span class="k">else</span>&#x000A;      <span class="n">_</span> <span class="o">-&gt;</span> <span class="no">nil</span>&#x000A;    <span class="k">end</span>&#x000A;    <span class="o">|&gt;</span> <span class="k">case</span> <span class="k">do</span>&#x000A;      <span class="no">nil</span> <span class="o">-&gt;</span> <span class="p">{</span><span class="ss">:error</span><span class="p">,</span> <span class="s2">"User is not authenticated to create TILs"</span><span class="p">}</span>&#x000A;      <span class="p">%</span><span class="no">User</span><span class="p">{}</span> <span class="o">=</span> <span class="n">user</span> <span class="o">-&gt;</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">user</span><span class="p">}</span>&#x000A;    <span class="k">end</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">defp</span> <span class="n">get_channel</span><span class="p">(%{</span><span class="ss">channel:</span> <span class="n">channel</span><span class="p">})</span> <span class="k">do</span>&#x000A;    <span class="o">...</span>&#x000A;  <span class="k">end</span>&#x000A;&#x000A;  <span class="k">defp</span> <span class="n">create_til_post</span><span class="p">(%</span><span class="no">User</span><span class="p">{}</span> <span class="o">=</span> <span class="n">current_user</span><span class="p">,</span> <span class="n">channel</span><span class="p">,</span> <span class="p">%{</span><span class="ss">title:</span> <span class="n">title</span><span class="p">,</span> <span class="ss">body:</span> <span class="n">body</span><span class="p">})</span> <span class="k">do</span>&#x000A;    <span class="o">...</span>&#x000A;  <span class="k">end</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre>
<p>To be honest, this is the gist of it. Again the implementation of <code>get_channel</code> and <code>create_til_post</code> are just Ecto Repo queries and Ecto Changeset insert.</p>
<a href="#providing-a-way-to-generate-the-mcp-tokens"><h2 id="providing-a-way-to-generate-the-mcp-tokens">Providing a Way to Generate the MCP Tokens</h2></a>

<p>We ended up adding into our own User profile page a new feature to generate a new MCP token:</p>

<p><img src="https://i.imgur.com/jb3WBTe_d.webp?maxwidth=1520&fidelity=grand" alt="MCP Key Generate" title="MCP Key Generate"></p>

<p>This way, when we click to generate a new token, we also get:</p>

<p><img src="https://i.imgur.com/kutn8K6.webp?maxwidth=1520&fidelity=grand" alt="MCP Key" title="MCP Key"></p>

<p>Right now, among all devs we are using 2 main AI tools: Cursor and Claude Code, so we ended up adapting that page to give the devs final instructions to setup that MCP on their preferred tool.</p>

<p>Another point to note here is that we used <code>Phoenix.Token</code> to sign and verify tokens for the authentication header. That&#39;s out of the scope of this post but again very nice that the this feature is available already to be used.</p>
<a href="#let-39-s-see-it-in-action"><h2 id="let-39-s-see-it-in-action">Let&#39;s see it in action</h2></a>

<p>So now I can open my claude code and input this type of prompt:</p>

<blockquote>
<p>write me a TIL about the most common bug in JS</p>
</blockquote>

<p>And this is the output that I got:</p>
<pre class="highlight plaintext" data-language="plaintext"><code>⏺ I'll help you write a TIL post about the most common bug in JavaScript. Let me first check if there's already a draft file, then I'll create the post using the TIL system.&#x000A;&#x000A;⏺ Read(til_most_common_js_bug.md)&#x000A;  ⎿  Read 1 line&#x000A;&#x000A;⏺ listMcpResources(List MCP resources from server "til")&#x000A;  ⎿  [&#x000A;       {&#x000A;         "name": "list_channels",&#x000A;     … +7 lines (ctrl+o to expand)&#x000A;&#x000A;⏺ readMcpResource(Read resource "til:///channels" from server "til")&#x000A;  ⎿  {&#x000A;       "contents": [&#x000A;         {&#x000A;     … +6 lines (ctrl+o to expand)&#x000A;&#x000A;⏺ Now I'll create a TIL post about one of the most common bugs in JavaScript - the closure in loops issue:&#x000A;&#x000A;⏺ til - new_post (MCP)(title: "The Classic Closure-in-Loop Bug", body: "One of the most common bugs in JavaScript is the closure-in-loop problem. Here's the classic example:\n\n```javascript\nfor (var i = 0; i &lt; 5; i++) {\n  setTimeout(() =&gt; console.log(i), 100);\n}\n// Prints: 5, 5, 5, 5, 5\n```\n\nWhy? Because `var` is function-scoped, not block-scoped. All&#x000A;                       callbacks share the same `i` reference, which equals 5 after the loop completes.\n\n**Solutions:**\n\n1. Use `let` (block-scoped):\n```javascript\nfor (let i = 0; i &lt; 5; i++) {\n  setTimeout(() =&gt; console.log(i), 100);\n}\n// Prints: 0, 1, 2, 3, 4\n```\n\n2. Use an IIFE to capture the value:\n```javascript\nfor (var i = 0; i &lt; 5; i++)&#x000A;                      {\n  (function(j) {\n    setTimeout(() =&gt; console.log(j), 100);\n  })(i);\n}\n```\n\nThis bug stems from misunderstanding how closures capture variables by reference, not by value. Modern JavaScript developers should prefer `let` and `const` to avoid this entirely.", channel: "javascript")&#x000A;  ⎿  [Resource link: til-post] http://til.hashrocket.com/posts/e4pehjsbkt-the-classic-closure-in-loop-bug/edit (Open this link in order to review the TIL and publish it!)&#x000A;&#x000A;⏺ Done! I've created a TIL post about the classic closure-in-loop bug in JavaScript - one of the most common bugs that trips up developers.&#x000A;&#x000A;  The post covers:&#x000A;  - Why the bug happens (function-scoped var vs block-scoped let)&#x000A;  - A clear example showing the problem&#x000A;  - Two solutions: using let (modern approach) and IIFE (legacy approach)&#x000A;&#x000A;  You can review and publish it here: http://til.hashrocket.com/posts/e4pehjsbkt-the-classic-closure-in-loop-bug/edit&#x000A;</code></pre>
<p>We can see that uppon my prompt Claude decided to call <code>list_channels</code> to it&#39;s gathering of content and finally call the <code>new_post</code> tool. In our happy path the server return a <code>Response.resource_link</code> with the newly created TIL and in the text we are suggesting the developer to go to that link, review the whole content and if that&#39;s ok to publish it.</p>
<a href="#final-thoughts"><h2 id="final-thoughts">Final Thoughts</h2></a>

<p>TIL started over 10 years ago as a Ruby on Rails project, and a few years ago that project was converted to be an Elixir Phoenix project. At that time, the developers that converted it wanted to experiment more with Elixir, so they migrated the project with the basic features. So there was 1 feature in particular that was left behind that I think we had to bring it back due to the use of this MCP server. It was the ability to create a TIL post as a draft. When we did this work, we wanted that feature back so badly.</p>

<p>The main issue is that no matter how we tell AI tooling to do their tasks the execution itself is always unpredictable. With the interactions we had so far to test all these new post MCP server we noticed that sometimes the TIL content created by the AI is ok but it&#39;s not the way we&#39;d write, it does not expose the element of learning a particular thing in the post. Sometimes the examples are too complex and take the focus off. So we end up having to <strong>adjust the content in 100% of times</strong>. This was the main driver for us to put that draft posts feature back.</p>

<p>Some other times the post created are completely hallucinated trash. When it happens it&#39;s usually that our prompt was not adequate enough for AI to understand, so we&#39;d like to prompt again, give more info and try to create again. We could create a new MCP server to update the last TIL or so, but we end up implementing a feature to delete a TIL. So when the user opens the TIL post page to check the content, now they can delete that post and go back to their AI and try again with a new post.</p>

<p>So in the end, implementing that <strong>MCP server was very easy</strong>. The power of scaffolding an idea into a new TIL is very helpful. But as the data now comes from an unpredictable source you may need to consider the implication of this new MCP servers. <strong>Adding a moderation</strong> to this process is quite important now.</p>

<hr>
<a href="#we-can-help"><h2 id="we-can-help">We Can Help</h2></a>

<p>At Hashrocket, we have deep expertise in Elixir, Phoenix, and integrating modern AI capabilities into production applications. Whether you&#39;re looking to build custom MCP servers, modernize your existing applications with Elixir, or explore how AI can enhance your development workflow, we&#39;d love to help. Our team has decades of combined experience building robust, scalable web applications and we&#39;re always excited to tackle interesting technical challenges. <a href="https://hashrocket.com/contact" target="_blank">Get in touch with us</a> to discuss how we can help with your next project.</p>
]]>
  </description>
  <author>
  </author>
  <guid>https://hashrocket.com/blog/posts/building-a-mcp-server-in-elixir</guid>
  <pubDate>Tue, 25 Nov 2025 09:00:00 -0500</pubDate>
  <category>Elixir</category>
  <category>Process</category>
  <category>Development</category>
  <category>AI</category>
</item>
<item>
  <title>
    <![CDATA[Speed Up Your Rails App by Squashing N+1 Queries]]>
  </title>
  <link>https://hashrocket.com/blog/posts/speed-up-your-rails-app-by-squashing-n-1-queries</link>
  <description>
    <![CDATA[<p>N+1 queries are one of the most common performance killers in Rails apps, but also one of the easiest to fix. In this post, we&#39;ll see how a single line of code can reduce 1,101 database queries down to 3.</p>

<p>N+1s occur in rails when you have associated <code>ActiveRecord</code> models, and iterate over one model while accessing fields on the associated records. This might be easier to explain with an example.</p>

<p>Let&#39;s say you have the following <code>ActiveRecord</code> models:</p>
<pre class="highlight ruby" data-language="ruby"><code><span class="k">class</span> <span class="nc">Author</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>&#x000A;  <span class="n">has_many</span> <span class="ss">:posts</span>&#x000A;<span class="k">end</span>&#x000A;&#x000A;<span class="k">class</span> <span class="nc">Post</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>&#x000A;  <span class="n">belongs_to</span> <span class="ss">:author</span>&#x000A;  <span class="n">has_many</span> <span class="ss">:tags</span>&#x000A;<span class="k">end</span>&#x000A;&#x000A;<span class="k">class</span> <span class="nc">Tag</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>&#x000A;  <span class="n">belongs_to</span> <span class="ss">:post</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre>
<p>An <em>author</em> has many <em>posts</em>, and each <em>post</em> can have many <em>tags</em>. If we want to list each author and their blog posts like below, then we&#39;ll make <strong>1</strong> query to the database for the authors, and then <strong>another query for each</strong> author&#39;s blog posts. For N authors, that&#39;s 1 + N queries to the database. </p>
<pre class="highlight ruby" data-language="ruby"><code><span class="no">Author</span><span class="p">.</span><span class="nf">take</span><span class="p">(</span><span class="mi">3</span><span class="p">).</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">author</span><span class="o">|</span>&#x000A;  <span class="nb">puts</span> <span class="n">author</span><span class="p">.</span><span class="nf">name</span>&#x000A;  <span class="n">author</span><span class="p">.</span><span class="nf">posts</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">post</span><span class="o">|</span>&#x000A;    <span class="nb">puts</span> <span class="n">post</span><span class="p">.</span><span class="nf">title</span>&#x000A;  <span class="k">end</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre>
<p>We can see all the queries in the logs - 1 query for authors, then 3 queries for each author&#39;s blog posts:</p>
<pre class="highlight ruby" data-language="ruby"><code><span class="no">Author</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">31.4</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"authors"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"authors"</span>&#x000A;<span class="no">Post</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">2.4</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"posts"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"posts"</span> <span class="no">WHERE</span> <span class="s2">"posts"</span><span class="o">.</span><span class="s2">"author_id"</span> <span class="o">=</span> <span class="mi">163</span>&#x000A;<span class="no">Post</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">0.4</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"posts"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"posts"</span> <span class="no">WHERE</span> <span class="s2">"posts"</span><span class="o">.</span><span class="s2">"author_id"</span> <span class="o">=</span> <span class="mi">164</span>&#x000A;<span class="no">Post</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">0.1</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"posts"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"posts"</span> <span class="no">WHERE</span> <span class="s2">"posts"</span><span class="o">.</span><span class="s2">"author_id"</span> <span class="o">=</span> <span class="mi">165</span>&#x000A;</code></pre>
<p>In small doses this isn&#39;t a big deal - these are small, quick queries. However, as soon as the data gets larger, the queries more complex, or the cardinality of these queries grow, then things can really slow down. </p>

<p>Let&#39;s look at a case study, where we start with an unoptimized query with 2 layers of N+1s, and see how much faster we can make it.</p>
<a href="#case-1-the-unoptimized-query"><h2 id="case-1-the-unoptimized-query">Case 1 - The Unoptimized Query</h2></a>

<p>Let&#39;s take the example above, and add another layer to it. On our authors index page, we want to list each author, then each of their blog posts, including their associated tags. With a totally unoptimized query, our controller action and view will look like this:</p>
<pre class="highlight ruby" data-language="ruby"><code><span class="k">class</span> <span class="nc">AuthorsController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>&#x000A;  <span class="k">def</span> <span class="nf">index</span>&#x000A;    <span class="vi">@authors</span> <span class="o">=</span> <span class="no">Author</span><span class="p">.</span><span class="nf">all</span><span class="p">.</span><span class="nf">order</span><span class="p">(</span><span class="ss">:name</span><span class="p">)</span>&#x000A;  <span class="k">end</span>&#x000A;<span class="k">end</span>&#x000A;&#x000A;<span class="c1"># app/views/authors/index.html.erb</span>&#x000A;<span class="o">&lt;</span><span class="n">h1</span><span class="o">&gt;</span><span class="no">Authors</span><span class="o">&lt;</span><span class="sr">/h1&gt;&#x000A;&#x000A;&lt;% @authors.each do |author| %&gt;&#x000A;  &lt;h2&gt;&lt;%= author.name %&gt;&lt;/</span><span class="n">h2</span><span class="o">&gt;</span>&#x000A;  <span class="o">&lt;</span><span class="n">ul</span><span class="o">&gt;</span>&#x000A;    <span class="o">&lt;</span><span class="sx">% author.posts.each </span><span class="k">do</span> <span class="o">|</span><span class="n">post</span><span class="o">|</span> <span class="sx">%&gt;&#x000A;      &lt;li&gt;</span><span class="o">&lt;</span><span class="sx">%= post.title %&gt; - &lt;%=</span> <span class="n">post</span><span class="p">.</span><span class="nf">tags</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:name</span><span class="p">).</span><span class="nf">join</span><span class="p">(</span><span class="s2">", "</span><span class="p">)</span> <span class="o">%&gt;&lt;</span><span class="sr">/li&gt;&#x000A;    &lt;% end %&gt;&#x000A;  &lt;/u</span><span class="n">l</span><span class="o">&gt;</span>&#x000A;<span class="o">&lt;</span><span class="sx">% end </span><span class="o">%&gt;</span>&#x000A;</code></pre>
<p>As we can see in the logs, there&#39;s an explosion of queries - for 3 authors with 3 blog posts each with 3 tags, that&#39;s 13 queries!</p>
<pre class="highlight ruby" data-language="ruby"><code><span class="no">Author</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">1.1</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"authors"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"authors"</span> <span class="no">ORDER</span> <span class="no">BY</span> <span class="s2">"authors"</span><span class="o">.</span><span class="s2">"name"</span> <span class="no">ASC</span>&#x000A;<span class="no">Post</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">0.9</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"posts"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"posts"</span> <span class="no">WHERE</span> <span class="s2">"posts"</span><span class="o">.</span><span class="s2">"author_id"</span> <span class="o">=</span> <span class="mi">163</span> &#x000A;<span class="no">Tag</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">1.1</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"tags"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"tags"</span> <span class="no">WHERE</span> <span class="s2">"tags"</span><span class="o">.</span><span class="s2">"post_id"</span> <span class="o">=</span> <span class="mi">631</span> &#x000A;<span class="no">Tag</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">0.2</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"tags"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"tags"</span> <span class="no">WHERE</span> <span class="s2">"tags"</span><span class="o">.</span><span class="s2">"post_id"</span> <span class="o">=</span> <span class="mi">632</span> &#x000A;<span class="no">Tag</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">0.2</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"tags"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"tags"</span> <span class="no">WHERE</span> <span class="s2">"tags"</span><span class="o">.</span><span class="s2">"post_id"</span> <span class="o">=</span> <span class="mi">633</span> &#x000A;<span class="no">Post</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">0.3</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"posts"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"posts"</span> <span class="no">WHERE</span> <span class="s2">"posts"</span><span class="o">.</span><span class="s2">"author_id"</span> <span class="o">=</span> <span class="mi">172</span> &#x000A;<span class="no">Tag</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">0.2</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"tags"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"tags"</span> <span class="no">WHERE</span> <span class="s2">"tags"</span><span class="o">.</span><span class="s2">"post_id"</span> <span class="o">=</span> <span class="mi">716</span> &#x000A;<span class="no">Tag</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">0.3</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"tags"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"tags"</span> <span class="no">WHERE</span> <span class="s2">"tags"</span><span class="o">.</span><span class="s2">"post_id"</span> <span class="o">=</span> <span class="mi">717</span> &#x000A;<span class="no">Tag</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">0.2</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"tags"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"tags"</span> <span class="no">WHERE</span> <span class="s2">"tags"</span><span class="o">.</span><span class="s2">"post_id"</span> <span class="o">=</span> <span class="mi">718</span> &#x000A;<span class="no">Post</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">0.2</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"posts"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"posts"</span> <span class="no">WHERE</span> <span class="s2">"posts"</span><span class="o">.</span><span class="s2">"author_id"</span> <span class="o">=</span> <span class="mi">262</span> &#x000A;<span class="no">Tag</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">0.2</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"tags"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"tags"</span> <span class="no">WHERE</span> <span class="s2">"tags"</span><span class="o">.</span><span class="s2">"post_id"</span> <span class="o">=</span> <span class="mi">1521</span> &#x000A;<span class="no">Tag</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">0.2</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"tags"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"tags"</span> <span class="no">WHERE</span> <span class="s2">"tags"</span><span class="o">.</span><span class="s2">"post_id"</span> <span class="o">=</span> <span class="mi">1522</span> &#x000A;<span class="no">Tag</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">0.2</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"tags"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"tags"</span> <span class="no">WHERE</span> <span class="s2">"tags"</span><span class="o">.</span><span class="s2">"post_id"</span> <span class="o">=</span> <span class="mi">1523</span> &#x000A;<span class="o">...</span>&#x000A;</code></pre>
<p>13 small queries isn&#39;t going to make that much of an impact. But what if there are 100 authors, each with 10 blog posts each, each post having 5 tags? That&#39;s <strong>1,101</strong> queries! On my well-specced computer, that page takes <strong>1.007s</strong> to load.</p>

<p>That&#39;s a noticeable delay in page load. We can do better.</p>
<a href="#case-2-the-partially-optimized-query"><h2 id="case-2-the-partially-optimized-query">Case 2 - The Partially Optimized Query</h2></a>

<p>N+1s are a common occurrence in a Rails app, and fortunately Rails has a great tool for dealing with them. We can use the <a href="https://edgeapi.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-includes" target="_blank"><code>.includes</code></a>  method to eagerly load the associated records in the original query. By including posts in the query, we&#39;re performing the posts query up front in bulk as a single query for all associated posts. Here&#39;s the updated controller action (the view won&#39;t change throughout this exercise).</p>
<pre class="highlight ruby" data-language="ruby"><code><span class="k">class</span> <span class="nc">AuthorsController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>&#x000A;  <span class="k">def</span> <span class="nf">index</span>&#x000A;    <span class="vi">@authors</span> <span class="o">=</span> <span class="no">Author</span><span class="p">.</span><span class="nf">all</span><span class="p">.</span><span class="nf">includes</span><span class="p">(</span><span class="ss">:posts</span><span class="p">).</span><span class="nf">order</span><span class="p">(</span><span class="ss">:name</span><span class="p">)</span>&#x000A;  <span class="k">end</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre>
<p>We can see in the logs that we&#39;ve cut down the number of queries - 1 for authors, 1 for posts, but still a tags query for each blog post. That totals <strong>1,002</strong> queries, down from 1,101. And page load time dropped from 1.007s -&gt; 0.678s - that&#39;s a <strong>32%</strong> improvement. Not bad! But, we can do better.</p>
<pre class="highlight ruby" data-language="ruby"><code><span class="no">Author</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">23.1</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"authors"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"authors"</span> <span class="no">ORDER</span> <span class="no">BY</span> <span class="s2">"authors"</span><span class="o">.</span><span class="s2">"name"</span> <span class="no">ASC</span> &#x000A;<span class="no">Post</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">1.2</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"posts"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"posts"</span> <span class="no">WHERE</span> <span class="s2">"posts"</span><span class="o">.</span><span class="s2">"author_id"</span> <span class="no">IN</span> <span class="p">(</span><span class="mi">513</span><span class="p">,</span> <span class="mi">522</span><span class="p">,</span> <span class="mi">612</span><span class="p">,</span> <span class="o">...</span><span class="p">)</span> &#x000A;<span class="no">Tag</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">0.8</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"tags"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"tags"</span> <span class="no">WHERE</span> <span class="s2">"tags"</span><span class="o">.</span><span class="s2">"post_id"</span> <span class="o">=</span> <span class="mi">4030</span> &#x000A;<span class="no">Tag</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">0.2</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"tags"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"tags"</span> <span class="no">WHERE</span> <span class="s2">"tags"</span><span class="o">.</span><span class="s2">"post_id"</span> <span class="o">=</span> <span class="mi">4031</span> &#x000A;<span class="no">Tag</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">0.2</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"tags"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"tags"</span> <span class="no">WHERE</span> <span class="s2">"tags"</span><span class="o">.</span><span class="s2">"post_id"</span> <span class="o">=</span> <span class="mi">4032</span> &#x000A;<span class="no">Tag</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">0.2</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"tags"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"tags"</span> <span class="no">WHERE</span> <span class="s2">"tags"</span><span class="o">.</span><span class="s2">"post_id"</span> <span class="o">=</span> <span class="mi">4033</span> &#x000A;<span class="no">Tag</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">0.2</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"tags"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"tags"</span> <span class="no">WHERE</span> <span class="s2">"tags"</span><span class="o">.</span><span class="s2">"post_id"</span> <span class="o">=</span> <span class="mi">4034</span> &#x000A;<span class="o">...</span>&#x000A;</code></pre><a href="#case-3-the-optimized-query"><h2 id="case-3-the-optimized-query">Case 3 - The Optimized Query</h2></a>

<p>You&#39;ll notice in the partially optimized query, we included the posts, but there remained an N+1 for the tags. <code>.includes</code> allows you to include further related records using a hash to denote the connections between records:</p>
<pre class="highlight ruby" data-language="ruby"><code><span class="k">class</span> <span class="nc">AuthorsController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>&#x000A;  <span class="k">def</span> <span class="nf">index</span>&#x000A;    <span class="vi">@authors</span> <span class="o">=</span> <span class="no">Author</span><span class="p">.</span><span class="nf">all</span><span class="p">.</span><span class="nf">includes</span><span class="p">(</span><span class="ss">posts: </span><span class="p">[</span><span class="ss">:tags</span><span class="p">]).</span><span class="nf">order</span><span class="p">(</span><span class="ss">:name</span><span class="p">)</span>&#x000A;  <span class="k">end</span>&#x000A;<span class="k">end</span>&#x000A;</code></pre>
<p>This allows us to include all the authors&#39; posts, and all the posts&#39; associated tags. We&#39;ll see in the logs that we&#39;re down to <strong>3</strong> queries now - one to bulk select the authors, one to bulk select the blog posts, and one to bulk select the tags. Page load dropped down to 0.126s - a 81% improvement over the partially optimized query, and an <strong>87%</strong> improvement over the original!</p>
<pre class="highlight ruby" data-language="ruby"><code><span class="no">Author</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">16.3</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"authors"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"authors"</span> <span class="no">ORDER</span> <span class="no">BY</span> <span class="s2">"authors"</span><span class="o">.</span><span class="s2">"name"</span> <span class="no">ASC</span>&#x000A;<span class="no">Post</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">1.0</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"posts"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"posts"</span> <span class="no">WHERE</span> <span class="s2">"posts"</span><span class="o">.</span><span class="s2">"author_id"</span> <span class="no">IN</span> <span class="p">(</span><span class="mi">513</span><span class="p">,</span> <span class="mi">522</span><span class="p">,</span> <span class="mi">612</span><span class="p">,</span> <span class="o">...</span><span class="p">)</span>&#x000A;<span class="no">Tag</span> <span class="no">Load</span> <span class="p">(</span><span class="mf">1.0</span><span class="n">ms</span><span class="p">)</span>  <span class="no">SELECT</span> <span class="s2">"tags"</span><span class="p">.</span><span class="nf">*</span> <span class="no">FROM</span> <span class="s2">"tags"</span> <span class="no">WHERE</span> <span class="s2">"tags"</span><span class="o">.</span><span class="s2">"post_id"</span> <span class="no">IN</span> <span class="p">(</span><span class="mi">4030</span><span class="p">,</span> <span class="mi">4031</span><span class="p">,</span> <span class="mi">4032</span><span class="p">,</span> <span class="mi">4033</span><span class="p">,</span> <span class="mi">4034</span><span class="p">,</span> <span class="mi">4035</span><span class="p">,</span> <span class="mi">4036</span><span class="p">,</span> <span class="o">...</span><span class="p">)</span>&#x000A;</code></pre><a href="#wrapping-it-up"><h2 id="wrapping-it-up">Wrapping it up</h2></a>

<p>With a single line change, we can make a massive improvement in performance. You can argue in isolation that this is a small win. But with multiple unoptimized queries on a single page, or with N+1s persistent across an app, users will feel the sluggishness. Why normalize slow performance when instead we can have fast apps?</p>

<hr>
<a href="#need-help-with-your-rails-app-"><h2 id="need-help-with-your-rails-app-">Need Help with your Rails App?</h2></a>

<p>At Hashrocket, we specialize in building robust, performant applications using modern technologies. If you&#39;re modernizing or speeding up your Ruby on Rails app, <a href="https://hashrocket.com/contact" target="_blank">get in touch</a> to discuss how our team of expert developers can help.</p>

<p>Photo by <a href="https://unsplash.com/@silverkblack" target="_blank">Vitaly Gariev</a> on <a href="https://unsplash.com/photos/hand-writing-mathematical-formulas-on-a-blackboard-with-chalk-0uKaXrG9zaQ" target="_blank">Unsplash</a></p>
]]>
  </description>
  <author>
  </author>
  <guid>https://hashrocket.com/blog/posts/speed-up-your-rails-app-by-squashing-n-1-queries</guid>
  <pubDate>Thu, 20 Nov 2025 09:00:00 -0500</pubDate>
  <category>ActiveRecord</category>
  <category>performance</category>
  <category>Ruby on Rails</category>
</item>
<item>
  <title>
    <![CDATA[Some Thoughts About Claude Code]]>
  </title>
  <link>https://hashrocket.com/blog/posts/some-thoughts-about-claude-code</link>
  <description>
    <![CDATA[<p>Claude code is a powerful AI toolset that runs right in your terminal. While providing a lot of impressive utility, it also suffers from the issues that arise from similar AI toolings with the addition of an expensive pricing model.</p>
<a href="#context-is-important"><h2 id="context-is-important">Context is Important</h2></a>

<p>To me, the main selling point for Claude Code is its ability to read through your entire codebase; a big shortcoming of many AI workflows is the model only partially understanding an issue due to it not having enough of the project&#39;s context and convention to be effective. Claude Code has the ability to access all of the files in the directory where you initiated the session, and can even ask for permission to search through extraneous directories. While potentially helpful, this is also a little sketchy when considering that everything Claude is processing is getting sent over the wire to Anthropic&#39;s servers; according to their privacy policy, the data sent over for processing will not be used for LLM training unless the user specifically opts in or the content has been flagged for a Trust &amp; Safety Review. It&#39;s also important to be aware that Claude&#39;s data retention policy has changed in recent months and will likely continue to change.</p>
<a href="#step-by-step"><h2 id="step-by-step">Step by Step</h2></a>

<p>When you start a Claude Code session and give it a task to complete, it will usually try to break down the task into steps to complete. Upon starting a &#39;step&#39;, Claude will show you what it wants to do, and ask you for permission to do it. In my experience, I always had three options when presented with Claude&#39;s suggestion: accept the suggested action, always accept the suggested action, or do not accept and tell Claude to do something different. I would urge any developer to stick to the first and last option. Take the time to look at Claude&#39;s suggestion critically and decide if it seems like the right way to go. Claude is usually logical, but also susceptable to <em>hallucinations</em>. A big advantage of Claude Code living in the terminal, is that it has the ability to run bash commands and verify its own work in lots of ways. This means it can actually run tests to see if the code it wrote compiles, if tests pass, or if a server spun up correctly. This was surprisingly useful when trying it out for my own projects and I found that Claude presented lot of concerns that I also held when working through a problem, and also highlighted potential mistakes and optimizations I had yet to consider. Claude is certainly not foolproof and sometimes it tries to confidently to run a command that makes no sense, or try to write some code that is off track. </p>
<a href="#the-cost-question"><h2 id="the-cost-question">The Cost Question</h2></a>

<p>Claude Code isn&#39;t cheap and charges you based on token usage. Each session burns through API tokens at a rate that can add up fast if you&#39;re using it regularly. If Claude is going through one of its <em>episodes</em>, it can end up repeating itself in logical thought loops that burn tokens quickly. For hobbyists or developers at smaller companies, this could create a weird calculus where you&#39;d constantly ask if the task you&#39;re about to delegate is worth it or not. This friction fundamentally changes how you interact with the tool instead of treating it as a seamless assistant, you&#39;re rationing its use (Which is probably a smart thing to do with AI anyways😬).</p>
<a href="#when-it-works-and-when-it-doesn-39-t-"><h2 id="when-it-works-and-when-it-doesn-39-t-">When It Works (And When It Doesn&#39;t)</h2></a>

<p>Claude Code is pretty impressive when working on well defined problems in codebases with obvious patterns and structure (Like a nice Ruby on Rails app). Need to refactor some logic in a Rails model or service object? Want to add some error handling across several files? Need to write some specs for new features? These are tasks where I found Claude Code to have the most success. It typically understands the scope, stays consistent, and navigates the files without losing its train of thought.</p>

<p>But it struggles with ambiguity. Vague requests like &quot;make this faster&quot; or &quot;fix the bugs&quot; often lead to Claude making assumptions that don&#39;t align with what you actually meant. It also tends to be overly conservative sometimes and overly aggressive other times there&#39;s no middle ground. It might refuse to modify a file because it&#39;s &quot;not sure&quot; what you want, or it might rewrite an entire module when you just wanted a small tweak.</p>
<a href="#my-verdict"><h2 id="my-verdict">My Verdict</h2></a>

<p>Claude Code is a pretty cool tool that improves certain workflows, but it&#39;s not a perfect advance in an AI revolution. It&#39;s expensive, it requires diligent supervision, and can make some pretty baffling mistakes. For the right tasks, particularly refactoring, boilerplate generation, or getting you out of being stumped by a bug, it can save a significant amount of time. Don&#39;t expect to be able to hand Claude Code your project directory and walk away. Claude Code is a tool that is more of a supplement to your workflow than it is a replacement. And for me, that&#39;s probably how it should be. The day we can fully trust AI to write production code unsupervised is not here yet and maybe further in the future than the hype suggests.</p>

<p>Use Claude Code with your eyes open. Review everything. Don&#39;t hit &quot;always accept.&quot; And maybe check your API bill more often than you&#39;d like to.</p>
]]>
  </description>
  <author>
  </author>
  <guid>https://hashrocket.com/blog/posts/some-thoughts-about-claude-code</guid>
  <pubDate>Tue, 18 Nov 2025 09:00:00 -0500</pubDate>
  <category>Ruby</category>
  <category>Workflow</category>
  <category>rails</category>
  <category>AI</category>
</item>
<item>
  <title>
    <![CDATA[Nativewind: Speeding up Styling in React Native]]>
  </title>
  <link>https://hashrocket.com/blog/posts/nativewind-speeding-up-styling-in-react-native</link>
  <description>
    <![CDATA[<p>How Nativewind can speed up your React Native Development</p>

<p>If you&#39;re anything like me, after working on a few web projects with Tailwind, it can feel like a drag to return to stacks that use other styling libraries. Tailwind has become, for myself, and many other developers, a standard styling paradigm. When starting my most recent React Native project, I was relieved to find out that NativeWind exists. NativeWind is exactly what is sounds like: Tailwind Classes in React Native. I can attest to the breeziness of writing an entire native app without a single call to StyleSheet.create.</p>
<a href="#it-39-s-familiar"><h2 id="it-39-s-familiar">It&#39;s Familiar</h2></a>

<p>Nativewind takes the familiar classes of Tailwind CSS directly to your React Native components. Virtually every* class from Tailwind CSS can be used the exact same way on your mobile app (and the web), and the development results end up being faster iteration, less translating, and components that feel more readable and maintainable.</p>

<p>Let’s look at how to set it up</p>

<p>NativeWind translates Tailwind class names into React Native styles at runtime or compile-time, if you use the Babel plugin. You end up with the the same composable mindset of Tailwind, but the output is just React Native styles.</p>

<p>The setup is simple enough, for my latest project we used expo:</p>
<pre class="highlight plaintext" data-language="plaintext"><code>npx expo install nativewind react-native-reanimated@~3.17.4 react-native-safe-area-context@5.4.0&#x000A;npx expo install --dev tailwindcss@^3.4.17 prettier-plugin-tailwindcss@^0.5.11&#x000A;</code></pre>
<p>Now to generate a tailwind config, run:</p>
<pre class="highlight plaintext" data-language="plaintext"><code>npx tailwindcss init&#x000A;</code></pre>
<p>Be sure to include the path to your components in the generated tailwind.config.js file</p>
<pre class="highlight javascript" data-language="javascript"><code><span class="cm">/** @type {import('tailwindcss').Config} */</span>&#x000A;<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>&#x000A;  <span class="na">content</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">./App.tsx</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">./components/**/*.{js,jsx,ts,tsx}</span><span class="dl">"</span><span class="p">],</span>&#x000A;  <span class="na">presets</span><span class="p">:</span> <span class="p">[</span><span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">nativewind/preset</span><span class="dl">"</span><span class="p">)],</span>&#x000A;  <span class="na">theme</span><span class="p">:</span> <span class="p">{</span>&#x000A;    <span class="na">extend</span><span class="p">:</span> <span class="p">{},</span>&#x000A;  <span class="p">},</span>&#x000A;  <span class="na">plugins</span><span class="p">:</span> <span class="p">[],</span>&#x000A;<span class="p">}</span>&#x000A;</code></pre>
<p>Next create your global.css file with tailwind&#39;s directives</p>
<pre class="highlight css" data-language="css"><code><span class="k">@tailwind</span> <span class="n">base</span><span class="p">;</span>&#x000A;<span class="k">@tailwind</span> <span class="n">components</span><span class="p">;</span>&#x000A;<span class="k">@tailwind</span> <span class="n">utilities</span><span class="p">;</span>&#x000A;</code></pre>
<p>Then, enable the Babel plugin in babel.config.js:</p>
<pre class="highlight jsx" data-language="jsx"><code><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nf">function </span><span class="p">(</span><span class="nx">api</span><span class="p">)</span> <span class="p">{</span>&#x000A;  <span class="nx">api</span><span class="p">.</span><span class="nf">cache</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>&#x000A;  <span class="k">return</span> <span class="p">{</span>&#x000A;    <span class="na">presets</span><span class="p">:</span> <span class="p">[</span>&#x000A;      <span class="p">[</span><span class="dl">"</span><span class="s2">babel-preset-expo</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="na">jsxImportSource</span><span class="p">:</span> <span class="dl">"</span><span class="s2">nativewind</span><span class="dl">"</span> <span class="p">}],</span>&#x000A;      <span class="dl">"</span><span class="s2">nativewind/babel</span><span class="dl">"</span><span class="p">,</span>&#x000A;    <span class="p">],</span>&#x000A;  <span class="p">};</span>&#x000A;<span class="p">};</span>&#x000A;</code></pre>
<p>Now you’re ready to use all the classes Nativewind provides in your components:</p>
<pre class="highlight jsx" data-language="jsx"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">View</span><span class="p">,</span> <span class="nx">Text</span><span class="p">,</span> <span class="nx">Pressable</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react-native</span><span class="dl">'</span>&#x000A;&#x000A;<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">MyComponent</span><span class="p">()</span> <span class="p">{</span>&#x000A;  <span class="k">return </span><span class="p">(</span>&#x000A;    <span class="p">&lt;</span><span class="nc">View</span> <span class="na">className</span><span class="p">=</span><span class="s">"flex-1 items-center justify-center bg-gray-100"</span><span class="p">&gt;</span>&#x000A;      <span class="p">&lt;</span><span class="nc">Text</span> <span class="na">className</span><span class="p">=</span><span class="s">"text-2xl font-semibold text-gray-900 mb-4"</span><span class="p">&gt;</span>&#x000A;        Hello world, this component is using NativeWind!&#x000A;      <span class="p">&lt;/</span><span class="nc">Text</span><span class="p">&gt;</span>&#x000A;      <span class="p">&lt;</span><span class="nc">Pressable</span> <span class="na">className</span><span class="p">=</span><span class="s">"bg-blue-400 px-4 py-2 rounded-lg active:bg-blue-600"</span><span class="p">&gt;</span>&#x000A;        <span class="p">&lt;</span><span class="nc">Text</span> <span class="na">className</span><span class="p">=</span><span class="s">"text-white font-medium"</span><span class="p">&gt;</span>Press me<span class="p">&lt;/</span><span class="nc">Text</span><span class="p">&gt;</span>&#x000A;      <span class="p">&lt;/</span><span class="nc">Pressable</span><span class="p">&gt;</span>&#x000A;    <span class="p">&lt;/</span><span class="nc">View</span><span class="p">&gt;</span>&#x000A;  <span class="p">)</span>&#x000A;<span class="p">}</span>&#x000A;</code></pre>
<p>You will probably want to create some custom tailwind colors/classes, which you can include into the existing theme.</p>
<pre class="highlight jsx" data-language="jsx"><code><span class="c1">//tailwind.config.js</span>&#x000A;&#x000A;<span class="k">import</span> <span class="p">{</span> <span class="nx">colors</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./theme/colors</span><span class="dl">"</span>&#x000A;<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>&#x000A;  <span class="na">theme</span><span class="p">:</span> <span class="p">{</span>&#x000A;    <span class="na">extend</span><span class="p">:</span> <span class="p">{</span>&#x000A;      <span class="na">colors</span><span class="p">:</span> <span class="nx">colors</span>&#x000A;      <span class="p">},</span>&#x000A;    <span class="p">},</span>&#x000A;  <span class="p">},</span>&#x000A;<span class="p">}</span>&#x000A;</code></pre>
<p>For more information on installing Nativewind, be sure to check out the <a href="https://www.nativewind.dev/docs/getting-started/installation" target="_blank">documentation</a></p>

<p>Defining style objects in React Native can spiral, and to be fair, so can inline tailwind classes at times. The separation of style sheets can look neat at first, but over time, it can become another layer to maintain, a long list of key names that rarely get reused across files when not carefully organized.</p>

<p>Let&#39;s compare StyleSheets with Nativewind</p>
<pre class="highlight jsx" data-language="jsx"><code><span class="kd">const</span> <span class="nx">styles</span> <span class="o">=</span> <span class="nx">StyleSheet</span><span class="p">.</span><span class="nf">create</span><span class="p">({</span>&#x000A;  <span class="na">container</span><span class="p">:</span> <span class="p">{</span>&#x000A;    <span class="na">flex</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>&#x000A;    <span class="na">backgroundColor</span><span class="p">:</span> <span class="dl">'</span><span class="s1">#f9fafb</span><span class="dl">'</span><span class="p">,</span>&#x000A;    <span class="na">justifyContent</span><span class="p">:</span> <span class="dl">'</span><span class="s1">center</span><span class="dl">'</span><span class="p">,</span>&#x000A;    <span class="na">alignItems</span><span class="p">:</span> <span class="dl">'</span><span class="s1">center</span><span class="dl">'</span><span class="p">,</span>&#x000A;  <span class="p">},</span>&#x000A;<span class="p">})</span>&#x000A;</code></pre>
<p>Now compare that to this:</p>
<pre class="highlight jsx" data-language="jsx"><code><span class="p">&lt;</span><span class="nc">View</span> <span class="na">className</span><span class="p">=</span><span class="s">"flex-1 bg-gray-50 justify-center items-center"</span> <span class="p">/&gt;</span>&#x000A;</code></pre>
<p>The result is the same, and I&#39;d argue the tailwind is more readable.</p>

<p>I prefer never having to context switch between the code I&#39;m writing and the styles it uses. Navigating to the bottom of the file just to tweak a component&#39;s padding can be tedious.</p>

<p>As a developer that uses Tailwind on the web, Nativewind makes me feel right at home. It also means that If you plan on distributing your native app to the web, it&#39;s simple to use Tailwind breakpoints to alter the design of your components for larger screens.</p>
<a href="#integration"><h2 id="integration">Integration</h2></a>

<p>Nativewind also plays well with other libraries. Since the generated output is just standard React Native styles, you can still use other animation and navigation libraries. Essentially, if it takes a className prop, you can throw Nativewind styles at it.</p>
<pre class="highlight plaintext" data-language="plaintext"><code>import { MotiView } from 'moti'&#x000A;&#x000A;&lt;MotiView&#x000A;  from={{ opacity: 0, translateY: 8 }}&#x000A;  animate={{ opacity: 1, translateY: 0 }}&#x000A;  className="p-4 bg-brand-500 rounded-xl"&#x000A;/&gt;&#x000A;</code></pre>
<p>Another great feature is that the basic animation classes included with Tailwind work with Nativewind. At the time of writing this the support is considered experimental, however in my experience all of the <code>animate-</code> classes work as expected on IOS and Android.</p>
<a href="#in-conclusion"><h2 id="in-conclusion">In Conclusion</h2></a>

<p>The familiar Tailwind class syntax and easily achieved responsiveness make Nativewind an effective library for speeding up react native development.</p>

<p>If you’re tired of jumping back and forth from component renders and StyleSheets, I highly recommend seeing if Nativewind can improve your workflow on your next React Native Project.</p>
<a href="#work-with-us"><h2 id="work-with-us">Work with us</h2></a>

<p>Need help with your next React Native project? Don&#39;t hesitate to reach out with any questions.</p>
]]>
  </description>
  <author>
  </author>
  <guid>https://hashrocket.com/blog/posts/nativewind-speeding-up-styling-in-react-native</guid>
  <pubDate>Thu, 13 Nov 2025 09:00:00 -0500</pubDate>
  <category>Mobile</category>
  <category>React Native</category>
</item>
  </channel>
</rss>
