Bridgetown2025-12-08T08:40:06-08:00https://www.fullstackruby.dev/feed.xmlFullstack RubyFuturistic web dev that's fast and fun.Jared Whitehttps://jaredwhite.comThe “rv” Tool is Making Swift Progress, Becoming a Must-Have for Rubyists2025-12-08T08:15:11-08:002025-12-08T08:15:11-08:00repo://posts.collection/_posts/2025/2025-12-08-rv-making-swift-progress.md<p>Back in the mists of time, I used a tool called <code class="highlighter-rouge">rvm</code>. It was more than just a Ruby version manager, it was also a gemset manager: instead of using <code class="highlighter-rouge">bundle exec</code> or a binstub, you could just type a command and it would &#8220;work&#8221; because every version+gemset was a pristine environment. Cool in a way, but also very complicated.</p> <p>I eventually migrated to the simpler <code class="highlighter-rouge">rbenv</code> and have used that for years now. It only manages Ruby versions and expects you to make use of Bundler to keep gems and executables in line. Y&#8217;know. Modern Ruby.</p> <p>But what if you could use something even <em>more</em> modern than <code class="highlighter-rouge">rbenv</code>? <strong>Enter <code class="highlighter-rouge">rv</code>.</strong></p> <p>Inspired by <code class="highlighter-rouge">uv</code> in the Python ecosystem, <a href="https://github.com/spinel-coop/rv">rv aims to become your singular solution</a> for total management of the Ruby environment. Right now this is primarily focused on installing Ruby versions, but in the future it will support installing gems and running executables. &#8220;Wait, would this then <em>replace</em> Bundler?&#8221; you might ask. That&#8217;s a distinct possibility, although my experience interfacing with Bundler as part of the <a href="https://www.bridgetownrb.com">Bridgetown</a> framework leads me to believe Bundler isn&#8217;t going anywhere any time soon. (rv would need to offer a host of Ruby APIs to replace similar APIs provided by Bundler.)</p> <p>One of the reasons rv is so <em>fast</em> is because it&#8217;s written in Rust. It seems to me the Rust is quickly becoming the new C when it comes to lower-level system languages operating alongside Ruby. A number of Rubyists are brushing up on their Rust and contributing to new tools like this, and I&#8217;m excited to see where that leads the ecosystem.</p> <p>I&#8217;m using rv now in a couple of environments instead of rbenv, and it works quite well. Once the gem installation feature lands, you can be sure I&#8217;ll give that a thorough workout and report back. <strong>Bonus:</strong> many of the folks working on <code class="highlighter-rouge">rv</code> and other experimental tools like <a href="https://github.com/RubyElders/ruby-butler">Ruby Butler</a> are also involved in the <a href="https://www.fullstackruby.dev/ruby-infrastructure/2025/10/06/theres-a-new-gem-server-in-town/">gem.coop initiative I wrote about</a> which is equally exciting. <img src="/images/ruby.svg" alt="small red gem symbolizing the Ruby language" width="14" style="vertical-align: -0.05em;margin-left: 0.1em" /></p>Jared Whitehttps://jaredwhite.comDon’t Make Me Think Principle, Testing, and Intuitive Expectations2025-11-25T11:18:39-08:002025-11-25T11:18:39-08:00repo://posts.collection/_posts/2025/2025-11-25-dont-make-me-think-testing-intuitive-expectations.md<h2 id="a-new-extension-to-minitest-expectations-by-yours-truly-is-the-perfect-illustration-for-this-philosophy-of-programming">A new extension to Minitest Expectations by yours truly is the perfect illustration for this philosophy of programming.</h2> <p>➡️ <strong>TL;DR:</strong> <a href="https://github.com/bridgetownrb/bridgetown/blob/main/bridgetown-foundation/lib/bridgetown/foundation/intuitive_expectations.rb">a look at the code</a> / <a href="#how-intuitive-are-intuitive-expectations-very-intuitive">examples</a></p> <p>A shorthand for how I think about the design of new APIs, whether I’m working on the <a href="https://www.bridgetownrb.com">Bridgetown web framework</a> or another library, is a principle I’ve come to call <em>Don’t Make Me Think</em>. (DMMT)</p> <p>DMMT plays out in many ways. Sometimes you might compare it to the <a href="https://en.wikipedia.org/wiki/Principle_of_least_astonishment">Principle of Least Astonishment</a> (or Surprise), but ultimately I think it’s more about <em>the vibes, man</em> and taste born of experience than any pat technical explanation. The aforementioned Wikipedia article also links to some other interesting and related principles such as Do What I Mean (DWIM) and that old chestnut Convention Over Configuration.</p> <p>When it comes to software testing, I believe the DMMT principle needs to kick into overdrive. Let me set the context for my entreaty:</p> <ol> <li><strong>I do not, have not, and will not practice TDD (Test-Driven Development).</strong> Does that shock you? I hope not. While I have no problem with people who are adherents to the TDD philosophy, I do have a massive problem with the notion that one must practice TDD to be a Good Programmer. Here’s what I do: I write tests when I need to write tests. Sometimes that’s before I work on a problem, in which case it looks like I’m practicing TDD. Other times I write tests after I’ve solved a problem. Still other times, <em>I write no tests at all.</em> Again, does that shock you? I hope not.</li> <li><strong>I do not enjoy writing tests.</strong> I enjoy solving problems. Test-writing is a “side effect” of wanting to author robust and reliable code, just as dish-washing is a “side effect” of keeping your kitchen clean and useful for the real task of <strong>cooking and eating delicious food</strong>. So while I might sometimes tolerate a certain degree of fussiness and ceremony in the process of authoring user-focused code because the end results are worth it, I have no such tolerance in my authoring of tests. <em>Get in, get green, get out</em> as fast as humanly possible is my motto!</li> </ol> <p>Given all that, I have spent the bulk of my career as a programmer wondering why some testing frameworks and techniques can be such a right PITA. Because if the primary goal is to <em>get me to write more tests</em> (a somewhat noble goal I might also poke holes in as the contrarian that I am), <em>your testing framework is not doing its job!</em></p> <h2 id="from-rspec-to-minitest-assertions-to-expectations-to-intuitive-expectations">From RSpec to Minitest Assertions to Expectations to Intuitive Expectations</h2> <p>Like many Rubyists, my first memories of working on Ruby applications and writing tests was in relation to the RSpec testing framework. Now I’m not here to cast aspersions on the creators of RSpec nor its many fans. <strong>Let’s just say RSpec has never tickled my fancy.</strong></p> <p>I mean,</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">expect</span><span class="p">(</span><span class="n">actual</span><span class="p">).</span><span class="nf">to</span> <span class="n">be_within</span><span class="p">(</span><span class="n">delta</span><span class="p">).</span><span class="nf">of</span><span class="p">(</span><span class="n">expected</span><span class="p">)</span> </code></pre></div></div> <p>superficially looks like a cool Ruby DSL, and we all love a good DSL right? Unfortunately, RSpec routinely falls down pretty hard on the Don’t Make Me Think (DMMT) principle. I have to hold all of this syntax in my head:</p> <ul> <li><code class="highlighter-rouge">expect</code>: this is straightforward enough.</li> <li><code class="highlighter-rouge">.to</code>: why is this here? Why can’t I just write <code class="highlighter-rouge">expect</code> and then a matcher like <code class="highlighter-rouge">eq</code>? Because there’s also <code class="highlighter-rouge">not_to</code> which is the opposite of <code class="highlighter-rouge">to</code>. I have to remember this now.</li> <li><code class="highlighter-rouge">be_*</code>: oh great, now I have to remember all of these various matchers starting with <code class="highlighter-rouge">be</code>…er, except that many matchers don’t! Can I write <code class="highlighter-rouge">expect(something).to be_equal_to(3)</code>? No! I can’t even though that sounds like a full English phrase. Some matchers literally make no grammatical sense to me at all. Like this one: <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">expect</span><span class="p">(</span><span class="mi">10</span><span class="p">).</span><span class="nf">to</span> <span class="n">satisfy</span><span class="p">(</span><span class="s2">"be a multiple of 5"</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">v</span><span class="o">|</span> <span class="n">v</span> <span class="o">%</span> <span class="mi">5</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">end</span> </code></pre></div> </div> <p>Huh? 10 should satisfy being a multiple of 5? I would never talk like this.</p> </li> <li><code class="highlighter-rouge">of</code>: is this just a one-off? Or are there other <code class="highlighter-rouge">of</code> cases in the matchers API? Answer: it’s a one-off! This definitely is a violation of the Principle of Least Astonishment. I don’t think it’s reasonable that someone learning a testing framework should have to know that for <em>only one specific matcher</em> there is a unique piece of DSL to learn and use.</li> </ul> <p>Now it’s true I would probably not use this particular matcher very often, as I don’t typically work on math problems requiring these calculations. But I think it’s a good illustration for the point I’m making.</p> <p>There’s also a lot about the RSpec code styles and ecosystem I don’t particularly care for. I don’t like all the indirections of multiple <code class="highlighter-rouge">let</code>s and how mocks look and just a whole host of issues. Again, if you personally dig all that, good for you! I always found it to be a miserable experience.</p> <p>As time passed, I started to run into increased usage of <a href="https://docs.seattlerb.org/minitest/">Minitest</a> and a breezy style of writing very simple test methods with a small set of possible assertions. Coming from RSpec, it’s almost unbearably terse, but once you get used to it, it’s hard to beat. For some cases like simply testing if something is true or false, it’s a revelation:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">something_is</span> <span class="o">=</span> <span class="kp">true</span> <span class="n">something_isnt</span> <span class="o">=</span> <span class="kp">false</span> <span class="n">assert</span> <span class="n">something_is</span> <span class="n">refute</span> <span class="n">something_isnt</span> </code></pre></div></div> <p>While that sort of syntax might not feel wildly Ruby-esque, I think it’s an amazing example of the DMMT principle.</p> <p>However, as time passed I started to grow a bit frustrated with simply writing assertions all the time. Not only is there the strange “backwards” arguments issue of Minitest’s <code class="highlighter-rouge">assert_equal</code> with the expected rather than the actual coming first (not a design flaw of Minitest per se, <a href="https://stackoverflow.com/a/57059952">it has a long history predating Minitest and even the Ruby community</a>), I don’t particularly care for the way my tests look with dozens or hundreds of <code class="highlighter-rouge">assert_*</code> or <code class="highlighter-rouge">refute_*</code> statements. I also <em>am</em> a big fan of spec-style test writing with blocks of <code class="highlighter-rouge">describe/it</code> or <code class="highlighter-rouge">context/should</code> or however you want to define those terms, and often you find the spec-style tests written not with assertions but with “expectations”.</p> <p>Minitest offers its own <a href="https://docs.seattlerb.org/minitest/Minitest/Expectations.html">expectations syntax</a> which I like to use with <code class="highlighter-rouge">expect</code>. The matchers all start with either <code class="highlighter-rouge">must</code> or <code class="highlighter-rouge">wont</code>:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">expect</span><span class="p">(</span><span class="n">first_name</span><span class="p">).</span><span class="nf">must_equal</span> <span class="s2">"Jared"</span> <span class="n">expect</span><span class="p">(</span><span class="n">last_name</span><span class="p">).</span><span class="nf">wont_equal</span> <span class="s2">"Whyte"</span> </code></pre></div></div> <p>It’s also quite nice when you need to run code within a block to match an expectation, such as checking if an <em>exception</em> was raised:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">expect</span> <span class="p">{</span> <span class="n">crash_my_app!</span> <span class="p">}.</span><span class="nf">must_raise</span> <span class="no">MyApp</span><span class="o">::</span><span class="no">CrashedException</span> </code></pre></div></div> <p>Yet, me being me, it wasn’t long before I started to ask myself the following question: why do I need to think about all of these <code class="highlighter-rouge">must_*</code> and <code class="highlighter-rouge">wont_*</code> matchers? DMMT! I should be able to write the most standard, most obvious Ruby language code possible without needing to learn much of anything at all.</p> <p>For example, if I wanted to check the equality of two values, couldn’t I simply write this?</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">expect</span><span class="p">(</span><span class="n">foo</span><span class="p">)</span> <span class="o">==</span> <span class="s2">"bar"</span> </code></pre></div></div> <p>And if not, why not?</p> <p>With that question in mind, I set out to experiment with the most <strong>Don’t Make Me Think</strong> testing paradigm possible…<em>and I succeeded.</em> 😎</p> <p>Enter <a href="https://www.bridgetownrb.com/docs/plugins/foundation-gem#intuitive-expectations-for-minitest">Intuitive Expectations</a>.</p> <h2 id="how-intuitive-are-intuitive-expectations-very-intuitive">How Intuitive are Intuitive Expectations? Very Intuitive!</h2> <p>A new feature provided by the <strong>Bridgetown Foundation</strong> gem (and one you can use in any Ruby app, not just a Bridgetown project), Intuitive Expectations builds upon Minitest’s exceptions and provides <a href="https://api.bridgetownrb.com/Bridgetown/Foundation/IntuitiveExpectations.html">simpler and chainable variations</a>.</p> <p>Here are some examples from the docs:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">expect</span><span class="p">(</span><span class="n">some_int</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">123</span> <span class="n">expect</span><span class="p">(</span><span class="n">some_big_str</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s2">"howdy"</span> <span class="c1"># or expect(...).include? ...</span> <span class="n">expect</span><span class="p">(</span><span class="n">some_bool</span><span class="p">).</span><span class="nf">true?</span> <span class="c1"># aliased to truthy?</span> <span class="n">expect</span><span class="p">(</span><span class="mi">2</span><span class="o">..</span><span class="mi">4</span><span class="p">).</span><span class="nf">within?</span><span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="mi">6</span><span class="p">)</span> <span class="n">expect</span><span class="p">(</span><span class="n">food_tastes</span><span class="p">)</span> <span class="o">=~</span> <span class="sr">/g(r+)eat/</span> </code></pre></div></div> <p>I also am a huge fan of chainable DSLs (aka where the methods return <code class="highlighter-rouge">self</code> so you can reuse that context), so naturally I had to make sure that works as well:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">expect</span><span class="p">(</span><span class="n">big_string</span><span class="p">)</span> <span class="p">.</span><span class="nf">include?</span><span class="p">(</span><span class="s2">"foo"</span><span class="p">)</span> <span class="p">.</span><span class="nf">include?</span><span class="p">(</span><span class="s2">"bar"</span><span class="p">)</span> <span class="p">.</span><span class="nf">exclude?</span><span class="p">(</span><span class="s2">"baz"</span><span class="p">)</span> <span class="n">expect</span><span class="p">(</span><span class="n">user</span><span class="p">)</span> <span class="p">.</span><span class="nf">is?</span><span class="p">(</span><span class="ss">:moderator?</span><span class="p">)</span> <span class="p">.</span><span class="nf">isnt?</span><span class="p">(</span><span class="ss">:admin?</span><span class="p">)</span> </code></pre></div></div> <p>Most of the named matchers have a <code class="highlighter-rouge">not</code> twin, so it’s easy to intuit:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">expect</span><span class="p">(</span><span class="n">beer_on_the_wall</span><span class="p">).</span><span class="nf">match?</span> <span class="sr">/[0-9]+ bottles/</span> <span class="n">expect</span><span class="p">(</span><span class="n">wine_on_the_wall</span><span class="p">).</span><span class="nf">not_match?</span> <span class="sr">/[0-9]+ bottles/</span> </code></pre></div></div> <p>We’re really excited on the Bridgetown Core team to be moving away from an older testing style based originally on a fork of Jekyll using the Shoulda gem and Minitest assertions to Minitest spec-style and Intuitive Expectations (not in all but in many cases). Here’s an example excerpt from such a test:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">TestComponents</span> <span class="o">&lt;</span> <span class="no">BridgetownUnitTest</span> <span class="n">describe</span> <span class="s2">"Bridgetown::Component"</span> <span class="k">do</span> <span class="n">it</span> <span class="s2">"renders with captured block content"</span> <span class="k">do</span> <span class="c1"># lots of funky whitespace from all the erb captures!</span> <span class="n">spaces</span> <span class="o">=</span> <span class="s2">" "</span> <span class="n">morespaces</span> <span class="o">=</span> <span class="s2">" "</span> <span class="n">expect</span><span class="p">(</span><span class="vi">@erb_page</span><span class="p">.</span><span class="nf">output</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="o">&lt;&lt;~</span><span class="no">HTML</span><span class="sh"> &lt;app-card&gt; &lt;header&gt;I&amp;#39;M A CARD&lt;/header&gt; &lt;app-card-inner&gt; </span><span class="si">#{</span><span class="n">spaces</span><span class="si">}</span><span class="sh"> &lt;p&gt;I'm the body of the card&lt;/p&gt; </span><span class="si">#{</span><span class="n">morespaces</span><span class="si">}</span><span class="sh">&lt;img src="test.jpg" /&gt; </span><span class="si">#{</span><span class="n">spaces</span><span class="si">}</span><span class="sh">NOTHING &lt;/app-card-inner&gt; &lt;footer&gt;I&amp;#39;m a footer&lt;/footer&gt; &lt;/app-card&gt; </span><span class="no"> HTML</span> <span class="k">end</span> <span class="n">it</span> <span class="s2">"does not render if render? is false"</span> <span class="k">do</span> <span class="n">expect</span><span class="p">(</span><span class="vi">@erb_page</span><span class="p">.</span><span class="nf">output</span><span class="p">)</span> <span class="p">.</span><span class="nf">exclude?</span><span class="p">(</span><span class="s2">"NOPE"</span><span class="p">)</span> <span class="p">.</span><span class="nf">exclude?</span><span class="p">(</span><span class="s2">"Canceled!"</span><span class="p">)</span> <span class="k">end</span> <span class="n">it</span> <span class="s2">"handles same-file namespaced components"</span> <span class="k">do</span> <span class="n">expect</span><span class="p">(</span><span class="vi">@erb_page</span><span class="p">.</span><span class="nf">output</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s2">"&lt;card-section&gt;blurb contents&lt;/card-section&gt;"</span> <span class="k">end</span> <span class="k">end</span> <span class="k">end</span> </code></pre></div></div> <p>Beauty is in the eye of the beholder and all art is subjective, but I truly believe Minitest Spec + Intuitive Expectations is the most elegant test syntax I’ve ever laid eyes on. Once this was available to me, I grew used to it so quickly I now feel like any project is “broken” if I can’t use this syntax. It’s so familiar to me that <a href="https://codeberg.org/heartml/streetcar-elements/src/commit/ff37befb1a49cceefe39c7d4b398ef523b61d8f4/test/expectations.js">I quickly wrote a JavaScript version</a> built on top of Node’s built-in assertions and testing features so I can write this in JavaScript:</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">content</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nf">evaluate</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nb">document</span><span class="p">.</span><span class="nf">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">div</span><span class="dl">"</span><span class="p">).</span><span class="nx">shadowRoot</span><span class="p">.</span><span class="nx">innerHTML</span><span class="p">)</span> <span class="nf">expect</span><span class="p">(</span><span class="nx">content</span><span class="p">).</span><span class="nf">include</span><span class="p">(</span><span class="dl">"</span><span class="s2">&lt;p&gt;I'm in the shadow DOM!&lt;/p&gt;</span><span class="dl">"</span><span class="p">)</span> <span class="nf">expect</span><span class="p">(</span><span class="nx">content</span><span class="p">).</span><span class="nf">notMatch</span><span class="p">(</span><span class="sr">/s</span><span class="se">\w</span><span class="sr">*z/</span><span class="p">)</span> <span class="kd">const</span> <span class="nx">funkyTown</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nf">evaluate</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nx">dataset</span><span class="p">.</span><span class="nx">results</span><span class="p">)</span> <span class="nf">expect</span><span class="p">(</span><span class="nx">funkyTown</span><span class="p">).</span><span class="nf">equal</span><span class="p">(</span><span class="dl">"</span><span class="s2">won't you take me 2</span><span class="dl">"</span><span class="p">)</span> <span class="kd">const</span> <span class="nx">count</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nf">evaluate</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nb">document</span><span class="p">.</span><span class="nf">querySelectorAll</span><span class="p">(</span><span class="dl">"</span><span class="s2">#delete-me</span><span class="dl">"</span><span class="p">).</span><span class="nx">length</span><span class="p">)</span> <span class="nf">expect</span><span class="p">(</span><span class="nx">count</span><span class="p">).</span><span class="nf">notEqual</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> </code></pre></div></div> <p><em>I guess once you expect Intuitive Expectations, you never go back.</em> <img src="/images/ruby.svg" alt="small red gem symbolizing the Ruby language" width="14" style="vertical-align: -0.05em;margin-left: 0.1em" /></p>Jared Whitehttps://jaredwhite.comBuckle Up, There’s a New Gem Server in Town: gem.coop2025-10-06T09:16:14-07:002025-10-06T09:16:14-07:00repo://posts.collection/_posts/2025/2025-10-06-theres-a-new-gem-server-in-town.md<p>Assuming you haven’t been living under a rock these past few weeks, the Ruby community has been embroiled in quite a bit of drama. I won’t recap it here…there are plenty of other sources to go (<a href="https://joel.drapper.me">Joel Drapper</a> for one), and I also have <a href="https://jaredwhite.com/articles/ruby-central-is-not-operating-in-good-faith">my own pointed take on the matter on my personal blog</a>. But here on <strong>Fullstack Ruby</strong> I like to maintain a positive, can-do attitude, and to that end, let’s talk about some very exciting developments!</p> <p>Most Rubyists are familiar with <strong>rubygems.org</strong> and the reason that you see <code class="highlighter-rouge">source "https://rubygems.org"</code> at the top of every <code class="highlighter-rouge">Gemfile</code> is so Bundler can download and install gems from the rubygems server.</p> <p>What I, and I suspect most of you, never considered is that <code class="highlighter-rouge">source</code> could be pointed at, well, anything. In fact, you can have multiple sources as well, and you can even <a href="https://bundler.io/man/gemfile.5.html#BLOCK-FORM-OF-SOURCE-GIT-PATH-GROUP-and-PLATFORMS">write blocks to install groups of gems from different sources including git repos</a>. (TIL 👀)</p> <p>So Bundler is very flexible in this regard, as is the <code class="highlighter-rouge">gem</code> command (more on that in a moment). Which is why this news matters: <a href="https://martinemde.com/2025/10/05/announcing-gem-coop.html">The Gem Cooperative has announced</a> a new community-minded gem server is now available, currently mirroring all the gems from rubygems. Martin Emde says that “all Ruby developers are welcome to switch to using this new server immediately.”</p> <p>And here’s how you do it. Edit your project’s <code class="highlighter-rouge">Gemfile</code> and replace the <code class="highlighter-rouge">source</code> line at the top with this:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">source</span> <span class="s2">"https://gem.coop"</span> </code></pre></div></div> <p>Now you can <code class="highlighter-rouge">bundle install</code> and <code class="highlighter-rouge">bundle update</code> and support this new community effort.</p> <p>Who is behind The Gem Cooperative, you may ask? Basically it’s all those folks who had previously been working on rubygems before they were unceremoniously kicked out of Ruby Central during the takeover. Ouch. From <a href="https://gem.coop">gem.coop</a>, they are:</p> <ul> <li><a href="https://github.com/deivid-rodriguez">David Rodríguez</a></li> <li><a href="https://github.com/duckinator">Ellen Marie Dash</a></li> <li><a href="https://github.com/indirect">André Arko</a></li> <li><a href="https://github.com/martinemde">Martin Emde</a></li> <li><a href="https://github.com/segiddins">Samuel Giddins</a></li> <li><a href="https://github.com/simi">Josef Šimánek</a></li> </ul> <p>And <a href="https://github.com/MikeMcQuaid">Mike McQuaid</a> of Homebrew fame is also helping out with establishing governance for the project (and some technical advisory from the looks of things).</p> <p>Work is currently underway on an updated version of Bundler that will support namespaces, and once that happens and gem.coop is updated to support gem pushes, we could see folks decide to publish new/updated gems only to gem.coop under new namespaces. This is all in service of moving away from Ruby Central as a single (and very problematic) point of failure.</p> <p>All right, so updating your <code class="highlighter-rouge">Gemfile</code> is easy enough, but what about when you need to install new gems from scratch using the <code class="highlighter-rouge">gem</code> command? You will use <code class="highlighter-rouge">gem sources</code> for that. First, you can get a list of which sources you currently use by running <code class="highlighter-rouge">gem sources --list</code>. Typically you’ll just see the rubygems server listed. To add gem.coop, run:</p> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem sources <span class="nt">--add</span> https://gem.coop </code></pre></div></div> <p>and to remove rubygems, run:</p> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem sources <span class="nt">--remove</span> https://rubygems.org/ </code></pre></div></div> <p>You can verify when you install a new gem which server is used by including the <code class="highlighter-rouge">-V</code> flag, e.g.:</p> <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem <span class="nb">install </span>solargraph <span class="nt">-V</span> </code></pre></div></div> <p>Otherwise just use <code class="highlighter-rouge">gem</code> as per usual.</p> <p>With news like this and the previous round of news regarding <a href="https://github.com/spinel-coop/rv">rv</a> which is an attempt to create next-generation unified Ruby tooling (install Ruby, install dependencies, run app commands, create new gems, etc.), I think we may be on the cusp of a big leap forward for the language both in terms of technical prowess as well as acceptable forms of community governance. <img src="/images/ruby.svg" alt="small red gem symbolizing the Ruby language" width="14" style="vertical-align: -0.05em;margin-left: 0.1em" /></p>Jared Whitehttps://jaredwhite.comLittle Content Tricks for Your Bridgetown Website2025-09-30T09:14:30-07:002025-09-30T09:14:30-07:00repo://posts.collection/_posts/2025/2025-09-30-little-content-tricks-for-your-bridgetown-website.md<p>Well my Ruby friends, a new day has dawned with the <a href="https://www.bridgetownrb.com/release/bridgetown-v2-river-city-released/">release of the Ruby web framework Bridgetown 2</a>, and that means I can start to enjoy the fruits of our labor by sharing useful code examples and architectural explanations here on <strong>Fullstack Ruby</strong>. Yay! 🎉</p> <p>(BTW…how cool is this custom artwork by <a href="https://adrianvalenz.com/">Adrian Valenzuela</a>??)</p> <p style="max-width:840px; margin-block: var(--wlm-grid-row-spacing); margin-inline: auto; text-align: center"><img src="https://www.bridgetownrb.com/images/river-city-postcard-bridgetown.jpg" alt="Greetings from River City" style="width:100%" /></p> <p>Now onto today’s little batch of snippets.</p> <h2 id="swapping-video-links-with-embeds">Swapping Video Links with Embeds</h2> <p>On a Bridgetown client project, we wanted to be able to drop in links to the client’s many videos hosted on Vimeo. I didn’t want to have to deal with the hassle of grabbing <code class="highlighter-rouge">&lt;iframe&gt;</code> tags for every single video, so my first inclination was to write a helper method and use those calls in the markup where needed. But then I realized I could go a step further: <em>just paste in the damn link and get an embed on the other side!</em> 😂</p> <p>It needs to go from this Markdown source:</p> <div class="language-md highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ever wonder what it's like to dance under the sea? Here's your chance to experience lights that simulate moving water. These are customizable with different color variations and ripple speeds. https://vimeo.com/390917842 </code></pre></div></div> <p>to this HTML output:</p> <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;p&gt;</span>Ever wonder what it’s like to dance under the sea? Here’s your chance to experience lights that simulate moving water. These are customizable with different color variations and ripple speeds.<span class="nt">&lt;/p&gt;</span> <span class="nt">&lt;p&gt;&lt;iframe</span> <span class="na">src=</span><span class="s">"https://player.vimeo.com/video/390917842"</span> <span class="na">width=</span><span class="s">"640"</span> <span class="na">height=</span><span class="s">"360"</span> <span class="na">frameborder=</span><span class="s">"0"</span> <span class="na">allow=</span><span class="s">"autoplay; fullscreen; picture-in-picture"</span> <span class="na">allowfullscreen</span> <span class="na">loading=</span><span class="s">"lazy"</span><span class="nt">&gt;&lt;/iframe&gt;&lt;/p&gt;</span> </code></pre></div></div> <p>And using a bit of string substitution in a <a href="https://www.bridgetownrb.com/docs/plugins/hooks">builder hook</a>, the solution is straightforward indeed:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># plugins/builders/vimeo_embeds.rb</span> <span class="k">class</span> <span class="nc">Builders::VimeoEmbeds</span> <span class="o">&lt;</span> <span class="no">SiteBuilder</span> <span class="k">def</span> <span class="nf">build</span> <span class="n">hook</span> <span class="ss">:resources</span><span class="p">,</span> <span class="ss">:post_render</span> <span class="k">do</span> <span class="o">|</span><span class="n">resource</span><span class="o">|</span> <span class="n">resource</span><span class="p">.</span><span class="nf">output</span><span class="p">.</span><span class="nf">gsub!</span> <span class="sr">%r!&lt;p&gt;https://vimeo.com/([0-9]+)&lt;/p&gt;!</span><span class="p">,</span> <span class="sx">%(&lt;p&gt;&lt;iframe src="https://player.vimeo.com/video/</span><span class="se">\\</span><span class="sx">1" width="640" height="360" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen loading="lazy"&gt;&lt;/iframe&gt;&lt;/p&gt;)</span> <span class="k">end</span> <span class="k">end</span> <span class="k">end</span> </code></pre></div></div> <p>In your case you might be using YouTube, or PeerTube, or some other form of video hosting, but the concept would be just the same. You could even layer up several <code class="highlighter-rouge">gsub</code> calls to handle them all.</p> <h2 id="lazy-images">Lazy Images</h2> <p>For better frontend performance, due to the large number of images we display on some of the content pages, I wanted to ensure that images added in the Markdown would output with a <code class="highlighter-rouge">loading="lazy"</code> attribute. This tells browsers to hold off on loading the image until the reader scrolls down to that place in the document.</p> <p>After making sure I had <code class="highlighter-rouge">gem "nokolexbor"</code> installed, and had added <code class="highlighter-rouge">html_inspector_parser "nokolexbor"</code> to my Bridgetown configuration in <code class="highlighter-rouge">config/initializers.rb</code>, I proceeded to write an <a href="https://www.bridgetownrb.com/docs/plugins/inspectors">HTML inspector</a> plugin to do the job:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># plugins/builders/lazy_images.rb</span> <span class="k">class</span> <span class="nc">Builders::LazyImages</span> <span class="o">&lt;</span> <span class="no">SiteBuilder</span> <span class="k">def</span> <span class="nf">build</span> <span class="n">inspect_html</span> <span class="k">do</span> <span class="o">|</span><span class="n">doc</span><span class="o">|</span> <span class="n">main</span> <span class="o">=</span> <span class="n">doc</span><span class="p">.</span><span class="nf">query_selector</span><span class="p">(</span><span class="s1">'main'</span><span class="p">)</span> <span class="k">next</span> <span class="k">unless</span> <span class="n">main</span> <span class="n">main</span><span class="p">.</span><span class="nf">query_selector_all</span><span class="p">(</span><span class="s2">"img"</span><span class="p">).</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">img</span><span class="o">|</span> <span class="k">next</span> <span class="k">if</span> <span class="n">img</span><span class="p">[</span><span class="ss">:loading</span><span class="p">]</span> <span class="n">img</span><span class="p">[</span><span class="ss">:loading</span><span class="p">]</span> <span class="o">=</span> <span class="ss">:lazy</span> <span class="k">end</span> <span class="k">end</span> <span class="k">end</span> <span class="k">end</span> </code></pre></div></div> <p>This loops through all <code class="highlighter-rouge">img</code> tags within the <code class="highlighter-rouge">main</code> layout element, and if it doesn’t already have a <code class="highlighter-rouge">loading</code> attribute, it will get set.</p> <h2 id="extracting-an-image-for-graph">Extracting an Image for Graph</h2> <p><a href="https://theinternet.review">On another project</a>, I wanted to have some smarts where the image used for open graph previews could be pulled directly out of the content, rather than me having to set an <code class="highlighter-rouge">image</code> front matter variable by hand. I decided to solve this with a bit of regex wizardry:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># plugins/builders/image_extractions.rb</span> <span class="k">class</span> <span class="nc">Builders::ImageExtractions</span> <span class="o">&lt;</span> <span class="no">SiteBuilder</span> <span class="k">def</span> <span class="nf">build</span> <span class="n">hook</span> <span class="ss">:posts</span><span class="p">,</span> <span class="ss">:pre_render</span> <span class="k">do</span> <span class="o">|</span><span class="n">resource</span><span class="o">|</span> <span class="k">next</span> <span class="k">if</span> <span class="n">resource</span><span class="p">.</span><span class="nf">data</span><span class="p">.</span><span class="nf">image</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">resource</span><span class="p">.</span><span class="nf">data</span><span class="p">.</span><span class="nf">image</span><span class="p">.</span><span class="nf">end_with?</span><span class="p">(</span><span class="s2">"an-image-i-wanted-to-skip-here.png"</span><span class="p">)</span> <span class="n">md_img</span> <span class="o">=</span> <span class="n">resource</span><span class="p">.</span><span class="nf">content</span><span class="p">.</span><span class="nf">match</span> <span class="sr">%r!</span><span class="se">\!\[</span><span class="sr">.*?</span><span class="se">\]\(</span><span class="sr">(.*?)</span><span class="se">\)</span><span class="sr">!</span> <span class="n">img_url</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="n">md_img</span><span class="o">&amp;</span><span class="p">.</span><span class="nf">captures</span> <span class="k">unless</span> <span class="n">img_url</span> <span class="n">html_img</span> <span class="o">=</span> <span class="n">resource</span><span class="p">.</span><span class="nf">content</span><span class="p">.</span><span class="nf">match</span> <span class="sr">%r!&lt;img src="(.*?)"!</span> <span class="n">img_url</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="n">html_img</span><span class="o">&amp;</span><span class="p">.</span><span class="nf">captures</span> <span class="k">end</span> <span class="k">if</span> <span class="n">img_url</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">img_url</span><span class="p">.</span><span class="nf">end_with?</span><span class="p">(</span><span class="s2">".gif"</span><span class="p">)</span> <span class="n">img_url</span> <span class="o">=</span> <span class="n">img_url</span><span class="p">.</span><span class="nf">start_with?</span><span class="p">(</span><span class="s2">"http"</span><span class="p">)</span> <span class="p">?</span> <span class="n">img_url</span> <span class="p">:</span> <span class="s2">"</span><span class="si">#{</span><span class="n">site</span><span class="p">.</span><span class="nf">config</span><span class="p">.</span><span class="nf">url</span><span class="si">}#{</span><span class="n">img_url</span><span class="si">}</span><span class="s2">"</span> <span class="c1"># Set the image front matter to the found URL</span> <span class="n">resource</span><span class="p">.</span><span class="nf">data</span><span class="p">.</span><span class="nf">image</span> <span class="o">=</span> <span class="n">img_url</span> <span class="k">end</span> <span class="k">end</span> <span class="k">end</span> <span class="k">end</span> </code></pre></div></div> <p>You could simplify this if you’re only dealing with Markdown content…in my case I have a lot of <em>old</em> HTML-based content predating the age of modern Markdown files, so I need to support both input formats.</p> <p>And that’s it for today’s round of <strong>Bridgetown tips</strong>! To stay in touch for the next installment, make sure that you <a href="https://ruby.social/@fullstackruby">follow us on Mastodon</a> and <a href="/about">subscribe to the newsletter</a>. What would you like to learn about next for building websites with Bridgetown? Let us know! ☺️ <img src="/images/ruby.svg" alt="small red gem symbolizing the Ruby language" width="14" style="vertical-align: -0.05em;margin-left: 0.1em" /></p>Jared Whitehttps://jaredwhite.comSunsetting the Fullstack Ruby Podcast (and What I’m Doing Instead)2025-06-29T10:54:27-07:002025-06-29T10:54:27-07:00repo://posts.collection/_posts/2025/2025-06-29-sunsetting-the-podcast.md<p>I always hate writing posts like this, which is why I rarely do it and tend to let content destinations linger on the interwebs indefinitely.</p> <p>But I’m in the midst of <del>spring</del> summer cleaning regarding all things content creation, so I figured it’s best to be upfront about these things and give folks a heads up what I’m currently working on.</p> <p><strong>TL;DR:</strong> I’m bidding the Fullstack Ruby podcast a bittersweet farewell and gearing up to launch a new podcast centered on current events in the software &amp; internet technology space, because we’ve reached a <strong>crisis point</strong> and future of the open web is more fragile than ever.</p> <hr /> <p><strong>Here’s the truth.</strong> There’s a lot that’s fucked up about Big Tech and software development right now. Pardon my language, but I have struggled mightily with burnout for going on two years now; not because I don’t like writing software (oddly enough, I care as much about the actual work I do as a developer as I ever have!), but because <em>I don’t like the software industry</em>. ☹️</p> <p>And sadly, I have been particularly disappointed with what’s going on with Ruby. I don’t want to rehash past consternation (you can <a href="https://www.fullstackruby.dev/fullstack-development/2024/03/31/ruby-fully-stacked/">read about my attempt</a> to fully reboot Fullstack Ruby a year ago for more background, and listen to the <a href="https://www.fullstackruby.dev/podcast/10/">followup podcast episode</a>). Here’s the summary:</p> <p>There are <em>two devastating downward pressures</em> on the software industry right now: the unholy alliance of far-right bigotry/propaganda &amp; Big Tech, and the atomic bomb-level threat to the open web that is Generative AI. And the crazy part is, there’s actually a cultural connection between fascism and genAI so in a sense, these aren’t two separate problems. <strong>They’re the same problem.</strong></p> <p>Unfortunately, the Ruby community taken as a whole has done NOTHING to fight these problems. Certain individuals have, yes. Good for them. It’s not moving the needle though.</p> <p>Ruby already has suffered in recent years from the brain-drain problem and the lack of mainstream awareness for new project development. I have always felt that problem alone is one we can surmount. But when you pile on top of that the <strong>fascism problem</strong> (personified in the founder &amp; figurehead of Ruby on Rails, DHH) and the <strong>AI problem</strong> (which major voices in the Ruby space have not only failed to combat but <em>they’re actively advocating for and encouraging</em> genAI use), I find it increasingly difficult to remain an active cheerleader going forward.</p> <p>Don’t get me wrong, I’m not saying it’s any better per se in other programming language communities. But if I have to deal with fighting off fascism and the evils of Big Tech on a daily basis, I might as well be writing JavaScript while I’m doing it. JavaScript is the lingua franca of the web. It just is. And it’s already what we use for frontend, out of technical necessity.</p> <p><strong>I still prefer writing Ruby on the backend.</strong> I do, I really do! And I’m not sure yet I’m ready to give that up, even now. But it does mean I find my enthusiasm for talking about Ruby and recommending it to others fading into the background. Damn.</p> <hr /> <p><strong>I haven’t yet decided</strong> what the ultimate fate of this blog is. But I do know I’m ready to scale my ambitions way down in this particular community, so to start, I must bid the podcast farewell. 🫡</p> <p>As I alluded to above, I’m actually gearing up to launch a brand new podcast! You may be interested it in, you may not. Regardless, the easiest way to stay notified on the launch is to <a href="https://indieweb.social/@jaredwhite">follow me on Mastodon</a> and <a href="https://buttondown.com/theinternet">subscribe to the Cycles Hyped No More newsletter</a> (the podcast will be a companion product if you will to that newsletter).</p> <p>It should come to no surprise by now that the purpose of the new podcast is to take the twin perils of fascism-adjacent Big Tech and genAI head on. I will be speaking about this early, often, for as long as it takes for people to <em>wake up</em> and realize the open web is under assault. I don’t mean to sound unnecessarily dramatic, but while we’re over here arguing about programming languages and coding patterns and architectural choices for our web apps, <strong>the very web itself</strong> is getting pillaged and dismantled brick by brick by hostile forces.</p> <p><strong>I’m not going to let that happen without a fight.</strong></p> <p>I hope you’re interested in joining me in this fight. <em>Stay tuned.</em></p> <p>–Jared ✌️</p>Jared Whitehttps://jaredwhite.comFinding My Happy Place with Hanami and Serbea Templates2025-02-06T19:05:45-08:002025-02-06T19:05:45-08:00repo://posts.collection/_posts/2025/2025-02-06-finding-my-happy-place-hanami-serbea.md<p>It sure seems like the <a href="https://hanamirb.org">Hanami web framework</a> has been in the news lately, most notably the announcement that Mike Perham of <a href="https://sidekiq.org">Sidekiq</a> fame <a href="https://www.mikeperham.com/2025/01/17/sponsoring-hanami/">has provided a $10,000 grant to Hanami</a> to keep building off the success of version 2.2. I also deeply appreciate Hanami’s <a href="https://hanamirb.org#our-community-values#:~:text=Our community values">commitment to fostering a welcoming and inclusive community</a>.</p> <p>Thus I figured it was high time I took Hanami for a spin, so after running <code class="highlighter-rouge">gem install hanami</code>, along with a few setup commands and a few files to edit, I had a working Ruby-powered website running with Hanami! <strong>Yay!!</strong> 🎉</p> <p>But then I started to miss the familiar comforts of <a href="https://www.serbea.dev">Serbea</a>, a Ruby template language based on ERB but with a few extra tricks up its sleeve to make it feel more like “brace-style” template languages such as Liquid, Nunjucks, Twig, Jinja, Mustache, etc. I’ve been using Serbea on nearly all of my <a href="https://www.bridgetownrb.com">Bridgetown</a> sites as well as a substantial Rails client project, so it’s second nature to write my HTML in this syntax. (Plus, y’know, Serbea is a <a href="https://rubygems.org/gems/serbea">gem I wrote</a>. 😋)</p> <p>After feeling sad for a moment, it occurred to me that I’d read that Hanami—like many Ruby frameworks—<a href="https://guides.hanamirb.org/v2.2/views/templates-and-partials/#template-engines">uses Tilt under the hood to load templates</a>. <em>Ah ha!</em> Serbea is also built on top of Tilt, so it shouldn’t be too difficult to get things working. One small hurdle I knew I’d have to overcome is that I don’t auto-register Serbea as a handler for “.serb” files, as Serbea requires a mixin for its “pipeline” syntax support as an initial setup step. So I’d need to figure out where that registration should go and how to apply the mixin.</p> <p>Turns out, there was a pretty straightforward solution. (Thanks Hanami!) I found that templates are rendered within a <code class="highlighter-rouge">Scope</code> object, and while a new Hanami application doesn’t include a dedicated “base scope” class out of the box, it’s very easy to create one. Here’s what mine looks like with the relevant Serbea setup code:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># this file is located at app/views/scope.rb</span> <span class="nb">require</span> <span class="s2">"serbea"</span> <span class="no">Tilt</span><span class="p">.</span><span class="nf">register</span> <span class="no">Tilt</span><span class="o">::</span><span class="no">SerbeaTemplate</span><span class="p">,</span> <span class="s2">"serb"</span> <span class="k">module</span> <span class="nn">YayHanami</span> <span class="k">module</span> <span class="nn">Views</span> <span class="k">class</span> <span class="nc">Scope</span> <span class="o">&lt;</span> <span class="no">Hanami</span><span class="o">::</span><span class="no">View</span><span class="o">::</span><span class="no">Scope</span> <span class="kp">include</span> <span class="no">Serbea</span><span class="o">::</span><span class="no">Helpers</span> <span class="k">end</span> <span class="k">end</span> <span class="k">end</span> </code></pre></div></div> <p>Just be sure to replace <code class="highlighter-rouge">YayHanami</code> with your application or slice constant. That and a <code class="highlighter-rouge">bundle add serbea</code> should be all that’s required to get Serbea up and running!</p> <p>Once this was all in place, I was able to convert my <code class="highlighter-rouge">.html.erb</code> templates to <code class="highlighter-rouge">.html.serb</code>. I don’t have anything whiz-bang to show off yet, but for your edification here’s one of Hanami’s ERB examples rewritten in Serbea:</p> <div class="language-serb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;h1&gt;</span>What's on the Bookshelf<span class="nt">&lt;/h1&gt;</span> <span class="nt">&lt;ul&gt;</span> <span class="cp">{%</span> <span class="n">books</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">book</span><span class="o">|</span> <span class="cp">%}</span> <span class="nt">&lt;li&gt;</span><span class="cp">{{</span> <span class="n">book</span><span class="p">.</span><span class="nf">title</span> <span class="cp">}}</span><span class="nt">&lt;/li&gt;</span> <span class="cp">{%</span> <span class="k">end</span> <span class="cp">%}</span> <span class="nt">&lt;/ul&gt;</span> <span class="nt">&lt;h2&gt;</span>Don't miss these best selling titles<span class="nt">&lt;/h2&gt;</span> <span class="nt">&lt;ul&gt;</span> <span class="cp">{%</span> <span class="n">best_sellers</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">book</span><span class="o">|</span> <span class="cp">%}</span> <span class="nt">&lt;li&gt;</span><span class="cp">{{</span> <span class="n">book</span><span class="p">.</span><span class="nf">title</span> <span class="cp">}}</span><span class="nt">&lt;/li&gt;</span> <span class="cp">{%</span> <span class="k">end</span> <span class="cp">%}</span> <span class="nt">&lt;/ul&gt;</span> </code></pre></div></div> <p>This may not look super thrilling, but imagine you wanted to write a helper that automatically creates a search link for a book title and author to a service like <a href="https://bookwyrm.social">BookWyrm</a>. You could add a method to your Scope class like so:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">bookwyrm</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">author</span><span class="p">:)</span> <span class="s2">"&lt;a href='https://bookwyrm.social/search?q=</span><span class="si">#{</span><span class="n">escape</span><span class="p">(</span><span class="n">input</span><span class="p">)</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">escape</span><span class="p">(</span><span class="n">author</span><span class="p">)</span><span class="si">}</span><span class="s2">'&gt;</span><span class="si">#{</span><span class="n">escape</span><span class="p">(</span><span class="n">input</span><span class="p">)</span><span class="si">}</span><span class="s2">&lt;/a&gt;"</span><span class="p">.</span><span class="nf">html_safe</span> <span class="k">end</span> </code></pre></div></div> <p>and then use it filter-style in the template:</p> <div class="language-serb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;li&gt;</span><span class="cp">{{</span> <span class="n">book</span><span class="p">.</span><span class="nf">title</span> <span class="o">|</span> <span class="ss">bookwyrm: author: </span><span class="n">book</span><span class="p">.</span><span class="nf">author</span> <span class="cp">}}</span><span class="nt">&lt;/li&gt;</span> </code></pre></div></div> <p>I like this much more than in ERB where helpers are placed <em>before</em> the data they’re acting upon which to me feels like a logical inversion:</p> <div class="language-erb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;li&gt;</span><span class="cp">&lt;%=</span> <span class="n">bookwyrm</span><span class="p">(</span><span class="n">book</span><span class="p">.</span><span class="nf">title</span><span class="p">,</span> <span class="ss">author: </span><span class="n">book</span><span class="p">.</span><span class="nf">author</span><span class="p">)</span> <span class="cp">%&gt;</span><span class="nt">&lt;/li&gt;</span> </code></pre></div></div> <p>Hmm. 🤨</p> <p>Anyway, I’m totally jazzed that I got <strong>Hanami</strong> and <strong>Serbea</strong> playing nicely together, and I can’t wait to see what I might try building next in Hanami! This will be an ongoing series here on <strong>Fullstack Ruby</strong> (loosely titled “Jared Tries to Do Unusual Things with Hanami”), so make sure that you <a href="https://ruby.social/@fullstackruby">follow us on Mastodon</a> and <a href="/about">subscribe to the newsletter</a> to keep abreast of further developments. <img src="/images/ruby.svg" alt="small red gem symbolizing the Ruby language" width="14" style="vertical-align: -0.05em;margin-left: 0.1em" /></p>Jared Whitehttps://jaredwhite.comA Casual Conversation with KOW (Karl Oscar Weber) on Camping, Open Source Politics, and More2024-12-18T10:58:07-08:002024-12-18T10:58:07-08:00repo://posts.collection/_posts/podcast/12.md<iframe width="100%" height="180" frameborder="no" scrolling="no" seamless="" src="https://share.transistor.fm/e/6104afa7"></iframe> <p>This is a right humdinger of an episode of <strong>Fullstack Ruby</strong>! I got the chance to talk with Karl Oscar Weber all about the Camping web framework, as well as his Grilled Cheese livestream, working as a freelancer, and how to criticize by creating as a programmer in a world fraught with political upheaval. <em>Great craic</em>, as the Irish say.</p> <h2 id="links--show-notes">Links &amp; Show Notes:</h2> <ul> <li><a href="https://buttondown.com/fullstackruby">Sign up for the Fullstack Ruby newsletter</a></li> <li><a href="https://plus.intuitivefuture.com/">Get bonus issues and support this content as an Intuitive+ member!</a></li> <li>Guest: <strong>Karl Oscar Weber</strong> (<a href="https://ruby.social/@kowfm">[email protected]</a>)</li> <li><a href="https://capsule.graphics/">Capsule Graphics</a></li> <li><a href="https://rubycamping.org/">Camping</a></li> <li><a href="https://www.twitch.tv/kowfm">Grilled Cheese</a> (Twitch)</li> <li><a href="https://konascript.org/">Kona</a></li> </ul>jaredTired of Dealing with Arguments? Just Forward Them Anonymously!2024-12-12T11:40:08-08:002024-12-12T11:40:08-08:00repo://posts.collection/_posts/2024/2024-12-11-simplified-anonymous-argument-forwarding.md<p>I don’t know about you, but after a while, I just get tired of the same arguments. Wouldn’t it be great if I could simply <em>forward</em> them instead? Let somebody else handle those arguments!</p> <p>OK I kid, I kid…but it’s definitely true that argument forwarding is an important aspects of API design in Ruby, and <strong>anonymous argument forwarding</strong> is a pretty awesome feature of recent versions of Ruby.</p> <p>Let’s first step through a history of argument forwarding in Ruby.</p> <hr /> <aside> <p><strong>Pedantic Ruby Question:</strong> Are they called method arguments, or method parameters? And when are they called arguments and when are they called parameters? And are keyword arguments also called named parameters?! I’m not entirely sure. I’m just sticking with arguments! 😂</p> </aside> <hr /> <h2 id="the-era-before-keyword-arguments">The Era Before Keyword Arguments</h2> <p>In the days before Ruby 2.0, Ruby didn’t actually have a language construct for what we call keyword arguments at the method definition level. All we had were positional arguments. So to “simulate” keyword arguments, you could call a method with what looked like keyword arguments (really, it was akin to Hash syntax), and all those key/value pairs would be added to a Hash argument at the end.</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">method_with_hash</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span> <span class="o">=</span> <span class="p">{})</span> <span class="nb">puts</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span> <span class="k">end</span> <span class="n">method_with_hash</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="ss">hello: </span><span class="s2">"world"</span><span class="p">,</span> <span class="ss">numbers: </span><span class="mi">123</span><span class="p">)</span> </code></pre></div></div> <p><a href="https://try.ruby-lang.org/playground/#code=def+method_with_hash%28a%2C+b%2C+c+%3D+%7B%7D%29%0A++puts+a%2C+b%2C+c%0Aend%0A%0Amethod_with_hash%281%2C+2%2C+hello%3A+%22world%22%2C+numbers%3A+123%29&amp;engine=cruby-3.2.0" class="button">Run in Try Ruby</a></p> <p>Fun fact, you can still do this even today! But it’s not recommended. Instead, we were graced with true language-level keyword arguments in Ruby 2. To build on the above:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">method_with_kwargs</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">hello</span><span class="p">:,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="nb">puts</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">hello</span><span class="p">,</span> <span class="n">kwargs</span> <span class="k">end</span> <span class="n">method_with_kwargs</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="ss">hello: </span><span class="s2">"world"</span><span class="p">,</span> <span class="ss">numbers: </span><span class="mi">123</span><span class="p">)</span> </code></pre></div></div> <p><a href="https://try.ruby-lang.org/playground/#code=def+method_with_kwargs%28a%2C+b%2C+hello%3A%2C+%2A%2Akwargs%29%0A++puts+a%2C+b%2C+hello%2C+kwargs%0Aend%0A%0Amethod_with_kwargs%281%2C+2%2C+hello%3A+%22world%22%2C+numbers%3A+123%29&amp;engine=cruby-3.2.0" class="button">Run in Try Ruby</a></p> <p>Here we’re specifying <code class="highlighter-rouge">hello</code> as a true keyword argument, but also allowing additional keyword arguments to get passed in via an argument splat.</p> <p>Back to the past though. When we just had positional arguments, it was “easy” to forward arguments because there was only one type of argument:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">pass_it_along</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span> <span class="n">you_take_care_of_it</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">pass_the_block_too</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span><span class="p">)</span> <span class="c1"># if you want the forward a block</span> <span class="n">do_all_the_things</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span><span class="p">)</span> <span class="k">end</span> </code></pre></div></div> <p>There is also a way to say “ignore all arguments, I don’t need them” which is handy when a subclass wants to override a superclass method and really doesn’t care about arguments for some reason:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">ignore_the_args</span><span class="p">(</span><span class="o">*</span><span class="p">)</span> <span class="n">bye_bye!</span> <span class="k">end</span> </code></pre></div></div> <h2 id="the-messy-middle">The Messy Middle</h2> <p>Things became complicated when we first got keyword arguments, because the question becomes: when you forward arguments the traditional way, do you get real keyword arguments forwarded as well, or do you just get a boring Hash?</p> <p>For the life of Ruby 2, this worked one way, and then we got a big change in Ruby 3 (and really it took a few iterations before a fully clean break).</p> <p>In Ruby 2, forwarding positional arguments only would automatically convert keywords over to keyword arguments in the receiving method:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">pass_it_along</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span> <span class="n">you_take_care_of_it</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">you_take_care_of_it</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="ss">abc: </span><span class="mi">0</span><span class="p">)</span> <span class="nb">puts</span> <span class="s2">"</span><span class="si">#{</span><span class="n">args</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">abc</span><span class="si">}</span><span class="s2">"</span> <span class="k">end</span> <span class="n">pass_it_along</span><span class="p">(</span><span class="s2">"hello"</span><span class="p">,</span> <span class="ss">abc: </span><span class="mi">123</span><span class="p">)</span> <span class="c1"># ["hello"] 123</span> </code></pre></div></div> <p>However, in the Ruby of today, this works differently. There’s a special <code class="highlighter-rouge">ruby2_keywords</code> method decorator that lets you simulate how things used to be, but it’s well past its sell date. What you should do instead is forward keyword arguments separately:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">pass_it_along</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="n">you_take_care_of_it</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">you_take_care_of_it</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="ss">abc: </span><span class="mi">0</span><span class="p">)</span> <span class="nb">puts</span> <span class="s2">"</span><span class="si">#{</span><span class="n">args</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">abc</span><span class="si">}</span><span class="s2">"</span> <span class="k">end</span> <span class="n">pass_it_along</span><span class="p">(</span><span class="s2">"hello"</span><span class="p">,</span> <span class="ss">abc: </span><span class="mi">123</span><span class="p">)</span> <span class="c1"># ["hello"] 123</span> </code></pre></div></div> <p>But…by the time you also add in block forwarding, this really starts to look messy. And as Rubyists, who likes messy?</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">pass_it_along</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span><span class="p">)</span> <span class="n">you_take_care_of_it</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span><span class="p">)</span> <span class="c1"># Ugh!</span> <span class="k">end</span> </code></pre></div></div> <p>Thankfully, we have a few syntactic sugar options available to use, some rather recent. Let’s take a look!</p> <h2 id="give-me-that-sweet-sweet-sugar">Give Me that Sweet, Sweet Sugar</h2> <p>The first thing you can do is use triple-dots notation, which we’ve had since Ruby 2.7:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">pass_it_along</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="n">you_take_care_of_it</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">you_take_care_of_it</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span><span class="p">)</span> <span class="nb">puts</span> <span class="p">[</span><span class="n">args</span><span class="p">,</span> <span class="n">kwargs</span><span class="p">,</span> <span class="n">block</span><span class="o">.</span><span class="p">()]</span> <span class="k">end</span> <span class="n">pass_it_along</span><span class="p">(</span><span class="s2">"hello"</span><span class="p">,</span> <span class="ss">abc: </span><span class="mi">123</span><span class="p">)</span> <span class="p">{</span> <span class="s2">"I'm not a blockhead!"</span> <span class="p">}</span> </code></pre></div></div> <p><a href="https://try.ruby-lang.org/playground/#code=def+pass_it_along%28...%29%0A++you_take_care_of_it%28...%29%0Aend%0A%0Adef+you_take_care_of_it%28%2Aargs%2C+%2A%2Akwargs%2C+%26block%29%0A++puts+%5Bargs%2C+kwargs%2C+block.%28%29%5D%0Aend%0A%0Apass_it_along%28%22hello%22%2C+abc%3A+123%29+%7B+%22I%27m+not+a+blockhead%21%22+%7D&amp;engine=cruby-3.2.0" class="button">Run in Try Ruby</a></p> <p>This did limit the ability to add anything extra in method definitions or invocations, but since Ruby 3.0 you can prefix with positional arguments if you wish:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">pass_it_along</span><span class="p">(</span><span class="n">str</span><span class="p">,</span> <span class="o">...</span><span class="p">)</span> <span class="n">you_take_care_of_it</span><span class="p">(</span><span class="n">str</span><span class="p">.</span><span class="nf">upcase</span><span class="p">,</span> <span class="o">...</span><span class="p">)</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">you_take_care_of_it</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span><span class="p">)</span> <span class="nb">puts</span> <span class="p">[</span><span class="n">args</span><span class="p">,</span> <span class="n">kwargs</span><span class="p">,</span> <span class="n">block</span><span class="o">.</span><span class="p">()]</span> <span class="k">end</span> <span class="n">pass_it_along</span><span class="p">(</span><span class="s2">"hello"</span><span class="p">,</span> <span class="ss">abc: </span><span class="mi">123</span><span class="p">)</span> <span class="p">{</span> <span class="s2">"I'm not a blockhead!"</span> <span class="p">}</span> </code></pre></div></div> <p><a href="https://try.ruby-lang.org/playground/#code=def+pass_it_along%28str%2C+...%29%0A++you_take_care_of_it%28str.upcase%2C+...%29%0Aend%0A%0Adef+you_take_care_of_it%28%2Aargs%2C+%2A%2Akwargs%2C+%26block%29%0A++puts+%5Bargs%2C+kwargs%2C+block.%28%29%5D%0Aend%0A%0Apass_it_along%28%22hello%22%2C+abc%3A+123%29+%7B+%22I%27m+not+a+blockhead%21%22+%7D&amp;engine=cruby-3.2.0" class="button">Run in Try Ruby</a></p> <p>However, for more precise control over what you’re forwarding, first Ruby 3.1 gave us an “anonymous” block operator:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">block_party</span><span class="p">(</span><span class="o">&amp;</span><span class="p">)</span> <span class="n">lets_party</span><span class="p">(</span><span class="o">&amp;</span><span class="p">)</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">lets_party</span> <span class="s2">"Oh yeah, </span><span class="si">#{</span><span class="k">yield</span><span class="si">}</span><span class="s2">!"</span> <span class="k">end</span> <span class="n">block_party</span> <span class="p">{</span> <span class="s2">"baby"</span> <span class="p">}</span> </code></pre></div></div> <p><a href="https://try.ruby-lang.org/playground/#code=def+block_party%28%26%29%0A++lets_party%28%26%29%0Aend%0A%0Adef+lets_party%0A++%22Oh+yeah%2C+%23%7Byield%7D%21%22%0Aend%0A%0Ablock_party+%7B+%22baby%22+%7D&amp;engine=cruby-3.2.0" class="button">Run in Try Ruby</a></p> <p>And then Ruby 3.2 gave us anonymous positional and keyword forwarding as well:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">pass_it_along</span><span class="p">(</span><span class="o">*</span><span class="p">,</span> <span class="o">**</span><span class="p">)</span> <span class="n">you_take_care_of_it</span><span class="p">(</span><span class="o">*</span><span class="p">,</span> <span class="o">**</span><span class="p">)</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">you_take_care_of_it</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="ss">abc: </span><span class="mi">0</span><span class="p">)</span> <span class="nb">puts</span> <span class="s2">"</span><span class="si">#{</span><span class="n">args</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">abc</span><span class="si">}</span><span class="s2">"</span> <span class="k">end</span> <span class="n">pass_it_along</span><span class="p">(</span><span class="s2">"hello"</span><span class="p">,</span> <span class="ss">abc: </span><span class="mi">123</span><span class="p">)</span> </code></pre></div></div> <p><a href="https://try.ruby-lang.org/playground/#code=def+pass_it_along%28%2A%2C+%2A%2A%29%0A++you_take_care_of_it%28%2A%2C+%2A%2A%29%0Aend%0A%0Adef+you_take_care_of_it%28%2Aargs%2C+abc%3A+0%29%0A++puts+%22%23%7Bargs%7D+%23%7Babc%7D%22%0Aend%0A%0Apass_it_along%28%22hello%22%2C+abc%3A+123%29&amp;engine=cruby-3.2.0" class="button">Run in Try Ruby</a></p> <p>So at this point, you can mix ‘n’ match all of those anonymous operators however you see fit.</p> <p>The reason you’d still want to use syntax like <code class="highlighter-rouge">*args</code>, <code class="highlighter-rouge">**kwargs</code>, or <code class="highlighter-rouge">&amp;block</code> in a method definition is if you need to do something with those values <strong>before</strong> forwarding them, or in some metaprogramming cases. Otherwise, using anonymous arguments (or just a basic <code class="highlighter-rouge">...</code>) is likely the best solution going, uh, <em>forward</em>. 😎</p> <hr /> <aside> <p><strong>Background:</strong> you can read about some of the particulars of <em>how</em> we got these features and the personalities involved, as well as some potential gotchas, <a href="https://zverok.space/blog/2023-11-24-syntax-sugar4-argument-forwarding.html">here in zverok’s excellent writeup</a>.</p> </aside> <hr /> <h2 id="do-you-need-more-advanced-delegation">Do You Need More Advanced Delegation?</h2> <p>There are also higher-level constructs available in Ruby to forward, or <em>delegate</em>, logic to other objects:</p> <p>The <a href="https://ruby-doc.org/3.3.6/stdlibs/forwardable/Forwardable.html">Forwardable</a> module is a stdlib mixin which lets you specify one or more methods to forward using class methods <code class="highlighter-rouge">def_delegator</code> or <code class="highlighter-rouge">def_delegators</code>.</p> <p>The <a href="https://ruby-doc.org/3.3.6/stdlibs/delegate/Delegator.html">Delegator</a> class is part of the stdlib and lets you wrap a another class and then add on some additional features.</p> <p>So depending on your needs, it may make more sense to rely on those additional stdlib features rather than handle argument forwarding yourself at the syntax level.</p> <p>No matter what though, it’s clear we have many good options for defining an API where one part of the system can hand logic off to another part of the system. This isn’t perhaps as common when you’re writing application-level code, but if you’re working on a gem or a framework, it can come up quite often. <strong>It’s nice to know that what was once rather cumbersome is now more streamlined in recent releases of Ruby.</strong> <img src="/images/ruby.svg" alt="small red gem symbolizing the Ruby language" width="14" style="vertical-align: -0.05em;margin-left: 0.1em" /></p>Jared Whitehttps://jaredwhite.comDissecting Bridgetown 2.0’s Signalize-based Fast Refresh2024-11-20T09:24:48-08:002024-11-20T09:24:48-08:00repo://posts.collection/_posts/2024/2024-11-20-dissecting-fash-refresh-and-signals-in-bridgetown.md<p>As the lead maintainer of the <a href="https://www.bridgetownrb.com">Bridgetown web framework</a>, I get to work on interesting (and sometimes very thorny!) Ruby problems which veer from what is typical for individual application projects.</p> <p>With version 2 of Bridgetown about to drop, I’m starting a series of articles regarding intriguing aspects of the framework’s internals. This time around, we’re taking a close look at one of the marquee features: <strong>Fast Refresh</strong>.</p> <h2 id="the-feedback-loop">The Feedback Loop</h2> <p>Bridgetown is billed as a “progressive site generator” which offers a “hybrid” architecture for application deployments. What all this jargon means is that you can have both statically-generated content which is output as final HTML and other files to a destination folder, and dynamically-served routes which offer the typical request/response cycle you see in traditional web applications.</p> <p>When it comes to the common development feedback loop of save-and-reload, traditional web applications are fairly straightforward. You make a change to some bit of code or content, you reload your browser tab which makes a new request to the application server, and <em>BOOM!</em> You’re refeshed.</p> <p>But what about in a static site? You make a change, and suddenly the question becomes: <em>which</em> HTML files need to be regenerated? And what if your change isn’t in a specific page template or blog post or whatever, but some shared template or model or even another page that’s referenced by the one you’re trying to look at? Suddenly you’re talking about the possibility that your change might require regenerating only one literal <code class="highlighter-rouge">.html</code> file…or <em>thousands</em>. As the saying goes, <strong>it depends</strong>.</p> <p>Prior to the fast refresh feature, Bridgetown regenerated <em>an entire website</em> on every change. You fix a typo in a single Markdown file…entire site regenerated. You update a logo URL in a site layout header…entire site regenerated. This may sound like a slow and laborious process, but on most sites of modest size, complete regeneration is only a second or two. Not that big of a deal, right?</p> <p>And yet…some sites definitely grow beyond modest size. On my personal blog <a href="https://jaredwhite.com">Jared White.com</a>, the number of resources (posts, podcast episodes, photos, etc.) plus the number of generated pages (tag archives, category archives, etc.) has reached around 1,000 at this point, with no signs of stopping. What used to be measured in the milliseconds is now measured in the <strong>seconds</strong> on a full build—and while that’s perfectly reasonable in production when a site’s deploying, it <em>stinks</em> when you’re talking about that save-and-reload process in development.</p> <p>Hence the need for a new approach. Some frameworks call this “incremental regeneration”, but I think “fast refresh” sounds cooler. Bridgetown has already had a live reload feature since its inception—aka, you don’t need to manually go to your browser and reload the page, <strong>the framework does it for you</strong>. But now with fast refresh enabled, your browser reloads almost instantly! It’s so fast, sometimes by the time I get back to my browser window, <em>the change has already appeared</em>. What a huge quality of life improvement! <strong>DX at its finest.</strong></p> <p>But how did we pull off such a feat? How do we know <em>which</em> <code class="highlighter-rouge">.html</code> files need to be regenerated? Is it ✨ magic ✨? The power of AI?</p> <p>Nope. Just some good ol’ fashioned dependency-tracking via linked lists and closures…aka Signals. What the what? <strong>Let’s dive in.</strong></p> <h2 id="i-thought-signals-was-a-frontend-thing-why-does-ruby-need-them">I thought “signals” was a frontend thing. Why does Ruby need them?</h2> <p>I’ve <a href="https://www.fullstackruby.dev/podcast/9/">talked a lot about signals before</a> here on Fullstack Ruby so I won’t go into the whole rationale again. Suffice it to say, if you need to establish any sort of dependency graph such that when one piece of data over <em>here</em> changes, you need to be notified so you can update another piece of data over <em>there</em>, the signals paradigm is a compelling way to do it. At first glance it looks a lot like the “observables” pattern, but where observables require a manual opt-in process (you as the developer need to indicate which bit of data you’d like to observe), signals do this automatically. When you write an “effect” closure (in JavaScript called a function, in Ruby called a proc) and access any signals-based data within that closure, a dependency is created between the effect and that signal (tracked using linked lists under the hood). Any time in the future some of that data changes, because the effect is dependent on the data, it is executed again. This automatic re-run functionality is what makes signals feel like ✨ magic ✨.</p> <p>Some signals are “computed”—meaning you write a special closure to access one or more other signals, perform some calculation, and return a result. Computed signals update “lazily”—in other words, the calculation is only performed at the point the result is required. Under the hood, a computed signal is built out of an effect, which means when you write your own effects to access the values of computed signals, effects are dependent on other effects. Again, it can feel like ✨ magic ✨ until you understand how it works.</p> <p>Now it’s true that the signals paradigm has taken off like wildfire on the frontend as a serious solution to the state -&gt; UI update lifecycle. You need to know which specific parts of the interface need to be rerendered based on which specific state has changed.</p> <p><em>Hmm.</em></p> <p>Rerendering based on changes to data. <em>Now where have I heard that one before?</em></p> <p>Yeah, that’s it! Sounds an awful lot like the exact problem Bridgetown faces when you modify code or content in a file. We need to know how to rerender specific parts of the interface (aka which particular <code class="highlighter-rouge">.html</code> files) based on the dependency graph of how your modified code touches various pages.</p> <p>Here’s how Fast Refresh solves the problem by effectively utilizing the <a href="https://github.com/whitefusionhq/signalize">Signalize gem</a>. For a framework-level overview of the feature, <a href="https://www.bridgetownrb.com/future/road-to-bridgetown-2.0-fast-refresh/">check out this post on the Bridgetown blog</a>.</p> <h2 id="transformations-in-effects">Transformations in Effects</h2> <p>The first road along our journey is making sure the process of <em>transformation</em>—aka compiling Markdown down to HTML, rendering view components, placing page content inside of a layout, etc. is wrapped in an <em>effect</em>. This way, if during the course of transforming Page A, there’s a reference to the “title” signal of Page B, any future change to Page B’s “title” would trigger a rerun of Page A’s transformation.</p> <p>However, it’s a wee bit more complicated than that. We don’t want to perform the rerender immediately when the effect is triggered for a variety of reasons (performance, avoiding infinite loops, etc.). We instead want to <em>mark</em> the resource which needs to be transformed, and then later on we’ll go through all of the queued resources in a single pass in order to perform transformations.</p> <p>Here’s a snippet from the Bridgetown codebase of what that looks like:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># bridgetown-core/lib/bridgetown-core/resource/base.rb</span> <span class="k">def</span> <span class="nf">transform!</span> <span class="n">internal_error</span> <span class="o">=</span> <span class="kp">nil</span> <span class="vi">@transform_effect_disposal</span> <span class="o">=</span> <span class="no">Signalize</span><span class="p">.</span><span class="nf">effect</span> <span class="k">do</span> <span class="k">if</span> <span class="o">!</span><span class="vi">@fast_refresh_order</span> <span class="o">&amp;&amp;</span> <span class="vi">@previously_transformed</span> <span class="nb">self</span><span class="p">.</span><span class="nf">content</span> <span class="o">=</span> <span class="n">untransformed_content</span> <span class="vi">@transformer</span> <span class="o">=</span> <span class="kp">nil</span> <span class="n">mark_for_fast_refresh!</span> <span class="k">if</span> <span class="n">site</span><span class="p">.</span><span class="nf">config</span><span class="p">.</span><span class="nf">fast_refresh</span> <span class="o">&amp;&amp;</span> <span class="n">write?</span> <span class="k">next</span> <span class="k">end</span> <span class="n">transformer</span><span class="p">.</span><span class="nf">process!</span> <span class="k">unless</span> <span class="n">collection</span><span class="p">.</span><span class="nf">data?</span> <span class="n">slots</span><span class="p">.</span><span class="nf">clear</span> <span class="vi">@previously_transformed</span> <span class="o">=</span> <span class="kp">true</span> <span class="k">rescue</span> <span class="no">StandardError</span><span class="p">,</span> <span class="no">SyntaxError</span> <span class="o">=&gt;</span> <span class="n">e</span> <span class="n">internal_error</span> <span class="o">=</span> <span class="n">e</span> <span class="k">end</span> <span class="k">raise</span> <span class="n">internal_error</span> <span class="k">if</span> <span class="n">internal_error</span> <span class="nb">self</span> <span class="k">end</span> </code></pre></div></div> <p>There are a few things going here, so let’s walk through it:</p> <ul> <li>We set up our effect using <code class="highlighter-rouge">Signalize.effect</code> by wrapping logic within the block.</li> <li>Every time there’s a full build, transformations start anew, so the value of <code class="highlighter-rouge">@previously_transformed</code> will be falsy (aka <code class="highlighter-rouge">nil</code>). Thus we go ahead with the <code class="highlighter-rouge">process!</code> method of the transformer object.</li> <li>If in the future this effect has been triggered “out of band”, meaning there was a downstream change in a dependency, that first <code class="highlighter-rouge">if</code> statement conditional will run. We’ll reset the transformation pipeline, mark the resource for fast refresh, and exit.</li> <li>For the curious: we have to be cautious about handling errors during transformations (perhaps raised in executing code in userland when a template is processed) because Signalize’s internals require special cleanup and we don’t want to eject out of the block prematurely.</li> </ul> <p>So that’s one facet of the overall process. Here’s another one: we needed to refactor resource data (front matter + content) to use signals, otherwise our effects will be useless.</p> <p>Here’s a snippet showing what happens when new data is assigned to a resource:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># bridgetown-core/lib/bridgetown-core/resource/base.rb</span> <span class="k">def</span> <span class="nf">data</span><span class="o">=</span><span class="p">(</span><span class="n">new_data</span><span class="p">)</span> <span class="n">mark_for_fast_refresh!</span> <span class="k">if</span> <span class="n">site</span><span class="p">.</span><span class="nf">config</span><span class="p">.</span><span class="nf">fast_refresh</span> <span class="o">&amp;&amp;</span> <span class="n">write?</span> <span class="no">Signalize</span><span class="p">.</span><span class="nf">batch</span> <span class="k">do</span> <span class="vi">@content_signal</span><span class="p">.</span><span class="nf">value</span> <span class="o">+=</span> <span class="mi">1</span> <span class="vi">@data</span><span class="p">.</span><span class="nf">value</span> <span class="o">=</span> <span class="vi">@data</span><span class="p">.</span><span class="nf">value</span><span class="p">.</span><span class="nf">merge</span><span class="p">(</span><span class="n">new_data</span><span class="p">)</span> <span class="k">end</span> <span class="vi">@data</span><span class="p">.</span><span class="nf">peek</span> <span class="k">end</span> </code></pre></div></div> <p>First of all, we immediately mark the resource itself as ready for fast refresh. This is to handle the first-party use case where someone has made a change to a resource and we definitely want to rerender that resource…no need for a fancy dependency graph in that case!</p> <p>Next, we create a batch routine to set a couple of signals: updating the data hash itself, and incrementing the “content” signal. For legacy reasons, we don’t use a signal internally to store the body content of a resource, but we still track its usage via an incrementing integer.</p> <p>All right, so we now have two core pieces of functionality in place. We can track when a resource is directly updated, and we can also track when another resource is updated that the first one is dependent on in order to rerender both of them.</p> <p>(I’ll leave out all of the primary file watcher logic which matches file paths with resources or other data structures in the first place and handles all the queue processing because it’s quite complex. <a href="https://github.com/bridgetownrb/bridgetown/blob/main/bridgetown-core/lib/bridgetown-core/concerns/site/fast_refreshable.rb">You can look at it here</a>.)</p> <p>Instead, let’s turn our attention to yet another use case: you’ve just updated the template of a component (say, a site-wide page header). How could Bridgetown possibly know which resources (in this instance, probably all of them!) need to be rerendered? Well, the solution is to use signal tracking when rendering components!</p> <p>Here’s the method which runs when a component is rendered. If fast refresh is enabled, we create or reuse an incrementing integer signal cached using a stable id (the location of the component source file), and then “subscribe” the effect that’s in the process of executing to that signal.</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># bridgetown-core/lib/bridgetown-core/component.rb</span> <span class="k">def</span> <span class="nf">render_in</span><span class="p">(</span><span class="n">view_context</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span><span class="p">)</span> <span class="vi">@view_context</span> <span class="o">=</span> <span class="n">view_context</span> <span class="vi">@_content_block</span> <span class="o">=</span> <span class="n">block</span> <span class="k">if</span> <span class="n">render?</span> <span class="k">if</span> <span class="n">helpers</span><span class="p">.</span><span class="nf">site</span><span class="p">.</span><span class="nf">config</span><span class="p">.</span><span class="nf">fast_refresh</span> <span class="n">signal</span> <span class="o">=</span> <span class="n">helpers</span><span class="p">.</span><span class="nf">site</span><span class="p">.</span><span class="nf">tmp_cache</span><span class="p">[</span><span class="s2">"comp-signal:</span><span class="si">#{</span><span class="nb">self</span><span class="p">.</span><span class="nf">class</span><span class="p">.</span><span class="nf">source_location</span><span class="si">}</span><span class="s2">"</span><span class="p">]</span> <span class="o">||=</span> <span class="no">Signalize</span><span class="p">.</span><span class="nf">signal</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="c1"># subscribe so resources are attached to this component within effect</span> <span class="n">signal</span><span class="p">.</span><span class="nf">value</span> <span class="k">end</span> <span class="n">before_render</span> <span class="n">template</span> <span class="k">else</span> <span class="s2">""</span> <span class="k">end</span> <span class="c1"># and some other stuff…</span> <span class="k">end</span> </code></pre></div></div> <p>Later on, when it’s time to determine which type of file has just changed on disk, we loop through component paths, and if we find one, increment the corresponding cached signal.</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># bridgetown-core/lib/bridgetown-core/concerns/site/fast_refreshable.rb</span> <span class="k">def</span> <span class="nf">locate_components_for_fast_refresh</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="n">comp</span> <span class="o">=</span> <span class="no">Bridgetown</span><span class="o">::</span><span class="no">Component</span><span class="p">.</span><span class="nf">descendants</span><span class="p">.</span><span class="nf">find</span> <span class="k">do</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span> <span class="n">item</span><span class="p">.</span><span class="nf">component_template_path</span> <span class="o">==</span> <span class="n">path</span> <span class="o">||</span> <span class="n">item</span><span class="p">.</span><span class="nf">source_location</span> <span class="o">==</span> <span class="n">path</span> <span class="k">rescue</span> <span class="no">StandardError</span> <span class="k">end</span> <span class="k">return</span> <span class="k">unless</span> <span class="n">comp</span> <span class="n">tmp_cache</span><span class="p">[</span><span class="s2">"comp-signal:</span><span class="si">#{</span><span class="n">comp</span><span class="p">.</span><span class="nf">source_location</span><span class="si">}</span><span class="s2">"</span><span class="p">]</span><span class="o">&amp;</span><span class="p">.</span><span class="nf">value</span> <span class="o">+=</span> <span class="mi">1</span> <span class="c1"># and some other stuff…</span> <span class="k">end</span> </code></pre></div></div> <p>So now, any time a component changes, the resources which had <em>previously</em> rendered that component will get marked for fast refresh and thus rerendered. (We do a similar thing for template partials as well.)</p> <h2 id="create-your-own-signals">Create Your Own Signals</h2> <p>There’s so much more we could go over, but I’ll mention one other cool addition to the system. Bridgetown offers the concept of a “site-wide” data object, which you can think of as global state. Site data (accessed via <code class="highlighter-rouge">site.data</code> naturally) can come from specific files which get read in from the <code class="highlighter-rouge">src/_data</code> folder like <code class="highlighter-rouge">.csv</code>, <code class="highlighter-rouge">.yaml</code>, or <code class="highlighter-rouge">.json</code>, but it can also be provided by code which runs at the start of a site build via a Builder.</p> <p>Bridgetown 2.0’s fast refresh necessitated the need to make even site data reactive, so that’s exactly what we did using a special feature of the Signalize gem: <code class="highlighter-rouge">Signalize::Struct</code> (with some Bridgetown-specific enhancements layered in to make it feel more Hash-like).</p> <p>In a nutshell, you can now set global data with <code class="highlighter-rouge">site.signals.some_value = 123</code> and read that later with <code class="highlighter-rouge">site.signals.some_value</code>. In any template for a resource, a component, whatever, if you read in that value you’ll make that template dependent on the signal value. So in the future, when that signal changes for any reason, your template(s) will get rerendered to display the new value.</p> <p>Bridgetown uses this internally for “metadata” (aka site title, tagline, etc.) so templates can get refreshed if you update the metadata, and who knows what use cases might be unlocked by this feature in the future? For example, you could spin up a thread and poll an external API such as a CMS every few seconds, and once you detect a new changeset, update a signal and get your site fast refreshed with the API’s new content.</p> <h2 id="fast-refresh-edge-cases">Fast Refresh Edge Cases</h2> <p>As anyone who has worked on incremental regeneration for a static site generator can tell you, <em>the devil’s in the details</em>. There are so many edge cases which can make it seem like the site is “broken” — aka you update a piece of data over here, and then view some page over there and wonder why nothing got updated. 🧐</p> <p>Some solutions have come in the form of <strong>elaborate JavaScript frontend frameworks</strong> which require complex data pipelines and GraphQL and TypeScript and static analysis and Hot Module Reload and an ever-growing string of buzzwords…and even then, performance in other areas can suffer such as on first build or when accessing various resources for the first time.</p> <p>Bridgetown will no doubt ship its v2 with a few remaining edge cases, but I’m feeling confident we’ve dealt with most of the low-hanging fruit. I’ve been using alpha and beta versions of Bridgetown 2.0 in production on my own projects, and by now I’m so used to fast refresh making it so <strong>I’m virtually never waiting for my browser to display updated content or UI</strong>, I’ve forgotten the <em>bad old days</em> of when we didn’t have this feature!</p> <p>It was (and is) complicated to build, but I’m sure it would have been even harder and more byzantine if we’d needed to architect the feature from scratch. By leveraging the capabilities afforded by the <a href="https://github.com/whitefusionhq/signalize">Signalize gem</a> and making it possible for dependency graphs to self-assemble based on how developers have structured their application code and site content, we now have a solid foundation for this major performance boost and can refactor bit by bit as issues and fixes arise.</p> <p>Bridgetown 2.0 is currently in beta and slated for final release before the end of the year. If you’re looking to develop a new website or modest web application using Ruby, <a href="https://www.bridgetownrb.com">check it out!</a> <img src="/images/ruby.svg" alt="small red gem symbolizing the Ruby language" width="14" style="vertical-align: -0.05em;margin-left: 0.1em" /></p>Jared Whitehttps://jaredwhite.comEpisode 11: Designing Your API for Their API (Yo Dawg!)2024-10-15T10:18:32-07:002024-10-15T10:18:32-07:00repo://posts.collection/_posts/podcast/11.md<iframe width="100%" height="180" frameborder="no" scrolling="no" seamless="" src="https://share.transistor.fm/e/e63b429b"></iframe> <p>It’s tempting to want to take the simplistic approach of writing “to the framework” or to the external API directly in the places where you need to interface with those resources, but it’s sometimes a much better approach to create your own abstraction layer. Having this layer which sits between your high-level business logic or request/response handling, and the low-level APIs you need to call, means you’ll be able to define an API which is clean and makes sense for <em>your</em> application…and then you can get messy down in the guts of the layer or even swap out one external API for another one. I explore all this and more in another <em>rousing</em> episode of <strong>Fullstack Ruby</strong>.</p> <h2 id="links--show-notes">Links &amp; Show Notes:</h2> <ul> <li><a href="https://buttondown.com/fullstackruby">Sign up for the Fullstack Ruby newsletter</a></li> <li><a href="https://plus.intuitivefuture.com/">Get bonus issues and support this content as an Intuitive+ member!</a></li> <li><a href="https://www.whitefusion.studio/">Whitefusion: a Boutique Web Studio</a> (<em>hire me!</em>)</li> <li><a href="https://www.fullstackruby.dev/fullstack-development/2024/10/07/top-ten-favorite-ruby-web-application-gems/">Top 10 Most Excellent Gems to Use in Any Ruby Web Application</a></li> <li><a href="https://edge.bridgetownrb.com/">Bridgetown 2.0 Beta</a></li> </ul>jared