<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"
  xmlns:dc="http://purl.org/dc/elements/1.1/">
  <author>
    <name>Brooke Kuhlmann</name>
    <uri>https://alchemists.io/team/brooke_kuhlmann</uri>
  </author>
  <category term="articles"
    label="Articles"/>
  <generator uri="https://alchemists.io"
    version="0.3.0">Alchemists</generator>
  <icon>https://alchemists.io/favicon.ico</icon>
  <id>https://alchemists.io/articles</id>
  <link href="https://alchemists.io/articles"
    rel="alternate"
    type="text/html"
    title="Alchemists: Articles (web)"/>
  <link href="https://alchemists.io/feeds/articles.xml"
    rel="self"
    type="application/atom+xml"
    title="Alchemists: Articles (feed)"/>
  <logo>https://alchemists.io/images/logo/syndication.png</logo>
  <rights>2026</rights>
  <subtitle>Articles on the craft of software engineering.</subtitle>
  <title>Alchemists: Articles</title>
  <updated>2026-10-06T00:29:40+00:00</updated>
  <entry>
    <author>
      <name>Brooke Kuhlmann</name>
      <uri>https://alchemists.io/team/brooke_kuhlmann</uri>
    </author>
    <category term="engineering"
      label="Engineering"/>
    <content type="html">&lt;div class=&quot;imageblock focal_point&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;/images/articles/joy_of_the_craft/cover.png&quot; alt=&quot;Cover&quot; width=&quot;800&quot; height=&quot;195&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Joy of the Craft&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This collective was founded on a simple idea that you should enjoy the work you do including a strong sense of purpose in order to enrich the lives of your team and customers combined. Profit, while important to keep the lights on, is not the driving force. Building expertise and using software to make life more enjoyable for folks is much more rewarding. Plus, it feels good to wake up each morning knowing you are working in a code base that is well maintained, always up-to-date, a joy to use, and a benefit to society.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The &lt;a href=&quot;/oath&quot;&gt;Engineer&amp;#8217;s Oath&lt;/a&gt; helps us never lose sight of our purpose while our &lt;a href=&quot;/rigor&quot;&gt;Engineer&amp;#8217;s Rigor&lt;/a&gt; keeps us honest because you can&amp;#8217;t make an impact in life if your environment, codebase, and team is in constant disarray. This definitely takes work, which isn&amp;#8217;t always fun but, as the saying goes, nothing in life worth achieving is easy.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This also means having grit to persevere when things get tough and integrity to do the right thing when no one is looking. For comparison, let&amp;#8217;s look at the work of others who exhibit some or all of these qualities.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot; class=&quot;title&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#_crafters&quot;&gt;Crafters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_sharpening_the_saw&quot;&gt;Sharpening the Saw&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_expert_beginners&quot;&gt;Expert Beginners&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_crafters&quot;&gt;Crafters&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Software engineering is both an art and a science. When expertly&amp;#8201;&amp;#8212;&amp;#8201;and seamlessly&amp;#8201;&amp;#8212;&amp;#8201;blended together, that&amp;#8217;s where the magic exists.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;You&amp;#8217;ve probably seen or worked with people&amp;#8201;&amp;#8212;&amp;#8201;if lucky&amp;#8201;&amp;#8212;&amp;#8201;which are the embodiment of joy while also being extremely skilled at the craft. &lt;a href=&quot;https://en.wikipedia.org/wiki/Jon_Batiste&quot;&gt;Jon Batiste&lt;/a&gt;, is one example, where his joy exudes from the stage, interviews, and especially in his music. Here&amp;#8217;s a few others:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Leonardo_da_Vinci&quot;&gt;Leonardo da Vinci&lt;/a&gt;: Often cited as champion of the High Renaissance not to mention an accomplished artist and engineer known for being a perfectionist where some ideas didn&amp;#8217;t see the light of day and while other ideas took years to complete.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Antoni_Gaud%C3%AD&quot;&gt;Antoni Gaudí&lt;/a&gt;: Devoted much of his life to the Sagrada Família, sometimes living on-site in his final years. He designed structural solutions decades ahead of the engineering math that would later validate his ideas.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/David_Bowie&quot;&gt;David Bowie&lt;/a&gt;: One of the greatest rock artists, and chameleon, that would constantly reinvent himself every few years (or less) to push the boundaries of what was possible. His last album, Blackstar, was released only a few days before his death.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Neil_Peart&quot;&gt;Neal Pert&lt;/a&gt;: After being known as an excellent drummer in &lt;a href=&quot;https://www.rush.com&quot;&gt;Rush&lt;/a&gt;, he relearned how to play the drums by practicing with jazz drummer Freddie Gruber in order to hone his skills further.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.longmontindiankitchen.com&quot;&gt;Sheel Joshi&lt;/a&gt;: An award winning, Michelin recognized, chef who is a master of the craft of Indian cuisine. Doesn&amp;#8217;t take long, after experiencing his cooking and getting the chance to talk with him, to know that he cares about the craft and sharing that joy through food. To quote from his site: &quot;A gathering place shaped by story and technique. A kitchen defined by heart.&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Jiro_Ono&quot;&gt;Jiro Ono&lt;/a&gt;: Embodies shokunin (see below) ethics of duty and perfection while also known for a tough apprenticeship culture which even excluded his eldest son.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;What each of the above have in common is a mastery of the craft through constant rigor. They held onto human ingenuity and inherit skill to achieve greatness while sharing the fruits of their labor in order to bring joy to millions of others. May we all strive for the same.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_sharpening_the_saw&quot;&gt;Sharpening the Saw&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;You&amp;#8217;ll find, in many cultures, that rigor and grit are necessary to hone your craft in order to achieve expertise and a life worth living. The Japanese are especially well versed in this practice with many words representing various forms of this concept:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/articles/shuhari&quot;&gt;Shuhari&lt;/a&gt; (守破離): This has been discussed before but shuhari is the life long pursuit of improving your craft by starting as an apprentice, evolving as a journeyer, and finally gaining mastery as a crafter. While mortally impossible to gain complete mastery, journey is where the joy the resides, not the destination.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Shokunin&lt;/strong&gt; (職人): Translates to &lt;em&gt;artisan&lt;/em&gt; or &lt;em&gt;craftsman&lt;/em&gt; which means one who endlessly pursues mastery of the craft with a strong sense of purpose and joy. This includes moral, ethical, and social consciousness to weld the craft for the benefit of others. Sound familiar? Yep, there&amp;#8217;s the &lt;a href=&quot;/oath&quot;&gt;Engineer&amp;#8217;s Oath&lt;/a&gt; again.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Kaizen&lt;/strong&gt; (改善): Translates as &lt;em&gt;improvement&lt;/em&gt; and is most notably attributed with The Toyota Way of working through constant improvement and refinement of processes. This includes a strong emphasis on quality including halting production if quality ever drops below a specific threshold. In terms of software, a good example of this is &lt;a href=&quot;/articles/ruby_code_quality/&quot;&gt;Ruby Code Quality&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Takumi&lt;/strong&gt; (匠): Means artisan, master crafter, or skilled expert. This harkens back to shuhari which is someone who has achieved mastery of the craft through decades of unyielding practice.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tatsujin&lt;/strong&gt; (達人): An expert who has arrived at mastery and who has attained the deepest level of the craft through intuitive, tacit, and effortless skill.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dojo&lt;/strong&gt; (道場): Literally means &lt;em&gt;place of the way&lt;/em&gt; which is where folks gather to form unions, guilds, and places of continuous learning.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Habits are the key to achieving the above but can take time to become instinctual. It would be amazing to be like Neo, in &lt;a href=&quot;https://en.wikipedia.org/wiki/The_Matrix&quot;&gt;The Matrix&lt;/a&gt;, and instantly learn new skills with all the knowledge, experience, and wisdom necessary in only a few seconds but, unfortunately, that&amp;#8217;s now how we gain experience. First we learn, then we apply, and slowly we gain experience by testing the limits of our knowledge in new and interesting ways to unlock new ideas. For example, the path to software engineering mastery might look like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Learning the basics of the language by first writing simple programs and then advancing to more complex architectures.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Adding linters, code quality checks, keyboard shortcuts, macros, and anything that augments your workflow while enforcing consistency across all codebases.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Learning patterns and antipatterns including when to apply them and when alter them in elegant ways.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Extracting hard problems, solved well, as gems for reuse in other projects.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Learning from and constantly evolving design choices which might mean refactoring large chunks of an application or multiple gems in order to maintain consistency. This can be painful but is critical to building experience and new learnings on what to do and what not to do.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Writing and sharing your learnings with others. If you can&amp;#8217;t explain it, then you haven&amp;#8217;t learned it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Creating video tutorials to augment your writings.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Speaking at conferences and connecting with others to spread new ideas and ways of thinking.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Writing books and becoming a knowledge expert in general.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Advancing and constantly evolving to the point where most knowledge is tacit and intuitive. There will always be new challenges but they become faster to debug and solve. This is pure gold because it&amp;#8217;s hard won and no one can take that away from you.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This is how you keep the saw sharp.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_expert_beginners&quot;&gt;Expert Beginners&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The antithesis of those who don&amp;#8217;t practice or understand the joy of the craft are known as an &lt;a href=&quot;/articles/expert_beginner&quot;&gt;Expert Beginner&lt;/a&gt;. These are people who hit a fork in the road and make the tragic mistake of heading down the path of supposed, or self-declared, mastery when they couldn&amp;#8217;t be father from the truth.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In short, and much has been written on this already, Expert Beginners tend to stagnate with minimal to no growth. They also have minimal to no ambition. This includes farming out learning, understanding, and personal growth to Large Language Models (LLMs). In some cases, they willingly give up what little expertise they have in favor of handing everything over to an LLM (also known as &lt;em&gt;brain rot&lt;/em&gt;). This is akin&amp;#8201;&amp;#8212;&amp;#8201;and vastly simplified&amp;#8201;&amp;#8212;&amp;#8201;to playing slot machines and hoping, this time, randomness favors your weakness. Worse, LLMs churn out the lowest common denominator of collective knowledge. This is because LLMs can&amp;#8217;t think, can&amp;#8217;t remember, and can&amp;#8217;t experience growth like humans can. Lastly, Expert Beginners definitely don&amp;#8217;t care about software architecture, code quality, maintenance, or team happiness.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Take the red pill, not the blue pill, to avoid becoming an Expert Beginner. Build upon what makes you unique and then share that joy with others.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;There will always be liars, grifters, &lt;a href=&quot;/articles/ghosting&quot;&gt;ghosters&lt;/a&gt;, fraudsters, and unsavory folk of weak constitution but this is a reminder that there is a better path in life. Harder, definitely, but much more rewarding and worth it in end.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;If this appeals to you and you truly care about the craft and the joy of getting up each day to tackle new challenges, learn new skills, write elegant code, and share what you know with others then join our &lt;a href=&quot;/community&quot;&gt;community&lt;/a&gt; or add your name to the list of &lt;a href=&quot;https://handcraftedcode.org&quot;&gt;Hand Crafted Coders&lt;/a&gt;. We&amp;#8217;re constantly working to share the joy by building a better future. We only have one shot at this life, so let&amp;#8217;s enjoy it!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
    <id>https://alchemists.io/articles/joy_of_the_craft</id>
    <link href="https://alchemists.io/articles/joy_of_the_craft"/>
    <published>2026-10-01T13:59:52+00:00</published>
    <rights>2026</rights>
    <title>Joy of the Craft</title>
    <updated>2026-10-06T00:29:40+00:00</updated>
    <dc:date>2026-10-06T00:29:40+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>Brooke Kuhlmann</name>
      <uri>https://alchemists.io/team/brooke_kuhlmann</uri>
    </author>
    <category term="ruby"
      label="Ruby"/>
    <category term="specifications"
      label="Specifications"/>
    <content type="html">&lt;div class=&quot;imageblock focal_point&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;a class=&quot;image&quot; href=&quot;https://www.rfc-editor.org&quot;&gt;&lt;img src=&quot;/images/articles/request_for_comments/cover.png&quot; alt=&quot;Cover&quot; width=&quot;842&quot; height=&quot;342&quot;&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Request For Comments (RFCs)&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Request for Comments (RFCs) are the official guiding documents that shape the internet via technical standards, protocols, and procedures. This encompass the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;HyperText Transfer Protocol (HTTP)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Transmission Control Protocol (TCP)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Internet Protocol (IP)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Domain Name System (DNS)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Transport Layer Security (TLS)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;RFCs are also the primary output of the Internet Engineering Task Force (&lt;a href=&quot;https://www.ietf.org&quot;&gt;IETF&lt;/a&gt;) which was founded in 1986.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The following will help you stay on top of the ever changing RFC landscape.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot; class=&quot;title&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#_highlights&quot;&gt;Highlights&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_gems&quot;&gt;Gems&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_tools&quot;&gt;Tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_syndication&quot;&gt;Syndication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_highlights&quot;&gt;Highlights&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;There are &lt;em&gt;a lot&lt;/em&gt; of RFCs to read through. At the time of this writing, there are 10,042 total. We don&amp;#8217;t have time to go through them all. That said, here&amp;#8217;s a few of my favorites (if not already aware, yourself):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.rfc-editor.org/info/rfc5545&quot;&gt;RFC 5545&lt;/a&gt; (Internet Calendaring and Scheduling Core Object Specification (iCalendar): While not the greatest specification, due to the complexity of the format used, this provides the specification for sharing of calender information.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.rfc-editor.org/info/rfc6648&quot;&gt;RFC 6648&lt;/a&gt; (Deprecating the &quot;X-&quot; Prefix and Similar Constructs in Application Protocols): Specifies avoiding the use of &lt;code&gt;x-&lt;/code&gt; prefixes for custom HTTP headers. If you are using custom header like this, &lt;em&gt;please stop&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.rfc-editor.org/info/rfc8288&quot;&gt;RFC 8288&lt;/a&gt; (Web Linking): Specifies how to provide links, especially, navigation which allows you to provide rich information in HTTP responses for any content, not only APIs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.rfc-editor.org/info/rfc8615/&quot;&gt;RFC 8615&lt;/a&gt; (Well-Known Uniform Resource Identifiers (URIs)): Specifies the files and formats in the &lt;code&gt;/.well-known/&lt;/code&gt; directory at the root of your site. These are authoritative and machine readable for discovery and information about your site. Sadly, some files don&amp;#8217;t adhere to this specification and live at the root of your site. Examples: &lt;code&gt;robots.txt&lt;/code&gt;, &lt;code&gt;humans.txt&lt;/code&gt;, and &lt;code&gt;sitemap.xml&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.rfc-editor.org/info/rfc9110&quot;&gt;RFC 9110&lt;/a&gt; (HTTP Semantics): Specifies all semantics for making HTTP requests/response especially all verbs and how to use them (see Section 9.3).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.rfc-editor.org/info/rfc9113&quot;&gt;RFC 9113&lt;/a&gt; (HTTP/2): Lots to read but important to note that header keys must be lower case (see Section 8.2).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.rfc-editor.org/info/rfc9114&quot;&gt;RFC 9114&lt;/a&gt; (HTTP/3): As mentioned with HTTP/2, above, header keys must be lower case (see Section 4.2).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.rfc-editor.org/info/rfc9264/&quot;&gt;RFC 9264&lt;/a&gt; (Linkset: Media Types and a Link Relation Type for Link Sets): Compliments RFC 8288 by specifying a JSON set of links which is especially handy when the number of links might be large by pointing to a specific, cacheable, document.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9309.html&quot;&gt;RFC 9309&lt;/a&gt; (Robots Exclusion Protocol): Specifics how to use &lt;code&gt;robots.txt&lt;/code&gt; that must be at the root of your site (sadly, does not adhere to RFC 8615).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.rfc-editor.org/info/rfc9457&quot;&gt;RFC 9457&lt;/a&gt; (Problem Details for HTTP APIs): Specifies how to provide structured responses for API errors. If you&amp;#8217;re not using this already, definitely start.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9745.html&quot;&gt;RFC 9745&lt;/a&gt; (The Deprecation HTTP Response Header Field): Specifies how to indicate to clients that the endpoint is deprecated including a future date/time so you know how long you have to make changes on your end. Plays nice with RFC 8288 (mentioned above).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.rfc-editor.org/info/rfc10008&quot;&gt;RFC 10008&lt;/a&gt; (The HTTP QUERY Method): Adds the new HTTP QUERY verb which is a hybrid of GET and POST requests which allows you to make idempotent GET requests but with the power of POST to supply a body of search criteria that would otherwise be too much for a GET request.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_gems&quot;&gt;Gems&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Some RFCs can be quite involved in terms of what is needed for compliance. This is where a &lt;a href=&quot;https://www.ruby-lang.org&quot;&gt;Ruby&lt;/a&gt; gem is a powerful solution in being able to provide an implementation for each specification. This allows you reuse a single implementation across multiple applications. A few of the following &lt;a href=&quot;/projects&quot;&gt;projects&lt;/a&gt; provide implementations to a couple of the RFCs mentioned above:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/projects/rfc-api-problem&quot;&gt;RFC API Problem&lt;/a&gt;: A RFC 9457 Problem Details for HTTP APIs implementation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/projects/rfc-web-link&quot;&gt;RFC Web Link&lt;/a&gt;: A RFC 8288 Web Linking implementation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Definitely recommend adding to your stack to enhance your HTTP responses/requests further.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_tools&quot;&gt;Tools&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;To augment your workflow, you&amp;#8217;ll want tap into the following tools for quick information sourcing including staying on top of the latest changes within the industry:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.rfc-editor.org&quot;&gt;RFC Editor&lt;/a&gt;: The primary source of information where you can search and read up on all RFCs as well as subscribe, via syndicated feed, for all of the latest news and information.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://rfcexplorer.net&quot;&gt;RFC Explorer&lt;/a&gt;: Provides a graphical interface to all RFCs as nodes linked to each other. This allows you to click on each RFC (node) to dive further, zoom in and out, etc. The main interface groups all material by areas of work. Unfortunately, there isn&amp;#8217;t a way to quickly search by RFC number.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://datatracker.ietf.org&quot;&gt;IETF Datatracker&lt;/a&gt;: The primary database for authors and maintainers of RFCs. The best part about this tool is a visual timeline at the top of each RFC which provides a clear and quick understanding of the RFC over time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://author-tools.ietf.org/iddiff&quot;&gt;IETF Diff&lt;/a&gt;: Handy for comparing and contrasting the changes between document revisions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/projects/pennyworth&quot;&gt;Pennyworth&lt;/a&gt;: For &lt;a href=&quot;https://www.alfredapp.com&quot;&gt;Alfred&lt;/a&gt; fans, this gem allows you to quickly access the &lt;a href=&quot;https://www.rfc-editor.org&quot;&gt;RFC Editor&lt;/a&gt;, search, and fuzzy type to lookup documentation on any RFC.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_syndication&quot;&gt;Syndication&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;/articles/syndication&quot;&gt;Syndication&lt;/a&gt; is a powerful way to stay on top of the changes being introduced. You can subscribe via RSS or Atom by adding the &lt;a href=&quot;https://www.rfc-editor.org&quot;&gt;RFC Editor&lt;/a&gt; URL to your feed reader and letting it do the work of informing you when new changes are posted.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As software engineer, especially in web development, staying on top of the latest RFCs news and information is critical. Even better, automatically getting the latest updates via syndication allows you to learn about changes within the industry with minimal effort.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Enjoy and may your stack be built upon the wisdom of others.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
    <id>https://alchemists.io/articles/request_for_comments</id>
    <link href="https://alchemists.io/articles/request_for_comments"/>
    <published>2026-09-01T12:06:06+00:00</published>
    <rights>2026</rights>
    <title>Request for Comments (RFCs)</title>
    <updated>2026-09-30T17:12:43+00:00</updated>
    <dc:date>2026-09-30T17:12:43+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>Brooke Kuhlmann</name>
      <uri>https://alchemists.io/team/brooke_kuhlmann</uri>
    </author>
    <category term="conference"
      label="Conference"/>
    <category term="htmx"
      label="htmx"/>
    <content type="html">&lt;div class=&quot;imageblock focal_point&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;a class=&quot;image&quot; href=&quot;https://bigskydevcon.com&quot;&gt;&lt;img src=&quot;/images/articles/big_sky_dev_con_2026/cover.png&quot; alt=&quot;Cover&quot; width=&quot;762&quot; height=&quot;362&quot;&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Big Sky Dev Con 2026&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;After attending &lt;a href=&quot;/articles/big_sky_dev_con_2024&quot;&gt;Big Sky Dev Con 2024&lt;/a&gt;, a couple years ago, and meeting some of the &lt;a href=&quot;https://htmx.org&quot;&gt;htmx&lt;/a&gt; team, I became interested in the conference and was eager to return as a speaker. In 2025, I submitted my &lt;a href=&quot;/talks/milestones&quot;&gt;Milestones&lt;/a&gt; talk but, sadly, wasn&amp;#8217;t accepted. This is a shame because &lt;a href=&quot;/projects/milestoner&quot;&gt;Milestoner&lt;/a&gt; is used to fully generate all versions and corresponding release notes for all &lt;a href=&quot;/projects&quot;&gt;projects&lt;/a&gt;. The UI uses htmx which is perfect for statically generated content. Can&amp;#8217;t live without it! &amp;#8230;&amp;#8203;but I digress.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This year, thankfully, my &lt;a href=&quot;/talks/htmx_view_transitions&quot;&gt;htmx View Transitions&lt;/a&gt; talk was accepted and glad it was because I had a great time presenting, talking to folks, and enjoying the conference. The conference organizers have definitely stepped up their game in terms of quality of presentations and venues.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The following are a few notes and learnings captured from the conference that might be of interest.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot; class=&quot;title&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#_speakers_dinner&quot;&gt;Speakers Dinner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_talks&quot;&gt;Talks&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#_day_1&quot;&gt;Day 1&lt;/a&gt;
&lt;ul class=&quot;sectlevel3&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#_executor_orchestrator_leader_by_jaron_jones&quot;&gt;Executor, Orchestrator, Leader! by Jaron Jones&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_falling_in_love_with_sql_by_adam_rackis&quot;&gt;Falling in Love with SQL by Adam Rackis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_htmx_view_transitions_by_brooke_kuhlmann&quot;&gt;htmx View Transitions by Brooke Kuhlmann&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_javascript_in_this_economy_a_love_letter_to_tailwind_and_modern_css_by_anthony_alaribe&quot;&gt;JavaScript? In This Economy? (A Love Letter to Tailwind and modern CSS) by Anthony Alaribe&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_html_is_not_just_for_the_web_by_lazarus_morrison&quot;&gt;HTML is not just for the web by Lazarus Morrison&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_day_2&quot;&gt;Day 2&lt;/a&gt;
&lt;ul class=&quot;sectlevel3&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#_getting_off_the_modernization_treadmill_by_alexander_petros&quot;&gt;Getting off the Modernization Treadmill by Alexander Petros&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_agentic_engineering_for_jvm_developers_by_thomas_schilling&quot;&gt;Agentic Engineering for JVM Developers by Thomas Schilling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_your_business_doesnt_fit_on_a_phone_neither_should_your_flutter_app_by_marcus_twichel&quot;&gt;Your Business Doesn&amp;#8217;t Fit on a Phone — Neither Should Your Flutter App by Marcus Twichel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_i_have_no_idea_what_im_doing_by_ronan_berder&quot;&gt;I have no idea what I&amp;#8217;m doing by Ronan Berder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_lessons_from_our_hardware_on_the_iss_running_a_linux_stack_beyond_the_clouds_by_mike_frandsen&quot;&gt;Lessons from our Hardware on the ISS: Running a Linux Stack Beyond the Cloud(s) by Mike Frandsen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_cognitive_vandalism_what_i_learned_by_mutilating_a_gpt_by_barrett_breshears&quot;&gt;Cognitive Vandalism: What I Learned by Mutilating a GPT by Barrett Breshears&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_prompt_engineering_isnt_enough_building_ai_agents_you_can_steer_by_sandhya_subramani&quot;&gt;Prompt Engineering Isn’t Enough: Building AI Agents You Can Steer by Sandhya Subramani&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_orms_are_killing_your_performance_by_jerason_banes&quot;&gt;ORMs are Killing Your Performance by Jerason Banes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_intro_to_compression_techniques_by_lance_fisher&quot;&gt;Intro to Compression Techniques by Lance Fisher&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_real_time_hypermedia_the_htmx_way_by_christian_tanul&quot;&gt;Real-Time Hypermedia (the htmx way) by Christian Tanul&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_htmx_4_whats_new_by_carson_gross&quot;&gt;htmx 4: What&amp;#8217;s New by Carson Gross&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_speakers_dinner&quot;&gt;Speakers Dinner&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Originally scheduled for Wednesday, the night before the conference kicked off, the dinner ended up taking place after the conference ended on the first day.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock focal_point&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;/images/articles/big_sky_dev_con_2026/speakers_dinner.png&quot; alt=&quot;Game&quot; width=&quot;800&quot; height=&quot;534&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Most impressive was the work Duncan put into this and the team behind this. The crew drives out, each year, from Utah with frozen BBQ, rents an AirBnB, and then spends a couple days cooking and prepping the food. Everything was delicious with a fantastic spread.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_talks&quot;&gt;Talks&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This year the conference consisted of a day and a half of talks with things kicking off at ~3pm on Friday at &lt;a href=&quot;https://www.tinworksart.org/rialto&quot;&gt;The Rialto&lt;/a&gt; and running all day on Saturday at &lt;a href=&quot;https://www.montana.edu&quot;&gt;Montana State University&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;_day_1&quot;&gt;Day 1&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As mentioned above, the first day was at the &lt;a href=&quot;https://www.tinworksart.org/rialto&quot;&gt;The Rialto&lt;/a&gt;. I arrived at 1pm to double check my display and portable WiFi router worked properly. This proved wise since the macOS display bug cropped up again which yielded a black screen. Thankfully the video crew had an extra video converter which resolved the problem.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;James Collins, Lazarus Morrison, and Carson Gross showed up shortly after. While chatting with Carson, I learned of &lt;a href=&quot;https://fil-c.org&quot;&gt;Fil-C&lt;/a&gt; which might be of interest to folks that might want modern&amp;#8201;&amp;#8212;&amp;#8201;and compatible&amp;#8201;&amp;#8212;&amp;#8201;C/C++ tooling that is memory safe.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_executor_orchestrator_leader_by_jaron_jones&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=BY2bpn4xvZo&amp;amp;list=PLLYO-P10l3y0&amp;amp;index=2&quot;&gt;Executor, Orchestrator, Leader!&lt;/a&gt; by Jaron Jones&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Jones kicked off the conference by discussing how ownership and quality of your work is so important. You can&amp;#8217;t give up on the fundamentals and let the brain rot consume you in this Age of Slopware.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_falling_in_love_with_sql_by_adam_rackis&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=NQ0WmX-Dm24&amp;amp;list=PLLYO-P10l3y0&amp;amp;index=3&quot;&gt;Falling in Love with SQL&lt;/a&gt; by Adam Rackis&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Rackis spent the core of the talk looking at execution plans. These are good skills to have so make sure to watch the video. This talk also pairs well with Jerason Banes talk on using pure SQL in general.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_htmx_view_transitions_by_brooke_kuhlmann&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=0JrHsHUErhk&amp;amp;list=PLLYO-P10l3y0&amp;amp;index=4&quot;&gt;htmx View Transitions&lt;/a&gt; by Brooke Kuhlmann&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This was my talk which focused entirely on CSS View Transitions using htmx and Ruby. The slides and ePaper devices (passed out to the audience) were completely rendered live via &lt;a href=&quot;/projects/terminus&quot;&gt;Terminus&lt;/a&gt;. You can find the slides here: &lt;a href=&quot;/talks/htmx_view_transitions&quot;&gt;htmx View Transitions&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;At the end of the talk, I suggested an improvement to the htmx library where you could provide a callback into the View Transitions API for more granular control and Carson Gross mentioned this Michael West who folded the &lt;a href=&quot;https://github.com/bigskysoftware/htmx/pull/3918&quot;&gt;change&lt;/a&gt; into htmx 4.0.0!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_javascript_in_this_economy_a_love_letter_to_tailwind_and_modern_css_by_anthony_alaribe&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=bTURcTjmKBQ&amp;amp;list=PLLYO-P10l3y0&amp;amp;index=5&quot;&gt;JavaScript? In This Economy?&lt;/a&gt; (A Love Letter to Tailwind and modern CSS) by Anthony Alaribe&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This is the second time seeing Anthony speak. This time he had interesting insights into how you can use Tailwind to programmatically control behavior by using the &lt;code&gt;:has&lt;/code&gt; and &lt;code&gt;:is&lt;/code&gt; pseudo-classes along with the &lt;code&gt;if()&lt;/code&gt; function.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;I would never recommend Tailwind since the output always looks like someone barfed all over your source code. That said, using CSS to conditionally style a site based on which classes are present is worth knowing about. Alternative ways to tackle this would be via &lt;code&gt;data&lt;/code&gt; attributes, hidden check boxes, and so forth. For example, this site uses a hidden checkbox to toggle a collapsed main menu when viewing on smaller devices. All using CSS, no JavaScript!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_html_is_not_just_for_the_web_by_lazarus_morrison&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=XQl9Dq_14_g&amp;amp;list=PLLYO-P10l3y0&amp;amp;index=6&quot;&gt;HTML is not just for the web&lt;/a&gt; by Lazarus Morrison&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Morrison closed out the day by discussing his &lt;a href=&quot;https://hypeapps.dev&quot;&gt;HypeApps&lt;/a&gt; project which provides enhanced HTML elements for building applications that can run on multiple platforms. Without having a chance to play with this more, this seems like a highly tailored, custom collection of web components without all of the JavaScript setup. The framework is built upon &lt;a href=&quot;https://flutter.dev&quot;&gt;Flutter&lt;/a&gt; and pairs well with Marcus Twichel&amp;#8217;s talk who also uses Flutter.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;_day_2&quot;&gt;Day 2&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The second day was held in the Strand Union Building on the &lt;a href=&quot;https://www.montana.edu&quot;&gt;Montana State University&lt;/a&gt; campus. I found this to be more enjoyable than &lt;a href=&quot;https://www.tinworksart.org/rialto&quot;&gt;The Rialto&lt;/a&gt; because you had more space to setup your laptop, take notes, enjoy better lighting. Enjoyed being on the college campus as well.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_getting_off_the_modernization_treadmill_by_alexander_petros&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=vRJVw8Di-4s&amp;amp;list=PLLYO-P10l3y0&amp;amp;index=7&quot;&gt;Getting off the Modernization Treadmill&lt;/a&gt; by Alexander Petros&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Alexander kicked off the day by first mentioning the book &lt;a href=&quot;https://www.recodingamerica.us&quot;&gt;Recoding America&lt;/a&gt; by Jennifer Pahlka which tied in well with the staggering numbers of government waste related to software development. Having tackled several government contracts in the past, I know this only too well. This is also a talk about software bloat and love that he emphasized the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Avoid JavaScript. Stick to pure HTML and CSS. Let the browsers upgrade for you in terms of performance and security fixes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Avoid React entirely and lean in on native HTML and CSS support.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_agentic_engineering_for_jvm_developers_by_thomas_schilling&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=M4xK7b9nEhE&amp;amp;list=PLLYO-P10l3y0&amp;amp;index=8&quot;&gt;Agentic Engineering for JVM Developers&lt;/a&gt; by Thomas Schilling&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Perhaps one of the only speakers who traveled the farthest distance by coming from Stuttgart, Germany to speak. This talk was entirely focused on AI automation via these four steps:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Tell, Watch, Steer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What To Do, How To Do It&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Behavior - User Interaction&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sliced Loops&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;He also mentioned checking out &lt;a href=&quot;https://www.aihero.dev/grill-with-docs&quot;&gt;Grill with Docs&lt;/a&gt; for further tweaking of your agents. Check out his &lt;a href=&quot;https://tschuehly.de&quot;&gt;site&lt;/a&gt; and &lt;a href=&quot;https://jvmskills.com/big-sky-2026&quot;&gt;slides&lt;/a&gt; for further details.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_your_business_doesnt_fit_on_a_phone_neither_should_your_flutter_app_by_marcus_twichel&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=LT4touZOs2E&amp;amp;list=PLLYO-P10l3y0&amp;amp;index=9&quot;&gt;Your Business Doesn&amp;#8217;t Fit on a Phone — Neither Should Your Flutter App&lt;/a&gt; by Marcus Twichel&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This talk was all about using &lt;a href=&quot;https://flutter.dev&quot;&gt;Flutter&lt;/a&gt; to run on iOS, macOS, Android, web, and other platforms with real-time communication and synchronization. This included a live demo which focused on a fake coffee store scenario where an audience member would order a beverage and we&amp;#8217;d watch the transaction process in real-time across multiple platforms.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This was a good demo and definitely can see why folks would be attracted to using Flutter. The problem is Flutter is Google&amp;#8217;s solution for this much like React is for Facebook and we all know how awful that has become especially for those that cargo cult these platforms&amp;#8201;&amp;#8212;&amp;#8201;without thinking through the ramifications&amp;#8201;&amp;#8212;&amp;#8201;by using a big hammer to solve the wrong problem.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This is why checking out Alexander Petros&#39; talk is well worth your time because focusing on modern HTML and CSS can get you a long way including leaning into &lt;a href=&quot;https://whatpwacando.today&quot;&gt;Progressive Web Apps&lt;/a&gt;. Minimizing your stack, reducing your dependencies, and avoiding complicated builds in order to release new versions of your software reduces your maintenance burdens. Not saying this is always the right solution but should definitely be the first thing you consider before going down the Flutter/React path.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_i_have_no_idea_what_im_doing_by_ronan_berder&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=c31UUvPk13Y&amp;amp;list=PLLYO-P10l3y0&amp;amp;index=10&quot;&gt;I have no idea what I&amp;#8217;m doing&lt;/a&gt; by Ronan Berder&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;During the speakers dinner I ended up driving Ronan home despite both of us staying at opposite ends of the city so was a chance to chat. His talk ended up being a self deprecating talk on entrepreneurship especially the failures and mistakes. This makes the talk unique since people don&amp;#8217;t always share the missteps of success.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;A couple insights worth noting from his talk:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://news.ycombinator.com&quot;&gt;Hacker News&lt;/a&gt; has a section where you can post about open source. Each submission must be prefixed with &quot;Show HN: &quot; followed by the project name and a brief description. A landing page, repository, or demo of your work is what is preferred. Keep it short but technical, no marketing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;&quot;&gt;Siddharta&lt;/a&gt; by Hermann Hesse might be worth a reading since it&amp;#8217;s about a spiritual journey of self-discovery.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Check out Ronan&amp;#8217;s &lt;a href=&quot;https://ronanberder.com&quot;&gt;writings&lt;/a&gt; and &lt;a href=&quot;https://github.com/hunvreus&quot;&gt;open source&lt;/a&gt; to learn more.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_lessons_from_our_hardware_on_the_iss_running_a_linux_stack_beyond_the_clouds_by_mike_frandsen&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=JdwQK9YI93w&amp;amp;list=PLLYO-P10l3y0&amp;amp;index=11&quot;&gt;Lessons from our Hardware on the ISS: Running a Linux Stack Beyond the Cloud(s)&lt;/a&gt; by Mike Frandsen&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Insightful talk on running code in Space with limited hardware, resources, and connectivity. This talk focuses on the Cambrian Works Gig Router and lessons learned such as:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Review your defaults.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ensure your systems are idempotent.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use structured logging.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ended up talking to Mike a bit afterwards because I was curious about the structured logging hurdles they faced especially since I maintain the &lt;a href=&quot;/projects/cogger&quot;&gt;Cogger&lt;/a&gt; gem which supports structured logging.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_cognitive_vandalism_what_i_learned_by_mutilating_a_gpt_by_barrett_breshears&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=EPmy1p06KHM&amp;amp;list=PLLYO-P10l3y0&amp;amp;index=12&quot;&gt;Cognitive Vandalism: What I Learned by Mutilating a GPT&lt;/a&gt; by Barrett Breshears&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This was a humorous talk, plus clever engineering, which focused on messing with pre-trained models by removing layers, duplicating them, injecting gibberish, and more. The results were rendered live producing ridiculous results in order to illustrate how the models function.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Worth checking out when the video for more.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_prompt_engineering_isnt_enough_building_ai_agents_you_can_steer_by_sandhya_subramani&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=MO9GTwNgZzw&amp;amp;list=PLLYO-P10l3y0&amp;amp;index=13&quot;&gt;Prompt Engineering Isn’t Enough: Building AI Agents You Can Steer&lt;/a&gt; by Sandhya Subramani&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This was the only talk that didn&amp;#8217;t land for me. The entire presentation was a lot like a marketing demo that had been given multiple times to companies and/or engineering teams. This would add up since Sandhya is a developer advocate which means traveling all over and talking about the same thing. Anyway, the talk was focused on using &lt;a href=&quot;https://github.com/strands-agents&quot;&gt;Strands Agents&lt;/a&gt; as an open source solution for building production agents.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_orms_are_killing_your_performance_by_jerason_banes&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=2DaRGRe4Ow4&amp;amp;list=PLLYO-P10l3y0&amp;amp;index=14&quot;&gt;ORMs are Killing Your Performance&lt;/a&gt; by Jerason Banes&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This was a good talk about ditching the use of Object-Relational Mappers (ORMs), using functional transformer pipelines to manipulate key/value pars to produce the results you need, and streaming content from the database.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This also planted a seed for further exploration because I&amp;#8217;d like to rip out &lt;a href=&quot;https://hanakai.org/hanami&quot;&gt;Hanami&lt;/a&gt;&#39;s use of &lt;a href=&quot;https://hanakai.org/rom&quot;&gt;Ruby Object Mapper&lt;/a&gt; and explore pure SQL as well since there&amp;#8217;s been very little progress with the &lt;a href=&quot;https://hanakai.org/rom&quot;&gt;Ruby Object Mapper&lt;/a&gt; stack for years.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_intro_to_compression_techniques_by_lance_fisher&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Kc8dXSgqqGE&amp;amp;list=PLLYO-P10l3y0&amp;amp;index=15&quot;&gt;Intro to Compression Techniques&lt;/a&gt; by Lance Fisher&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Good talk&amp;#8201;&amp;#8212;&amp;#8201;along with helpful visuals&amp;#8201;&amp;#8212;&amp;#8201;on different compression techniques such as gzip, brotli and others. Worth checking out the video especially if you have an interest in the different compression algorithms.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_real_time_hypermedia_the_htmx_way_by_christian_tanul&quot;&gt;Real-Time Hypermedia (the htmx way) by Christian Tanul&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Christian is a member of the htmx Core team and traveled from Romania to speak so was a bit jet lagged. The talk was also a bit discombobulated as a result. That said, I loved learning about the &lt;code&gt;multipart/mixed&lt;/code&gt; HTTP &lt;code&gt;Content-Type&lt;/code&gt; header which allows you to stream mixed data via a single connection! This has been part of the HTML spec since 1992 but I&amp;#8217;ve never seen anyone talk about this before.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Use of &lt;code&gt;multipart/mixed&lt;/code&gt; is also more versatile than &lt;a href=&quot;/articles/server_sent_events&quot;&gt;Server Sent Events&lt;/a&gt;, which I recently wrote about, so plan to explore and write about this more in the future.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;By the way, Christian&amp;#8217;s work is folded into the &lt;a href=&quot;https://four.htmx.org/extensions/hx-multipart&quot;&gt;hx-multipart&lt;/a&gt; extension which you can use in htmx 4.0.0.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_htmx_4_whats_new_by_carson_gross&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=SjUoc8R1dzQ&amp;amp;list=PLLYO-P10l3y0&amp;amp;index=16&quot;&gt;htmx 4: What&amp;#8217;s New&lt;/a&gt; by Carson Gross&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Carson closed out the conference with an overview of htmx 4.0.0 features. Here&amp;#8217;s a few highlights:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A strong focus and return to established web standards.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;As mentioned at &lt;a href=&quot;/articles/big_sky_dev_con_2024&quot;&gt;Big Sky Dev Con 2024&lt;/a&gt;, Carson continues using social media to play the game while staying true the cause.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;htmx 4.0.0&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Will include an upgrade helper.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Will support declarative partial updates via the new &lt;code&gt;hx-partial&lt;/code&gt; element (think &lt;code&gt;template&lt;/code&gt; but enhanced).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A new &lt;code&gt;hx-action&lt;/code&gt; and &lt;code&gt;hx-method&lt;/code&gt; methods which taps into Alexander Petros&#39; &lt;a href=&quot;https://triptychproject.org&quot;&gt;Triptych&lt;/a&gt; project. Super cool!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A new &lt;code&gt;hx-alpine-compat&lt;/code&gt; attribute that smoothes out interaction with &lt;a href=&quot;https://alpinejs.dev&quot;&gt;Alpine.js&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Much more!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The best part&amp;#8201;&amp;#8212;&amp;#8201;and epic ending to the entire conference&amp;#8201;&amp;#8212;&amp;#8201;was when Carson mentioned the Gameboy game for htmx 4.0.0 and then handed the mic to Stephen Mitchell to explain the development of the game, the design, levels, and access to the source code. Even better, as conference attendees, we all got a copy of the &lt;a href=&quot;https://swag.htmx.org/products/htmx-4-the-game&quot;&gt;game&lt;/a&gt;!&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock focal_point&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;/images/articles/big_sky_dev_con_2026/game.png&quot; alt=&quot;Game&quot; width=&quot;720&quot; height=&quot;658&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The only bad aspect of this is that the game is labeled as a &lt;em&gt;Golden Master&lt;/em&gt; but&amp;#8201;&amp;#8212;&amp;#8201;for all us that lived through that era and knows what this means&amp;#8201;&amp;#8212;&amp;#8201;this &lt;em&gt;isn&amp;#8217;t&lt;/em&gt; a Golden Master, only a Beta. Use of Golden Master is incorrect and misleading.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Both Carson and Stephen were channeling a Steve Jobs kind of vibe with the release of the htmx 4.0.0 and the corresponding game (&lt;a href=&quot;https://www.youtube.com/watch?v=M-GtT4-DgkI&amp;amp;list=PLLYO-P10l3y0&amp;amp;index=17&quot;&gt;watch&lt;/a&gt;). Loved it and definitely made the conference special.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Stephen closed with &quot;Don&amp;#8217;t stop caring and love your craft&quot;. This resonates deeply because this site and my entire career has been focused on the &lt;em&gt;Joy of the Craft&lt;/em&gt;. This also coincides with another great quote, above a bench, while walking the &lt;a href=&quot;https://www.montana.edu&quot;&gt;Montana State University&lt;/a&gt; campus:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;quoteblock&quot;&gt;
&lt;blockquote&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;I want our graduates to leave here with the clear understanding that ethical purpose and pursuit of knowledge are higher life goals than the pursuit of profit.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;attribution&quot;&gt;
&amp;#8212; Michael P. Malone
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock focal_point&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;/images/articles/big_sky_dev_con_2026/bench.png&quot; alt=&quot;Bench&quot; width=&quot;1000&quot; height=&quot;707&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In the Age of Slopware, don&amp;#8217;t forget what it means to create high quality, well architected, and maintainable code. Sweating the details of good quality software&amp;#8201;&amp;#8212;&amp;#8201;both inside and outside&amp;#8201;&amp;#8212;&amp;#8201;is important. Don&amp;#8217;t lose that!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This, by far, has been the best conference of the year. Glad I was able to attend and speak. The entire team has stepped up their game since I was last here in 2024. Loved being on the college campus and being back in my home state. In some ways, this is a bit of a full circle for me by being able to share the joy of the craft with others that started several decades ago.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Looking forward to attending in 2027 and hope you consider this conference in the future as well!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
    <id>https://alchemists.io/articles/big_sky_dev_con_2026</id>
    <link href="https://alchemists.io/articles/big_sky_dev_con_2026"/>
    <published>2026-08-01T16:10:18+00:00</published>
    <rights>2026</rights>
    <title>Big Sky Dev Con 2026</title>
    <updated>2026-09-11T16:06:01+00:00</updated>
    <dc:date>2026-09-11T16:06:01+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>Brooke Kuhlmann</name>
      <uri>https://alchemists.io/team/brooke_kuhlmann</uri>
    </author>
    <category term="ruby"
      label="Ruby"/>
    <category term="rack"
      label="Rack"/>
    <category term="htmx"
      label="htmx"/>
    <content type="html">&lt;div class=&quot;imageblock focal_point&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;/images/articles/server_sent_events/cover.png&quot; alt=&quot;Cover&quot; width=&quot;842&quot; height=&quot;280&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Server Sent Events&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Server Sent Events (SSE) allows a server to push data, one way, to a client over a long lived HTTP connection without the client needing to make multiple requests. Even better, the client will automatically reconnect if the connection drops. Here&amp;#8217;s a few use cases that SSE is great for:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Real-time notifications.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Live dashboards.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Live edits and rendering of updated content.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Progress updates of long running tasks.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Implementing SSE is fairly trivial in &lt;a href=&quot;https://www.ruby-lang.org&quot;&gt;Ruby&lt;/a&gt; especially when using &lt;a href=&quot;https://github.com/rack/rack&quot;&gt;Rack&lt;/a&gt; 3.0.0 middleware and &lt;a href=&quot;https://htmx.org&quot;&gt;htmx&lt;/a&gt; on the front end so let&amp;#8217;s explore further by diving into the code!&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot; class=&quot;title&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#_quick_start&quot;&gt;Quick Start&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_ruby&quot;&gt;Ruby&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#_gems&quot;&gt;Gems&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_dependencies&quot;&gt;Dependencies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_event_source&quot;&gt;Event Source&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_middleware&quot;&gt;Middleware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_rack&quot;&gt;Rack&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_html_htmx&quot;&gt;HTML (htmx)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_curl&quot;&gt;curl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_falcon&quot;&gt;Falcon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_running&quot;&gt;Running&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_specs&quot;&gt;Specs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_quick_start&quot;&gt;Quick Start&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;If you&amp;#8217;d prefer to have all of the code and start playing around, you only need these two files:&lt;/p&gt;
&lt;/div&gt;
&lt;details&gt;
&lt;summary class=&quot;title&quot;&gt;Ruby&lt;/summary&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Save as &lt;code&gt;demo&lt;/code&gt; and run &lt;code&gt;chmod 755 demo&lt;/code&gt; to make it executable.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;#! /usr/bin/env ruby&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Save as `demo`, then `chmod 755 demo`, and run as `./demo`.&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;bundler/inline&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;gemfile&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;https://rubygems.org&quot;&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;amazing_print&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;cogger&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;containable&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;debug&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;infusible&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;initable&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;puma&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;rack&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;rackup&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Container&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Containable&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Cogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;id: :demo&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Dependencies&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Infusible&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EventSource&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Dependencies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Initable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;%i[req id]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;kernel: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Kernel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;write&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Errno&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EPIPE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Errno&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ECONNRESET&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IOError&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;debug&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Event stream disconnected.&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;ensure&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;count: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;kernel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CONTENT&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
        event: demo
        data: Found: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;, Count: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;      CONTENT&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;

      &lt;span class=&quot;nb&quot;&gt;sleep&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Middleware&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Initable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;sx&quot;&gt;%i[req application]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;sx&quot;&gt;%i[keyreq pattern]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;headers: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;Cache-Control&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;no-cache&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;text/event-stream&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;X-Accel-Buffering&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;no&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;event_source: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EventSource&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;environment&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rack&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;environment&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;path&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pattern&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event_source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;environment&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;application&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rack&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Middleware&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;pattern: &lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;%r(^/sse/(?&amp;lt;id&amp;gt;.+)$)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rack&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Static&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;urls: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;root: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;index: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;index.html&quot;&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;content-type&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;text/plain&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Demonstration&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Rackup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;app: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;application&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;details&gt;
&lt;summary class=&quot;title&quot;&gt;HTML&lt;/summary&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Save as &lt;code&gt;index.html&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;lang=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;en&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;viewport&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;content=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;width=device-width,initial-scale=1,shrink-to-fit=no&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Demo&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;charset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;description&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;content=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A demo site.&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;author&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;content=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Alchemists&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;style &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/css&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://unpkg.com/htmx.org@2.0.10&quot;&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;integrity=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;sha384-H5SrcfygHmAuTDZphMHqBJLc3FhssKjG7w/CeCpFReSfwBWDTKpkzPP8c+cLsK+V&quot;&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;crossorigin=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;anonymous&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://unpkg.com/htmx-ext-sse@2.2.4&quot;&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;integrity=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;sha384-A986SAtodyH8eg8x8irJnYUk7i9inVQqYigD6qZ9evobksGNIXfeFvDwLSHcp31N&quot;&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;crossorigin=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;anonymous&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;hx-ext=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;sse&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;sse-connect=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/sse/1&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;sse-swap=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;demo&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Loading...&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Once you&amp;#8217;ve saved the above to disk, you should have the following files:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;index.html
demo&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;To launch the application, run: &lt;code&gt;./demo&lt;/code&gt;. The open &lt;code&gt;&lt;a href=&quot;http://localhost:9292&quot; class=&quot;bare&quot;&gt;http://localhost:9292&lt;/a&gt;&lt;/code&gt; in your default web browser. The rest of this article will explain how all of this works.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_ruby&quot;&gt;Ruby&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;With the above, we are using a &lt;a href=&quot;/articles/ruby_bundler_inline&quot;&gt;Bundler Inline&lt;/a&gt; script for demonstration purposes. Normally, you&amp;#8217;d want to use &lt;a href=&quot;projects/rubysmith&quot;&gt;Rubysmith&lt;/a&gt; to split the implementation into separate files but keeping all of this together, for now, allows you to quickly experiment with the code.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;_gems&quot;&gt;Gems&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;At the top of &lt;a href=&quot;/articles/ruby_bundler_inline&quot;&gt;Bundler Inline&lt;/a&gt; script, several gems are installed for you. The following breaks down why each is used:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.ruby-lang.org&quot;&gt;Ruby&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/amazing-print/amazing_print&quot;&gt;Amazing Print&lt;/a&gt;: For pretty printing of inspected objects.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/projects/cogger&quot;&gt;Cogger&lt;/a&gt;: For logging.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/projects/containable&quot;&gt;Containable&lt;/a&gt;: For dependency injection containers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/ruby/debug&quot;&gt;Debug&lt;/a&gt;: For debugging code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/projects/infusible&quot;&gt;Infusible&lt;/a&gt;: For automatic dependency injection.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/projects/initable&quot;&gt;Initable&lt;/a&gt;: For automatic initialization.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://puma.io&quot;&gt;Puma&lt;/a&gt;: For serving web content.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/rack/rack&quot;&gt;Rack&lt;/a&gt;: For building modular web applications.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/rack/rackup&quot;&gt;Rackup&lt;/a&gt;: For launching rack middleware.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://htmx.org&quot;&gt;htmx&lt;/a&gt;: For sending HTML fragments over the wire.&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://htmx.org/extensions/sse/&quot;&gt;htmx SSE Extension&lt;/a&gt;: For processing and rendering SSE.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;_dependencies&quot;&gt;Dependencies&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Next, we register and prepare our dependencies for automatic injection:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Container&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Containable&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Cogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;id: :demo&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Dependencies&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Infusible&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This allows us to leverage dependency injection containers for dependency injection via &lt;a href=&quot;/projects/containable&quot;&gt;Containable&lt;/a&gt; and &lt;a href=&quot;/projects/infusible&quot;&gt;Infusible&lt;/a&gt; respectively so we can register and inject &lt;a href=&quot;/projects/cogger&quot;&gt;Cogger&lt;/a&gt; for logging purposes.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;_event_source&quot;&gt;Event Source&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Next, we have our event source:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EventSource&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Dependencies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Initable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;%i[req id]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;kernel: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Kernel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;write&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Errno&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EPIPE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Errno&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ECONNRESET&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IOError&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;debug&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Event stream disconnected.&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;ensure&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;count: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;kernel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CONTENT&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
        event: demo
        data: Found: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;, Count: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;      CONTENT&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;

      &lt;span class=&quot;nb&quot;&gt;sleep&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The &lt;code&gt;logger&lt;/code&gt; and &lt;code&gt;kernel&lt;/code&gt; are injected as dependencies while the &lt;code&gt;id&lt;/code&gt; is required for loading of a record associated with the ID. For demonstration purposes, we only print the ID to avoid setting up a persistence layer since you can do that on your own if you like.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In order to adhere to &lt;a href=&quot;https://github.com/rack/rack&quot;&gt;Rack&lt;/a&gt; 3.0.0 specification for event streaming, we only need make our event source callable by implementing the &lt;code&gt;#call&lt;/code&gt; method which does the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Writes to the given stream.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Gracefully handles errors by logging them.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ensures the stream is always closed.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;You&amp;#8217;ll notice the private &lt;code&gt;#write&lt;/code&gt; method loops indefinitely on a &lt;code&gt;0.5&lt;/code&gt; second sleep schedule. In order to make the output interesting, a counter is incremented but, again, you&amp;#8217;d normally be loading or processing data to write to the stream instead of updating a simple counter.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;When writing to the stream, you always need the following keys:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;event&lt;/code&gt;: The event name (or identifier). In our case, &lt;code&gt;demo&lt;/code&gt; is our name. We&amp;#8217;ll look for this even in our &lt;a href=&quot;https://htmx.org&quot;&gt;htmx&lt;/a&gt; client shortly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;data&lt;/code&gt;: The actual data we want to return.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Both the &lt;code&gt;event&lt;/code&gt; and &lt;code&gt;data&lt;/code&gt; data keys must be on separate lines followed by two new lines. Example (with new lines included for demonstration purposes:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;event: demo\n
data: Found 1, Count: 5\n
\n&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;It&amp;#8217;s &lt;em&gt;critical&lt;/em&gt; that you always adhere to this format or your events will be corrupted. It&amp;#8217;s important to note that you can use multiple data keys if necessary. Example:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;event: demo
data: Found 1
data: ,
data: Count: 5&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The above will be automatically concatenated for you and is identical to the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;event: demo
data: Found 1, Count: 5&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;If you need to keep writing to the stream in order to keep the stream alive, so-to-speak, you can drop the &lt;code&gt;data&lt;/code&gt; key to use a colon:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;event: demo
: A comment.&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Some call this a pulse or a heartbeat to prevent the stream from disconnecting especially when your writes to the stream are infrequent. All comments are ignored by your client (basically a no operation). Again, only useful if your updates are infrequent in order to keep the connection open.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;_middleware&quot;&gt;Middleware&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In order to be compatible with &lt;a href=&quot;https://github.com/rack/rack&quot;&gt;Rack&lt;/a&gt;, our middleware must be callable:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Middleware&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Initable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;sx&quot;&gt;%i[req application]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;sx&quot;&gt;%i[keyreq pattern]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;headers: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;Cache-Control&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;no-cache&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;text/event-stream&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;X-Accel-Buffering&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;no&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;event_source: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EventSource&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;environment&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rack&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;environment&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;path&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pattern&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event_source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;environment&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We also need to require &lt;code&gt;application&lt;/code&gt; as the first positional parameter. Everything else is optional. Each, optional, keyword parameter is:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pattern&lt;/code&gt;: This allows us to match the request path based on regular expression pattern. This also makes this middleware reusable for different routes that need shared behavior.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;headers&lt;/code&gt;: The first two headers are required for being in compliance with Server Sent Events. The &lt;code&gt;X-Accel-Buffering&lt;/code&gt; is optional but aids in being compatible with Nginx load balancers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;event_source&lt;/code&gt;: This is our &lt;code&gt;EventSource&lt;/code&gt; object, as discussed earlier, which aids in swapping in different implementations if desired.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;It&amp;#8217;s worth pointing out, depending on your situation, that you might want to modify the &lt;code&gt;case&lt;/code&gt; statement as follows:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pattern&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;id:
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;puma.mark_as_io_bound&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;rack.session.options&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:skip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event_source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;environment&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The first line allows you to make use of the &lt;a href=&quot;https://puma.io&quot;&gt;Puma&lt;/a&gt; feature that allows you to mark your Server Sent Event as I/0 bound so it can exceed the normal thread pool maximum for I/O operations that are usually slower. This pairs well with Puma&amp;#8217;s &lt;code&gt;max_io_threads&lt;/code&gt; configuration setting.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The second line allows you to skip sending your Rack session/authentication/cookie information with each event since you only need to send this once upon initial connection. Otherwise, you&amp;#8217;ll see duplicate sessions pile up with each response which is definitely &lt;em&gt;not&lt;/em&gt; what you want when streaming events.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;_rack&quot;&gt;Rack&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The last part of our script uses &lt;code&gt;Rack::Builder&lt;/code&gt; to add our middleware, serve static assets (especially our index page), and run our demonstration application.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;application&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rack&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Middleware&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;pattern: &lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;%r(^/sse/(?&amp;lt;id&amp;gt;.+)$)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rack&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Static&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;urls: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;root: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;index: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;index.html&quot;&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;content-type&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;text/plain&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Demonstration&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Rackup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;app: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;application&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Again&amp;#8201;&amp;#8212;&amp;#8201;and as mentioned earlier&amp;#8201;&amp;#8212;&amp;#8201;you&amp;#8217;d split all objects out to separate files and use a &lt;code&gt;config.ru&lt;/code&gt; file to launch your Rack app but keeping all this logic in one file makes this easier to demonstrate.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;That&amp;#8217;s it for our Ruby code. Now let&amp;#8217;s look at our htmx client.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_html_htmx&quot;&gt;HTML (htmx)&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://htmx.org&quot;&gt;htmx&lt;/a&gt; is great for having a simple client for consuming our Server Sent Events:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;lang=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;en&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;viewport&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;content=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;width=device-width,initial-scale=1,shrink-to-fit=no&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Demo&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;charset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;description&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;content=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;A demo site.&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;author&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;content=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Alchemists&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;style &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/css&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://unpkg.com/htmx.org@2.0.10&quot;&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;integrity=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;sha384-H5SrcfygHmAuTDZphMHqBJLc3FhssKjG7w/CeCpFReSfwBWDTKpkzPP8c+cLsK+V&quot;&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;crossorigin=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;anonymous&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://unpkg.com/htmx-ext-sse@2.2.4&quot;&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;integrity=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;sha384-A986SAtodyH8eg8x8irJnYUk7i9inVQqYigD6qZ9evobksGNIXfeFvDwLSHcp31N&quot;&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;crossorigin=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;anonymous&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;hx-ext=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;sse&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;sse-connect=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/sse/1&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;sse-swap=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;demo&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Loading...&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;With the above, we require the &lt;a href=&quot;https://htmx.org&quot;&gt;htmx&lt;/a&gt; library and associated &lt;a href=&quot;https://htmx.org/extensions/sse/&quot;&gt;htmx SSE Extension&lt;/a&gt; extension for consuming and rendering events. You&amp;#8217;ll notice that our path (&lt;code&gt;/sse/1&lt;/code&gt;) maps to the same path used to configure our middleware and the event name, &lt;code&gt;demo&lt;/code&gt;, also maps to the name used in our &lt;code&gt;EventSource&lt;/code&gt; implementation. This is what connects the client and server together.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_curl&quot;&gt;curl&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;You&amp;#8217;re not limited to viewing the output of your SSE via the web browser as you can use &lt;code&gt;curl&lt;/code&gt; as follows:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;bash&quot;&gt;curl &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; http://localhost:9292/sse/1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The above should yield output similar to the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;* Host localhost:9292 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:9292...
* Connected to localhost (::1) port 9292
&amp;gt; GET /sse/1 HTTP/1.1
&amp;gt; Host: localhost:9292
&amp;gt; User-Agent: curl/8.7.1
&amp;gt; Accept: */*
&amp;gt;
* Request completely sent off
&amp;lt; HTTP/1.1 200 OK
&amp;lt; cache-control: no-cache
&amp;lt; content-type: text/event-stream
&amp;lt; x-accel-buffering: no
&amp;lt;
* no chunk, no close, no size. Assume close to signal end
event: demo
data: Found: 1, Count: 1

event: demo
data: Found: 1, Count: 2

event: demo
data: Found: 1, Count: 3&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_falcon&quot;&gt;Falcon&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;If you&amp;#8217;d perfer &lt;a href=&quot;https://socketry.github.io/falcon/&quot;&gt;Falcon&lt;/a&gt;, instead of &lt;a href=&quot;https://puma.io&quot;&gt;Puma&lt;/a&gt;, you can definitely do this by requiring the gem and using &lt;code&gt;falcon&lt;/code&gt; as the server by changing the very last line of the &lt;a href=&quot;/articles/ruby_bundler_inline&quot;&gt;Bundler Inline&lt;/a&gt; script as follows:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;Rackup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;app: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;server: :falcon&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_running&quot;&gt;Running&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As mentioned in the &lt;em&gt;Quick Start&lt;/em&gt; section, to run the demonstration, you only need to run as follows:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;./demo
open http://localhost:9292&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;💡 You can then launch the same URL in multiple browser tabs to get multiple streams running at once.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_specs&quot;&gt;Specs&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;If using &lt;a href=&quot;https://puma.io&quot;&gt;Puma&lt;/a&gt;, ensure you use two minimum and two maximum threads since you&amp;#8217;ll need one thread for normal page requests and one for SSE connections since they are persisted and longer lived. Otherwise, you&amp;#8217;ll have random spec failures with fewer threads. Here&amp;#8217;s what you should have in your spec helper for your &lt;em&gt;feature&lt;/em&gt; specs to get started:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;Capybara&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:puma&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Silent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Threads&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;2:2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The threads value of &lt;code&gt;2:2&lt;/code&gt; means a minimum of two threads (first number before the colon) and a maximum of two threads (second value after the colon). You can definitely tweak these numbers by making them smaller or larger but do recommend keeping the maximum number of threads two or higher.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As you can see, implementing SSE takes minimal effort. You only need an event stream, middleware, and a client to wire everything up. Even better, you&amp;#8217;ve learned how to make all of your objects reusable and configurable should you need to introduce different behavior without complicating or reducing the maintenance of your architecture.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Enjoy and may your applications be lively!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
    <id>https://alchemists.io/articles/server_sent_events</id>
    <link href="https://alchemists.io/articles/server_sent_events"/>
    <published>2026-07-01T12:06:32+00:00</published>
    <rights>2026</rights>
    <title>Server Sent Events</title>
    <updated>2026-07-02T13:05:31+00:00</updated>
    <dc:date>2026-07-02T13:05:31+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>Brooke Kuhlmann</name>
      <uri>https://alchemists.io/team/brooke_kuhlmann</uri>
    </author>
    <category term="engineering"
      label="Engineering"/>
    <content type="html">&lt;div class=&quot;imageblock focal_point&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;/images/articles/proactive_engineering/cover.png&quot; alt=&quot;Cover&quot; width=&quot;874&quot; height=&quot;274&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Proactive Engineering&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Proactive engineering is the practice&amp;#8201;&amp;#8212;&amp;#8201;and &lt;a href=&quot;/rigor&quot;&gt;rigor&lt;/a&gt;&amp;#8201;&amp;#8212;&amp;#8201;of staying atop new technologies, designs, and changes in order to keep your software stack healthy, minimize risks, and keep technical debt low. This allows you to build a healthy team and well maintained code base that is a joy to work with.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Proactive engineering is also the exact opposite of &lt;em&gt;reactive&lt;/em&gt; engineering (a.k.a. move fast and break things). The problem is most teams don&amp;#8217;t have this kind of rigor. They work in a constant state of fire fighting, dealing with on-call issues, bombarded with interrupts, and/or sheer neglect. The goal is to get you into a healthy state in which you have time for &lt;a href=&quot;https://app.thestorygraph.com/books/021987b7-8555-4413-bee0-c073d94b3fe3&quot;&gt;Deep Work&lt;/a&gt; with the ability to maintain your application, research solutions, and focus on delivering a high quality product your customers enjoy.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot; class=&quot;title&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#_notifications&quot;&gt;Notifications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_news&quot;&gt;News&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_weekly_upkeep&quot;&gt;Weekly Upkeep&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_daily_habits&quot;&gt;Daily Habits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_culture&quot;&gt;Culture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_notifications&quot;&gt;Notifications&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;First and foremost turn off all notifications or, at a minimum, silence them so they don&amp;#8217;t interrupt your flow state. The kinds of notifications you want to disable is social media, syndicated feeds, email, group chat, source code issues/reviews, and so forth.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;You might think you can tackle multiple things at once but the human brain isn&amp;#8217;t wired to work like that. What&amp;#8217;ll you end up doing is a lot of shallow work with a higher chance of introducing issues which could have been mitigated if given your full attention. This creates more work which is the opposite of the desired outcome you started with.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Your super power is to remain deeply focused on your task at hand, completing it, and moving on. Even better, this gives you the space to see connections to other aspects of your architecture which can be refactored or improved upon further because you have the time to think deeply, detect duplication, and optimize your code base further rather than trying to complete as many tasks in as little time as possible.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Any interrupts, while working in this flow state, should be immediately added to your second brain (example: &lt;a href=&quot;https://obsidian.md&quot;&gt;Obsidian&lt;/a&gt;) so you can tackle after your task at hand is complete. Interrupts in this context, would be new discoveries, new solutions, refactorings, deletion of dead code, etc. Important to identify but better to address afterwards.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;When you turn off notifications, you put all the power back in your hands. You dictate when and how you&amp;#8217;ll tackle interrupts not the other way around. This is the first step in being &lt;em&gt;proactive&lt;/em&gt; instead of reactive. Welcome, friend.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_news&quot;&gt;News&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;At one point, &lt;a href=&quot;https://changelog.com&quot;&gt;The Changelog&lt;/a&gt; used to sign off with the adage: &quot;Open source moves fast, keep up!&quot; They are correct because the minute you fall behind, the technical debt rises which makes updating your software projects that much harder.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The best way to stay abreast on what&amp;#8217;s changing in the world is through &lt;a href=&quot;/articles/syndication&quot;&gt;Syndication&lt;/a&gt;. This empowers you to manage your own curated sources of information while your feed reader works tirelessly, in the background, to inform you of updates.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;At a minimum, your feeds should be checked morning and noon every day via this simple workflow:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;olist arabic&quot;&gt;
&lt;ol class=&quot;arabic&quot;&gt;
&lt;li&gt;
&lt;p&gt;Open your feed reader to catch up on all updates.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add next actions into you second brain, as necessary, to tackle after you&amp;#8217;re caught up.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finish by getting to &lt;a href=&quot;/articles/inbox_zero&quot;&gt;Inbox Zero&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Close your feed reader when done to avoid it distracting you further.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As with notifications, you control when you catch up on the news, not the other way around. This allows you to fold in changes as you see them with minimal effort. For example, upgrading to the latest &lt;a href=&quot;https://www.ruby-lang.org&quot;&gt;Ruby&lt;/a&gt; patch or gem update because it&amp;#8217;s a lot easier to address changes as you see them rather than upgrade a bunch of gems at once. This is made even easier when your test suite is at or near 100% code coverage so you have even more confidence to upgrade early and often.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_weekly_upkeep&quot;&gt;Weekly Upkeep&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As done with personal hygiene, physical exercise, eating healthy foods, and getting plenty of sleep, your &lt;a href=&quot;/articles/machine_upkeep&quot;&gt;Machine Upkeep&lt;/a&gt; is equally critical to augmenting your mental and physical abilities. This means the upkeep of your machine should be performed every other day. On a weekly schedule, this means Monday, Wednesday, and Friday at a minimum. Do this before the start of your work day.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In addition to keeping your machine up-to-date, the same must be applied to all projects you maintain. &lt;a href=&quot;https://www.gnu.org/software/bash&quot;&gt;Bash&lt;/a&gt; or &lt;a href=&quot;https://www.ruby-lang.org&quot;&gt;Ruby&lt;/a&gt; scripts are extremely valuable in automating this workflow. For plenty of examples, feel free to pilfer from the following projects:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/projects/dotfiles&quot;&gt;Dotfiles&lt;/a&gt;: Use to manage your Dotfiles and associated configurations including tapping into a wealth of aliases and functions that speed up your workflow.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/projects/mac_os-config&quot;&gt;macOS Configuration&lt;/a&gt;: Use to rebuild your machine(s) from scratch and keep them up to date as well.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Having this kind of automation in place reduces the friction of upgrading your projects so you can quickly resolve any issues and move on. This also means you&amp;#8217;re more likely to perform upgrades, once detected, instead of pushing off until later. This is &lt;em&gt;exactly&lt;/em&gt; the kind of workflow you need in order to prevent tech debt from building up.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;To illustrate further, each &lt;a href=&quot;https://docs.rubocop.org/rubocop/latest/index.html&quot;&gt;RuboCop&lt;/a&gt; release almost always takes time to read through the release notes because the project doesn&amp;#8217;t adhere to &lt;a href=&quot;/articles/milestones&quot;&gt;Milestones&lt;/a&gt; or always has documentation updated in lock step with each release. This then requires chasing down the source code to get better examples of what has changed and/or testing out each cop individually so you can make &lt;a href=&quot;/articles/git_commit_anatomy&quot;&gt;atomic commits&lt;/a&gt; to your own code to explain the &lt;em&gt;what&lt;/em&gt; and and &lt;em&gt;why&lt;/em&gt; of the change(s). Doing this work per version release is less time consuming than putting this off and tackling all changes at once which can take a day or more.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;There is another benefit, not often discussed enough, which is retaining and attracting new talent because the joy of working in a well maintained codebase increases productivity due to less friction of fighting with broken aspects of your tech stack. Fixing a bug or adding a feature should be enjoyable and effortless as possible. This doesn&amp;#8217;t mean the problem you are tackling isn&amp;#8217;t a challenge or takes time to accomplish, only that you shouldn&amp;#8217;t be constantly fighting against the software stack you are working in.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_daily_habits&quot;&gt;Daily Habits&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As written in &lt;a href=&quot;https://app.thestorygraph.com/books/7595d6e0-b75d-4f37-9226-50d5a73851df&quot;&gt;Atomic Habits&lt;/a&gt; by James Clear, ensure you turn all of the above into habits. Ideally, you don&amp;#8217;t want to think about them but intrinsically do them as part of your daily flow. To aid in this endeavor, lean on your second brain (or task managing software in general). For example, consider using the following workflow at dawn and dusk each day:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt; (catch up and/or address feedback)&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Check mobile/watch notifications.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check communications (i.e. social media, email, group chat, etc).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check GitHub notifications.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check feeds.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Plan&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Review calendars.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Review/plan next actions based on new information gleaned from above.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Clean&lt;/strong&gt; (a.k.a. &lt;a href=&quot;/articles/inbox_zero&quot;&gt;Inbox Zero&lt;/a&gt;)&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Clear your email.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Clear your feed reader.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Clear your voice memos.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Clear your second brain inbox (i.e. &lt;a href=&quot;https://www.omnigroup.com/omnifocus/&quot;&gt;OmniFocus&lt;/a&gt;, &lt;a href=&quot;https://obsidian.md&quot;&gt;Obsidian&lt;/a&gt;, etc).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Clear browser tabs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Close all communication and news apps.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reflect on yesterday&amp;#8217;s learnings and plan any final actions.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;💡 At dusk, one key difference is not to reflect on yesterday&amp;#8217;s learnings but the current day&amp;#8217;s learnings instead. This is also a good chance to capture any further notes from the day in which to reflect upon tomorrow.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The above should be on a reoccurring schedule that appears at dawn (Example: 6am) and then again at dusk (Example: 5pm). Any task managing software (trusted system) can automate and ensure these actions are front and center.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The above framework puts you in control so you can do what you do best with a strong emphasis on high quality output. Most importantly, you are in control which means you are being calm, collected, and proactive in your actions rather than reacting without having time to think.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_culture&quot;&gt;Culture&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Once you have your daily habits in place, make this a core part of your culture. You do this by avoiding &lt;a href=&quot;/articles/interviewing&quot;&gt;hiring&lt;/a&gt; anyone who doesn&amp;#8217;t have (or can&amp;#8217;t learn) these habits and fire anyone who can&amp;#8217;t maintain this discipline. Remember, each person you add to your team should raise bar and reinforce the culture, not lower.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In this Age of Slopware, &lt;a href=&quot;https://app.thestorygraph.com/books/5d972450-a8fa-48bd-98ec-6325a22a8b34&quot;&gt;Enshittification&lt;/a&gt;, and lack of code quality in general, we need more engineers that show up day in and day out with professional etiquette that doesn&amp;#8217;t tolerate anything less. Being proactive puts you in control by allowing you to tame the chaos and have a solid structure in which to build amazing cultures, teams, products, and services.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We can all do better and being proactive aids in this endeavor. Make this a priority so you can enjoy the work and the people you work with.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
    <id>https://alchemists.io/articles/proactive_engineering</id>
    <link href="https://alchemists.io/articles/proactive_engineering"/>
    <published>2026-06-01T12:07:18+00:00</published>
    <rights>2026</rights>
    <title>Proactive Engineering</title>
    <updated>2026-06-01T12:07:13+00:00</updated>
    <dc:date>2026-06-01T12:07:13+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>Brooke Kuhlmann</name>
      <uri>https://alchemists.io/team/brooke_kuhlmann</uri>
    </author>
    <category term="ruby"
      label="Ruby"/>
    <content type="html">&lt;div class=&quot;imageblock focal_point&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;a class=&quot;image&quot; href=&quot;https://blueridgeruby.com&quot;&gt;&lt;img src=&quot;/images/articles/blue_ridge_ruby_2026/cover.png&quot; alt=&quot;Cover&quot; width=&quot;650&quot; height=&quot;250&quot;&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Blue Ridge Ruby 2026&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;I attended &lt;a href=&quot;https://blueridgeruby.com&quot;&gt;Blue Ridge Ruby&lt;/a&gt; for the first time and had a great time speaking and participating in the conference held in &lt;a href=&quot;https://www.romanticasheville.com&quot;&gt;Asheville, North Carolina&lt;/a&gt;. The following are a learnings and experiences that might be of interest.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot; class=&quot;title&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#_conference&quot;&gt;Conference&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#_day_1_wednesday&quot;&gt;Day 1 (Wednesday)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_day_2_thursday&quot;&gt;Day 2 (Thursday)&lt;/a&gt;
&lt;ul class=&quot;sectlevel3&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#_state_is_the_first_decision_you_never_made_by_joël_quenneville&quot;&gt;State is the First Decision You Never Made by Joël Quenneville&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_yes_and_rubys_secret_talent_for_improvisation_by_ifat_ribon&quot;&gt;Yes, and: Ruby’s Secret Talent for Improvisation by Ifat Ribon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_lunch&quot;&gt;Lunch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_how_to_finish_what_you_start_lessons_from_actually_shipping_a_big_refactor_by_annie_kiley&quot;&gt;How To Finish What You Start: Lessons From Actually Shipping a Big Refactor by Annie Kiley&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_instillment_of_successful_practices_in_an_agentic_world_by_kevin_murphy&quot;&gt;InstiLLMent of Successful Practices in an Agentic World by Kevin Murphy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_dinner&quot;&gt;Dinner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_roundtable_discussion&quot;&gt;Roundtable Discussion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_day_3_friday&quot;&gt;Day 3 (Friday)&lt;/a&gt;
&lt;ul class=&quot;sectlevel3&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#_terminus_a_hanami_htmx_web_application_for_epaper_devices&quot;&gt;Terminus: A Hanami + htmx web application for ePaper devices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_your_first_open_source_contribution_by_rachael_wright_munn&quot;&gt;Your First Open-Source Contribution by Rachael Wright-Munn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_llm_telemetry_as_a_first_class_rails_concern_by_david_paluy&quot;&gt;LLM Telemetry as a First-Class Rails Concern by David Paluy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_lunch_2&quot;&gt;Lunch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_lightning_talks&quot;&gt;Lightning Talks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_5_ways_to_invest_in_yourself_for_the_long_haul_by_thomas_cannon&quot;&gt;5 ways to invest in yourself for the long haul by Thomas Cannon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_dinner_2&quot;&gt;Dinner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_after_party&quot;&gt;After Party&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_asheville&quot;&gt;Asheville&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#_city_saturday&quot;&gt;City (Saturday)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_the_biltmore_sunday&quot;&gt;The Biltmore (Sunday)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_blue_ridge_parkway_monday&quot;&gt;Blue Ridge Parkway (Monday)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_the_river_arts_district_tuesday&quot;&gt;The River Arts District (Tuesday)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_the_rhu_and_home&quot;&gt;The Rhu and Home&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_conference&quot;&gt;Conference&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Both my wife and I traveled together so she could work on her novel and I could focus on the conference then have time to explore the city and surrounding area afterwards.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;_day_1_wednesday&quot;&gt;Day 1 (Wednesday)&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We woke at 5:30am to grab breakfast and then catch a cab, bus, and flight to Asheville. Thankfully, the Denver International Airport (DIA) has direct flights to Asheville.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;It was 3pm, due to gaining two hours while flying East, before we landed in Asheville. The airport, while small, is modern and nicely laid out. There was some construction but simple enough to grab your luggage and catch a cab downtown (~30 minutes drive, roughly).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We stayed at the &lt;a href=&quot;https://www.marriott.com/en-us/hotels/avlal-aloft-asheville-downtown&quot;&gt;Aloft&lt;/a&gt;, as recommended by the conference, with easy walking distance to the &lt;a href=&quot;https://www.ymiculturalcenter.org&quot;&gt;YMI Cultural Center&lt;/a&gt;. We ended up on the fifth floor with beautiful, unfettered, views of the mountains due to being at the end of the building. Definitely nice to wake up to each morning.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock focal_point&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;/images/articles/blue_ridge_ruby_2026/photos/hotel_dawn.png&quot; alt=&quot;Ruby Passport&quot; width=&quot;1060&quot; height=&quot;622&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Once unpacked, we walked over to the &lt;a href=&quot;https://www.ymiculturalcenter.org&quot;&gt;YMI Cultural Center&lt;/a&gt; to poke our heads in out of curiosity. This is where I met &lt;a href=&quot;https://dev.to/burdettelamar&quot;&gt;Burdette Lamar&lt;/a&gt;, a retired Rubyist, who works on &lt;a href=&quot;https://docs.ruby-lang.org&quot;&gt;Ruby Documentation&lt;/a&gt;. Even better, he&amp;#8217;s working on &lt;code&gt;Pathname&lt;/code&gt; documentation which warmed my heart because I see so many Rubyists who are not familiar with this Object API and only use &lt;code&gt;File&lt;/code&gt; when they should be using &lt;code&gt;Pathname&lt;/code&gt;. This is why I&amp;#8217;ve put effort into &lt;code&gt;Pathname&lt;/code&gt; &lt;a href=&quot;/projects/refinements&quot;&gt;Refinements&lt;/a&gt; so you can do even more with less in your implementations.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Afterwords we went to the &lt;a href=&quot;https://theblackbirdrestaurant.com&quot;&gt;Blackbird&lt;/a&gt; restaurant for the Speaker&amp;#8217;s Dinner. I ended up spending most of the evening talking with fellow speaker, Joël Quenneville, along with Mark Locklear (conference organizer). With Joël, our discussion was all about Elm, Ruby, and functional programming general. I always love talking to folks that blend Object Oriented + Functional programming in Ruby. This is exactly what my unpublished &lt;a href=&quot;/books&quot;&gt;book&lt;/a&gt; is about (which, yes, I need to publish).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;_day_2_thursday&quot;&gt;Day 2 (Thursday)&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This was the first full day of the conference so arrived at 8am to get my conference badge and a &lt;a href=&quot;https://therubypassport.com&quot;&gt;Ruby Passport&lt;/a&gt; which required filling out paperwork and tracking down a fellow attendee to notarize the document by drawing an impression of a random question they had to answer. A bit amusing since the paperwork also mimics a government document. This includes the texture and paper quality of the passport itself. Nice attention to detail. Once completed, I was able to get my passport officially stamped:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock focal_point&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;/images/articles/blue_ridge_ruby_2026/photos/passport_stamp.jpeg&quot; alt=&quot;Ruby Passport&quot; width=&quot;1000&quot; height=&quot;737&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;During this time, I met a bunch of new Rubyists before catching the first talk. The following are quick notes on what captured my attention. Definitely check out the videos when they land. The full schedule for the day can be found &lt;a href=&quot;https://blueridgeruby.com/schedule/#thursday&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_state_is_the_first_decision_you_never_made_by_joël_quenneville&quot;&gt;State is the First Decision You Never Made by Joël Quenneville&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The talk was a chapter from from Joël&amp;#8217;s upcoming book, &lt;a href=&quot;https://buttondown.com/webasplatform&quot;&gt;The Web as a Platform&lt;/a&gt;, and was well done in terms of the HTTP specification, the web in general, and managing state via an architecture that is stateless by nature. There&amp;#8217;s even a nice callout to &lt;a href=&quot;https://htmx.org&quot;&gt;htmx&lt;/a&gt; which I emphasize in my talk as well. Here&amp;#8217;s the &lt;a href=&quot;https://speakerdeck.com/joelq/state-is-the-first-decision-you-never-made&quot;&gt;slides&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_yes_and_rubys_secret_talent_for_improvisation_by_ifat_ribon&quot;&gt;Yes, and: Ruby’s Secret Talent for Improvisation by Ifat Ribon&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We chatted, briefly, while in the lobby before the day kicked off so was interested to learn where she would take this. The functional aspects of Ruby, especially functional composition which I&amp;#8217;ve also &lt;a href=&quot;/articles/ruby_function_composition&quot;&gt;written&lt;/a&gt; and &lt;a href=&quot;/talks/ruby_function_composition&quot;&gt;spoken&lt;/a&gt; about is always of particular interest to me. Even better is when people show how you can apply the &lt;a href=&quot;/articles/railway_pattern&quot;&gt;Railway Pattern&lt;/a&gt; for fault tolerant pipelines.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Instead, this talk was about the basics of procs and lambdas told through the lens of improvisation. Good for people getting started but there was definitely a part of me wishing the audience could see the full spectrum of how far you can take this and how much better your implementations can become.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_lunch&quot;&gt;Lunch&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;For lunch, I ended up catching up with folks at the &lt;a href=&quot;https://whiteducktacoshop.com&quot;&gt;White Duck Taco Shop&lt;/a&gt; which was delicious. I was late getting there because I wanted to test my slides after the last talk only to discover I was getting no video from my macOS.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Turns out there was a macOS Tahoe bug, that I haven&amp;#8217;t seen in previous versions, where your macOS can&amp;#8217;t recognize external displays. I believe this is related to HDMI connections but not entirely sure. The workaround was:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Find a speaker (thanks Christine) who was running an older version of macOS and have them connect to the projector.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enable AirPlay so you could send your mac&amp;#8217;s video to the machine connected to the projector.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;I wasn&amp;#8217;t the only one effected by this as a few other speakers ran into this too. If you are running macOS 26.4.1, then this is something to pay attention to.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_how_to_finish_what_you_start_lessons_from_actually_shipping_a_big_refactor_by_annie_kiley&quot;&gt;How To Finish What You Start: Lessons From Actually Shipping a Big Refactor by Annie Kiley&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This is more of critique&amp;#8201;&amp;#8212;&amp;#8201;but not of the speaker&amp;#8201;&amp;#8212;&amp;#8201;of what happens in large organizations that aren&amp;#8217;t adamant about code quality. What caught my eye, during this talk, was the lack of nested modules/namespaces. This is known as the &lt;a href=&quot;https://alchemists.io/articles/ruby_antipatterns#_flat_modules&quot;&gt;Flat Modules Antipattern&lt;/a&gt;. As a consultant, I&amp;#8217;ve run into this antipattern more times than I care to admit because this always causes unnecessary pain when improving or maintaining code bases (especially larger codebases).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;You can avoid this pitfall by always nesting your models because this gives you more flexibility in using modules and classes. Ruby definitely allows you to use flat modules but wish it didn&amp;#8217;t.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_instillment_of_successful_practices_in_an_agentic_world_by_kevin_murphy&quot;&gt;InstiLLMent of Successful Practices in an Agentic World by Kevin Murphy&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The day ended with LLM dark humor which is both a cautionary take on slopware and not forgetting about the human connection that makes up great teams. It&amp;#8217;s a well executed talk so give it a watch when it becomes available. Here are the &lt;a href=&quot;https://kevinjmurphy.com/posts/instillment-of-successful-practices-in-an-agentic-world/&quot;&gt;slides&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_dinner&quot;&gt;Dinner&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;For dinner, I ended up at &lt;a href=&quot;https://birdiespizza.com&quot;&gt;Birdie&amp;#8217;s Pizza&lt;/a&gt; hanging out with a few folks from the Greenville, North Carolina area. The pizza was good, and with only two huge slices, was definitely filling.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_roundtable_discussion&quot;&gt;Roundtable Discussion&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;I was most interested in this discussion but ended up leaving early because the discussion didn&amp;#8217;t tap in to the thing I wanted most which is actionable next steps. Like how do we form unions, collectives, etc. and how do we band together to fight for a better future where the craft of software can be enjoyed instead of the race to the bottom through slopware.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Instead, this ended up being more of a alcoholics anonymous meeting where the mic went around the room where each person would start with some form of the following: &quot;Hi, I&amp;#8217;m Bob and I&amp;#8217;m a recovering Rubyist of X years.&quot; Others would take the mic and then ramble on for what seemed like ages. I did enjoy that when Rachael Wright-Munn (fellow speaker) spoke, she was composed and to the point.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;All of the sentiment, sharing of experiences, etc was definitely valid but not exactly what I was looking for. I didn&amp;#8217;t end up speaking, only listening until I left after an hour in.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;_day_3_friday&quot;&gt;Day 3 (Friday)&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The following is a capture of my notes from Day 3. Here&amp;#8217;s the &lt;a href=&quot;https://blueridgeruby.com/schedule/#friday&quot;&gt;full schedule&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_terminus_a_hanami_htmx_web_application_for_epaper_devices&quot;&gt;Terminus: A Hanami + htmx web application for ePaper devices&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;I ended up kicking off the second day. I also had the most complicated talk in terms of technology. Here&amp;#8217;s the full setup:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.gl-inet.com/products/gl-e750&quot;&gt;Mudi Portable 4G LTE Router&lt;/a&gt;: My neighbor graciously let me borrow her Mudi for the trip because my &lt;a href=&quot;https://www.gl-inet.com/products/gl-e5800&quot;&gt;Mudi 7&lt;/a&gt; delivery was delayed. I needed this to create a private network that would have no interference from conference traffic because, when your whole presentation is built atop live running code, you want to reduce as many variables that can possibly go wrong.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://trmnl.com&quot;&gt;Four TRMNL ePaper Devices&lt;/a&gt;: These were connected to the Mudi and my &lt;a href=&quot;/projects/terminus&quot;&gt;Terminus&lt;/a&gt; server. They were passed out to the audience and set at a one second refresh rate so folks to could view each slide on the device in real-time with a slight delay. Christine and Thomas, fellow speakers, helped me distribute these devices and then collect them after.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/projects/terminus&quot;&gt;Terminus&lt;/a&gt;: This is my Hanami + htmx web application which was also serving and rendering my &lt;a href=&quot;https://alchemists.io/talks/terminus&quot;&gt;slides&lt;/a&gt; in real-time. All of this was running on the Mudi private network.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;I had fun giving this talk which was on intermediate to advance Ruby (functional, object oriented, and, advanced patterns). It was also the most code heavy of all talks. I&amp;#8217;ll let you judge whether I did a good job or not but hope folks are inspired to expand upon what&amp;#8217;s possible in Ruby.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;These talks always take a toll on me as my mouth tends to dry up when speaking and I had to pause, briefly, to get some water. I thought I had unscrewed the lid for quick access but, alas, it required an awkward moment to get back on track. Best laid plans as they say.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_your_first_open_source_contribution_by_rachael_wright_munn&quot;&gt;Your First Open-Source Contribution by Rachael Wright-Munn&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Rachael is a very good speaker including elegantly bouncing back from technical hiccups. Her talk was on getting started in open source, contributions, guidelines, and so forth. Definitely good for beginners.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;I&amp;#8217;ve also thought a lot about this and put a bunch of effort into automating much of the process set forth in Rachael&amp;#8217;s talk and would definitely recommend using these gems if setting up a new project which applies much of what Rachael is saying:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/projects/rubysmith&quot;&gt;Rubysmith&lt;/a&gt;: For Ruby projects.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/projects/gemsmith&quot;&gt;Gemsmith&lt;/a&gt;: For Ruby gems.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/projects/hanamismith&quot;&gt;Hanamismith&lt;/a&gt;: For Hanami projects.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;I never spin up a new project without the above. Hopefully, they help you too.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_llm_telemetry_as_a_first_class_rails_concern_by_david_paluy&quot;&gt;LLM Telemetry as a First-Class Rails Concern by David Paluy&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;David suffered some of the same display issues as me and wasn&amp;#8217;t able to fully use his machine, which was a bummer, but he handled the hurdles well and gave an interesting look at LLM rubrics. I&amp;#8217;m still chewing on all of this and still have it on my list to dig into this more and give feedback. We ended up talking afterwards which was enlightening. Super interesting and very positive person to talk to.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Here are his &lt;a href=&quot;https://speakerdeck.com/dpaluy/llm-telemetry-and-evals-as-first-class-rails-concerns-blueridgeruby-2026&quot;&gt;slides&lt;/a&gt; and recommended links to check out:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://huggingface.co/openai/privacy-filter&quot;&gt;OpenAI Privacy Filter&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/dpaluy/privacy-filter-server&quot;&gt;TraceBook Privacy Filter Sidecare&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/dpaluy/rubric_llm/tree/master/examples&quot;&gt;RubricLLM Examples&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_lunch_2&quot;&gt;Lunch&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Ended up at &lt;a href=&quot;https://packstavern.com&quot;&gt;Pack&amp;#8217;s Tavern&lt;/a&gt;, which was delicious, and talking to a lady interested in my hardware setup along with an old acquaintance, John Paul Ashenfelter, that I think I&amp;#8217;ve known for over a decade and Mark Starkman. The discussions were mostly focused on the craft of software engineering. Always my favorite kind of discussion.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_lightning_talks&quot;&gt;Lightning Talks&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Lots of interesting talks and too much to detail here. Make sure to check out the video when they land. I managed to give a talk on &lt;a href=&quot;/projects/git-lint&quot;&gt;Git Lint&lt;/a&gt; which, despite some rendering issues with my &lt;a href=&quot;/talks/git_lint&quot;&gt;slides&lt;/a&gt; (fixed now), made it in at the end.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;My lightning talk turned out to be serendipitous and a nice setup for Thomas as he would speak about the importance of good Git commit messages more in his talk that concluded the day. More people discussing how important this is is good for all of us.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_5_ways_to_invest_in_yourself_for_the_long_haul_by_thomas_cannon&quot;&gt;5 ways to invest in yourself for the long haul by Thomas Cannon&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This was a dense and jam packed talk that moved about as fast as my talk. I was delighted to see someone, as adamant as me, speaking about good Git etiquette and the power of &lt;a href=&quot;/articles/git_commit_anatomy&quot;&gt;Git Commit Anatomy&lt;/a&gt;. My favorite engineers are those that take the time to capture their thought process in their commits as it makes code reviews, learning the history of the project, and leveling up so much more enjoyable. Great writers make for great engineers.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;There&amp;#8217;s a lot to unpack in this talk so check out the &lt;a href=&quot;https://thomascannon.me/assets/talks/brr-2026/#0&quot;&gt;slides&lt;/a&gt; to learn more.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_dinner_2&quot;&gt;Dinner&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Funny enough, I ended back at &lt;a href=&quot;https://packstavern.com&quot;&gt;Pack&amp;#8217;s Tavern&lt;/a&gt; which, again, can&amp;#8217;t complain. The food was great and a good excuse to explore more of their menu.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect3&quot;&gt;
&lt;h4 id=&quot;_after_party&quot;&gt;After Party&lt;/h4&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;After dinner, I followed the group down to &lt;a href=&quot;https://burialbeer.com/pages/asheville-south-slope&quot;&gt;Burial&lt;/a&gt; at South Slope. My social battery was getting drained at this point. I&amp;#8217;ve learned that two days of social interaction is about where I start to run out of steam. I stayed for an hour or so and then made my way back to the hotel. Tried to say goodbye to those I could and, of course, Jeremy who put all of this together.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Would have liked to have talked with Jeremy more but when, you&amp;#8217;re running the entire conference, time is extremely limited.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Jeremy did an amazing job (including Mark and Joe). Definitely would like to come back in the future and hope Blue Ridge Ruby continues to gather fellow Rubyists to this location.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_asheville&quot;&gt;Asheville&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;With the conference behind, my wife and I were able to spend time together exploring the city and surrounding area. The following is a capture of the highlights.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;_city_saturday&quot;&gt;City (Saturday)&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;To kick things off, we spent the day walking all over the city. This is one of my favorite things to do when new to a city. This ended up being:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.romanticasheville.com/packsquare.htm&quot;&gt;Pack Square Park&lt;/a&gt;: Small but nice park. There&amp;#8217;s several chairs you can sit and rest in while watching folks play with their pets and get views of the governmental buildings.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://frenchbroadchocolates.com&quot;&gt;French Broad Chocolate&lt;/a&gt;: Only a short walk from Pack Square Park. Opens late but the hot dark cholocote and liquid truffle shot is well worth checking out.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.ashevillenc.gov/locations/pritchard-park/&quot;&gt;Pritchard Park&lt;/a&gt;: A bunch of booths were setup for the weekend. Picked up a deck of cards, made of up of local art, that supports the local artists (especially those hit by the flood).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.romanticasheville.com/grove_arcade.htm&quot;&gt;Grove Arcade&lt;/a&gt;: Lots of artists selling their wares, small shops, and eateries. Especially enjoyed the &lt;a href=&quot;https://grovearcade.com/restaurants/battery-park-book-exchange-champagne-bar/&quot;&gt;Battery Park Book Exchange &amp;amp; Champagne Bar&lt;/a&gt; where you can eat at while surrounded by books.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;_the_biltmore_sunday&quot;&gt;The Biltmore (Sunday)&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We rented a car and took a short drive to The Biltmore. Spent the entire day there only getting home a little before midnight as we had tickets for the &lt;a href=&quot;https://www.biltmore.com/visit/biltmore-estate/biltmore-house/&quot;&gt;House&lt;/a&gt;, &lt;a href=&quot;https://www.biltmore.com/visit/biltmore-estate/gardens-grounds/&quot;&gt;Gardens and Grounds&lt;/a&gt;, and the &lt;a href=&quot;https://www.biltmore.com/things-to-do/events/luminere/&quot;&gt;Luminere&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;If I lived in the area, I&amp;#8217;m pretty sure I&amp;#8217;d want a season pass in order to walk and read in the gardens as often as possible.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock focal_point&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;/images/articles/blue_ridge_ruby_2026/photos/flowers.png&quot; alt=&quot;Flowers&quot; width=&quot;1060&quot; height=&quot;727&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;_blue_ridge_parkway_monday&quot;&gt;Blue Ridge Parkway (Monday)&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Started the day by having brunch at the &lt;a href=&quot;https://biscuitheads.com&quot;&gt;Biscuit Head&lt;/a&gt; before driving the parkway. The place is easy to miss if not paying attention to as the sign is tucked away in a cottage like setting. Definitely worth checking out because the biscuits were fluffy and delicious. This included several kinds of jams and sauces you could spread on your biscuits as well. Would definitely go back.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;After leaving Biscuit Head, we caught the Blue Ridge Parkway and initially headed to the &lt;a href=&quot;https://www.ncarboretum.org&quot;&gt;North Carolina Arboretum&lt;/a&gt; but then headed North to the visitors center instead. It was a tough decision but had to skip since we were worried about maximizing our time on the parkway.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;At the &lt;a href=&quot;https://www.blueridgeparkway.org/poi/blue-ridge-parkway-visitor-center/&quot;&gt;Visitor&amp;#8217;s Center&lt;/a&gt; we met an astute park ranger who helped clarify what parts of the road were open and closed which allowed us to drive/hike to the top of Mount Mitchell and then turn back to cross over in to the Pisgah Forest and eventually over Mount Pisgah itself.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We spent the afternoon in the Pisgah Forest, enjoying the roads, and scenic views. We eventually caught I-40 back into Asheville to end the day.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;imageblock focal_point&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;/images/articles/blue_ridge_ruby_2026/photos/looking_glass_rock.png&quot; alt=&quot;Looking Glass Rock&quot; width=&quot;1062&quot; height=&quot;624&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;_the_river_arts_district_tuesday&quot;&gt;The River Arts District (Tuesday)&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We spent the day walking to, around, and back from the &lt;a href=&quot;https://www.romanticasheville.com/river_arts.htm&quot;&gt;River Arts District&lt;/a&gt;. As chance would have it, this was also the worst day to be in the River Arts District since this seems to be their slowest day of the week so several places were closed. I was also curious to eat a &lt;a href=&quot;https://www.hole-doughnuts.com&quot;&gt;Hole Doughnut&lt;/a&gt; but they were closed as well. Alas.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This area of the city was hit hard by the hurricane that struct about a year prior where whole houses were seen floating down the river. Hurricanes don&amp;#8217;t normally travel this far inland but climate change is lashing out in extreme ways.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;While enjoying the district, viewing creative works of art, and collecting a bunch of ideas we ended up assembling a list of our favorites:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://philipdeangeloart.com&quot;&gt;Philip DeAngelo&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.cindylouchenard.com&quot;&gt;Cindy Lou Chenard&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ipsydoodles.com&quot;&gt;Christine VanCott&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.lucycobos.com&quot;&gt;Lucy Cobos&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.luzaderstudios.com&quot;&gt;Luzader Studios&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://theczartgallery.com&quot;&gt;Rob Czar&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.raymondbyram.com&quot;&gt;Ray Byram&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.marcosmartinezartwork.com&quot;&gt;Marcos Martinez&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.suzannesaundersartwork.com&quot;&gt;Suzanne Saunders&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;_the_rhu_and_home&quot;&gt;The Rhu and Home&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This ended up being a rainy day so we spent the morning working from &lt;a href=&quot;https://the-rhu.com&quot;&gt;The Rhu&lt;/a&gt;, popping over to the &lt;a href=&quot;https://frenchbroadchocolates.com&quot;&gt;French Broad Chocolate&lt;/a&gt; one last time, and then grabbing lunch at &lt;a href=&quot;https://melaasheville.com&quot;&gt;Mela&lt;/a&gt; (I&amp;#8217;d eat Indian or Ethiopian every day of the year if possible) before catching a cab and our flight back home.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Definitely glad I was accepted to speak, &lt;a href=&quot;https://trmnl.com&quot;&gt;TRMNL&lt;/a&gt; to send me out, and have time to adventure with my wife afterwards.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Asheville is definitely has a lot to offer into terms of food and outdoor adventure. It&amp;#8217;s not, sadly, a tech hub as I was hoping. The city&amp;#8217;s major industry seems to be tourism and culinary. As for where some of the local Rubyists are, they seem to be farther out such as Greenville, South Carolina; Charlotte, North Carolina; Raleigh, North Carolina; and so forth.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;A part of me is searching for city which has a lot of Rubyist that are pushing the boundary of what&amp;#8217;s possible who are not Rails engineers. A lofty dream but only need one or two to start building something great.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Anyway, if you get a chance to speak or attend Blue Ridge Ruby, don&amp;#8217;t hesitate. The single track days plus having lots of time to hang out and talk to folks is a great experience. Highly recommend.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
    <id>https://alchemists.io/articles/blue_ridge_ruby_2026</id>
    <link href="https://alchemists.io/articles/blue_ridge_ruby_2026"/>
    <published>2026-05-15T15:12:02+00:00</published>
    <rights>2026</rights>
    <title>Blue Ridge Ruby 2026</title>
    <updated>2026-05-15T18:46:26+00:00</updated>
    <dc:date>2026-05-15T18:46:26+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>Brooke Kuhlmann</name>
      <uri>https://alchemists.io/team/brooke_kuhlmann</uri>
    </author>
    <category term="ruby"
      label="Ruby"/>
    <content type="html">&lt;div class=&quot;imageblock focal_point&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;/images/articles/ruby_classes/cover.png&quot; alt=&quot;Cover&quot; width=&quot;497&quot; height=&quot;240&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Ruby Classes&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Classes are at the heart of Object-Oriented Programming and &lt;a href=&quot;https://www.ruby-lang.org&quot;&gt;Ruby&lt;/a&gt; makes them effortless to use. They provide the following benefits:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Composition&lt;/strong&gt;: Allow you to leverage the Dependency Inversion Principle (DIP)&amp;#8201;&amp;#8212;&amp;#8201;the &quot;D&quot; in SOLID design&amp;#8201;&amp;#8212;&amp;#8201;by injecting dependencies via your constructor or methods. Prefer composition over inheritance because this maximizes your ability to reuse your components to build robust architectures.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Inheritance&lt;/strong&gt;: Allow you to subclass and inherit behavior from a superclass and/or use multiple inheritance to enhance your class with common functionality.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Encapsulation&lt;/strong&gt;: Allows you to wrap data, state, and/or behavior (methods) within a single object. Additionally, you gain the ability to abstract common functionality without having to understand the intricate details.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Modularity&lt;/strong&gt;: Promote module design which leads to improved testability and maintainability since your object is highly focused on doing one thing well. In other words, the Single Responsibility Principle (SRP) which is the &quot;S&quot; in SOLID design.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reusability&lt;/strong&gt;: Allow you to make multiple instances of your class without having to repeat yourself in terms of constantly writing the same implementation over and over.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Polymorphism&lt;/strong&gt;: Allow you to use objects from different classes which can respond to the same method calls in their own appropriate ways, making your code more flexible and extensible (a.k.a. Duck Typing).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;ll discuss the above in more detail but, before we do that, it&amp;#8217;s important we take a moment to talk about the categories that classes fall into.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot; class=&quot;title&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#_categories&quot;&gt;Categories&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_basic_versus_functional&quot;&gt;Basic versus Functional&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_composition&quot;&gt;Composition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_inheritance&quot;&gt;Inheritance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_encapsulation&quot;&gt;Encapsulation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_modularity&quot;&gt;Modularity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_reusability&quot;&gt;Reusability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_avoidances&quot;&gt;Avoidances&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_categories&quot;&gt;Categories&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;At a high level, there are three main categories for classes. These are not official categories are useful for this discussion. They are:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Basic&lt;/strong&gt;: Any object created via the &lt;code&gt;class&lt;/code&gt; keyword (or anonymously via &lt;code&gt;Class.new&lt;/code&gt;), which is one of only a handful of &lt;a href=&quot;/articles/ruby_keywords&quot;&gt;Ruby Keywords&lt;/a&gt;, to implement.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Whole Value&lt;/strong&gt;: Created via &lt;code&gt;Data&lt;/code&gt; (see &lt;a href=&quot;/articles/ruby_data&quot;&gt;Ruby Data&lt;/a&gt;) or &lt;code&gt;Struct&lt;/code&gt; (see &lt;a href=&quot;/articles/ruby_structs&quot;&gt;Ruby Structs&lt;/a&gt;). You can also use the &lt;a href=&quot;/projects/wholeable&quot;&gt;Wholeable&lt;/a&gt; gem for situations in which you can&amp;#8217;t quite use a &lt;code&gt;Data&lt;/code&gt; or a &lt;code&gt;Struct&lt;/code&gt; but want to make your existing class a whole value object instead.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Functional&lt;/strong&gt;: Created via the &lt;code&gt;class&lt;/code&gt; keyword but the difference&amp;#8201;&amp;#8212;&amp;#8201;and this is important&amp;#8201;&amp;#8212;&amp;#8201;is that they adhere to the &lt;a href=&quot;/articles/command_pattern&quot;&gt;Command Pattern&lt;/a&gt; by implementing the &lt;code&gt;#call&lt;/code&gt; method. This humble method unlocks the world of functional programming in Ruby by allowing you to blend objects and functions together. This is also key to &lt;a href=&quot;/articles/ruby_function_composition&quot;&gt;Function Composition&lt;/a&gt; where you can compose classes along with procs, lambdas, and methods to maximum effect.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;ℹ️ Technically, the Functional category is a specific pattern within the Basic category. You could say the same for Whole Values since they are a subclasses of &lt;code&gt;Object&lt;/code&gt; except they do behave differently than basic classes which makes them important to distinguish.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Sadly, few Ruby engineers know about the latter two categories and tend know about basic classes more. Nothing wrong with basic classes as you can go far with them. The problem is that they are often abused in the most grotesque ways. Glad you&amp;#8217;re here because we can do better!&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Much has been written about whole value objects already so we won&amp;#8217;t rehash them in this discussion in order to remain focused on comparing/contrasting basic versus functional classes instead.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_basic_versus_functional&quot;&gt;Basic versus Functional&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Basic classes are vast and encapsulate a wide range of creational, structural, and behavior patterns such as adapters, clients, presenters, factories, observers, visitors, and so much more. For example, consider the following presenter:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;forwardable&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Demo&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Place&lt;/span&gt;
    &lt;span class=&quot;kp&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Forwardable&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;delegate&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%i[name created_at]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:record&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;record&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@record&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;record&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;capitalize&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;human_created_at&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strftime&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;%B %d, %Y (%A) at %H:%M %Z&quot;&lt;/span&gt;

    &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

    &lt;span class=&quot;nb&quot;&gt;attr_reader&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:record&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The above wraps a &lt;code&gt;record&lt;/code&gt; object with additional behavior (i.e. decorations) for presentation purposes. You need this when you want to keep your data and presentation layers separate in your application. This allows you to avoid overloading your &lt;code&gt;record&lt;/code&gt; with presentation logic that is only used by your views.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Functional classes, on the other hand, adhere to the &lt;a href=&quot;/articles/command_pattern&quot;&gt;Command Pattern&lt;/a&gt; by implementing the &lt;code&gt;#call&lt;/code&gt; method. Example:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;http&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Demo&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Pinger&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;http: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;kernel: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Kernel&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@http&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@kernel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kernel&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kernel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;puts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Site is up.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kernel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;puts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Site is down.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

    &lt;span class=&quot;nb&quot;&gt;attr_reader&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:kernel&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The key difference between these two objects is that the former has several methods which expose data and/or behavior while the latter is purely functional in that you message a single public method, &lt;code&gt;call&lt;/code&gt;, and get a result. No mutations or side effects. Only data in, data out.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The above examples are definitely not exhaustive and only represent a small subset of what is possible. The goal isn&amp;#8217;t to explain all patterns and styles, only for you to study the &lt;em&gt;shape&lt;/em&gt; of these objects. We can now compare/contrast by walking through the benefits that classes provide.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_composition&quot;&gt;Composition&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;With both examples, you see an adherence to DIP where all dependencies are injected via the constructor.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;With the presenter, a &lt;code&gt;record&lt;/code&gt; is required because you can&amp;#8217;t build a presenter with nothing to wrap. That would defeat the purpose.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;With the command, default dependencies are injected so you can construct an instance with minimal effort. This is important because you want to create an instance you can reuse for different URIs to ping. This doesn&amp;#8217;t stop you from injecting &lt;code&gt;http&lt;/code&gt; and &lt;code&gt;kernel&lt;/code&gt; dependencies with different behavior either. This is most apparent when writing specs. For example, we can use &lt;a href=&quot;https://rspec.info&quot;&gt;RSpec&lt;/a&gt; to stub &lt;code&gt;HTTP&lt;/code&gt; and spy on &lt;code&gt;Kernal&lt;/code&gt; like so:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Pinger&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:pinger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;described_class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;kernel: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;class_double&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;HTTP&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:kernel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;class_spy&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Kernel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;#call&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;prints site is up&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;allow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;and_return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kernel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;have_received&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:puts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Site is up.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;prints site is down&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;allow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;and_return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kernel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;have_received&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:puts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Site is down.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Notice, by allowing dependencies to be injected, we can inject a spy for testing purposes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;A common mistake is hard coding dependencies directly within the body of the class. This leads to a &lt;a href=&quot;/articles/connascence#_name&quot;&gt;Connascence of Name (CoN)&lt;/a&gt; situation where the class becomes brittle and hard to test because you have to keep altering the implementation to adjust behavior. With dependency injection, you avoid CoN while providing room for different dependencies to alter behavior.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_inheritance&quot;&gt;Inheritance&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Inheritance is a double edged sword because subclassing, or worse, creating a deep hierarchy of subclasses, leads to highly coupled and brittle code. Same goes for multiple inheritance where too much behavior is injected into a class thus breaking SRP where the object has confusing behavior that is hard to test.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;With the presenter, multiple inheritance is used to &lt;code&gt;delegate&lt;/code&gt; messages to the injected &lt;code&gt;record&lt;/code&gt; via the &lt;code&gt;Forwardable&lt;/code&gt; module. This is a good use of multiple inheritance because we are &lt;em&gt;enhancing&lt;/em&gt; the object without effecting the external behavior of the object or violating SRP.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We could lean into multiple inheritance further by using the &lt;a href=&quot;/projects/initable&quot;&gt;Initable&lt;/a&gt; gem to clean up the examples as shown below:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Presenter&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;forwardable&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;initable&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Demo&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Place&lt;/span&gt;
    &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Initable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;%i[req record]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;kp&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Forwardable&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;delegate&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%i[name created_at]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:record&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;capitalize&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;human_created_at&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strftime&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;%B %d, %Y (%A) at %H:%M %Z&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;Command&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;http&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;initable&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Demo&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Pinger&lt;/span&gt;
    &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Initable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;http: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;kernel: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Kernel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success?&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kernel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;puts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Site is up.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kernel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;puts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Site is down.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Notice how the original implementations require less code by using Initable to automatically DRY up and apply the &lt;a href=&quot;/articles/barewords_pattern&quot;&gt;Barewords Pattern&lt;/a&gt; for us. With the presenter, we now have multiple inheritance via &lt;code&gt;Forwardable&lt;/code&gt; and &lt;code&gt;Initable&lt;/code&gt;. What&amp;#8217;s significant is we are not violating SRP by exposing additional &lt;em&gt;public&lt;/em&gt; behavior. Instead, we are &lt;em&gt;privately&lt;/em&gt; simplifying the implementation while writing less code! This is a good example of multiple inheritance applied appropriately.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;You can definitely take this to extremes by including, extending, and/or prepending multiple modules which leads to a form of Dissociative Identity Disorder with your objects due to confusing behavior that is hard to reason about and test. Please avoid this.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_encapsulation&quot;&gt;Encapsulation&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Both examples adhere to the &lt;a href=&quot;/articles/barewords_pattern&quot;&gt;Barewords Pattern&lt;/a&gt; by ensuring the Object API is private by default to prevent leaky abstractions. When implementing classes&amp;#8201;&amp;#8212;&amp;#8201;and this is important&amp;#8201;&amp;#8212;&amp;#8201;always default to making your attributes private by default. Only expose data when you are absolutely certain it&amp;#8217;s needed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Did you notice you can&amp;#8217;t access the &lt;code&gt;record&lt;/code&gt; directly in the presenter above? Same goes for the command where you can&amp;#8217;t access any of the injected primitives either. This is good encapsulation!&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;A properly encapsulated objects does two things:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Data&lt;/strong&gt;: In the basic example, the &lt;code&gt;record&lt;/code&gt; is the encapsulated data. In the functional example, the data is the URI passed in via the &lt;code&gt;#call&lt;/code&gt; method. The only dependencies are &lt;code&gt;HTTP&lt;/code&gt; and &lt;code&gt;Kernel&lt;/code&gt; which can be swapped out with different implementations at object initialization.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;State&lt;/strong&gt;: Only the functional example has state&amp;#8201;&amp;#8212;&amp;#8201;but it&amp;#8217;s brief&amp;#8201;&amp;#8212;&amp;#8201;in that it makes an HTTP request and, for a short moment, has a response object which contains the status. Upon subsequent requests, the response is created anew, only to check status, and then is scheduled for garbage collection after.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_modularity&quot;&gt;Modularity&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In both the presenter and command examples, both are highly modular in that they one thing well: present or perform an action. That&amp;#8217;s it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;With the presenter, yes, you have multiple methods you can interact with but they are all focused on presenting your record in manner that is visually pleasing within your views.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;With the command, you have a single Object API: &lt;code&gt;#call&lt;/code&gt;. You give it a URI and it answers with a messaging letting you know if your site is up or down. Even better, you can use &lt;a href=&quot;/articles/ruby_function_composition&quot;&gt;Function Composition&lt;/a&gt; to pipe the output of this command to other commands for further manipulation as needed. This is the beauty of functional programming because, once you have the building blocks in place, you can mix and match in any order to build more sophisticated solutions.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_reusability&quot;&gt;Reusability&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Both examples have varying degrees of reusability. With the presenter, you can wrap any record that has the same attributes without changing the original design of your presenter.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;With the command example, you can always supply a different &lt;code&gt;uri&lt;/code&gt; to the same instance since the &lt;code&gt;uri&lt;/code&gt; is what will change the most, not your instance. Even better, you can inject different dependencies to create new instances with slightly different behavior with no change to your original implementation either.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_avoidances&quot;&gt;Avoidances&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The greatest strength, and weakness, of &lt;a href=&quot;https://www.ruby-lang.org&quot;&gt;Ruby&lt;/a&gt; is extreme flexible. Ruby treats you with respect by assuming you know what you are doing. Unfortunately, many programmers don&amp;#8217;t fully grasp this&amp;#8201;&amp;#8212;&amp;#8201;or worse, are an &lt;a href=&quot;/articles/expert_beginner&quot;&gt;Expert Beginner&lt;/a&gt;&amp;#8201;&amp;#8212;&amp;#8201;which turns a beautiful language into an absolute abomination to read and maintain.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;To paraphrase Uncle Ben from Spiderman: &quot;With great power comes great responsibility.&quot; Use the following to avoid common pitfalls and wield your power responsibly:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/articles/ruby_antipatterns&quot;&gt;Ruby Antipatterns&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/articles/rspec_antipatterns&quot;&gt;RSpec Antipatterns&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The above will vastly improve the quality of your code and reduce your maintenance burden to boot.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;You&amp;#8217;ve learned the power of Ruby classes and the potentially they can bring to your design and architecture. This includes the power of functional programming in Ruby by blending object-oriented with functional programming for maximum effect.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Be diligent in your design, apply the right objects when appropriate, and you&amp;#8217;ll have few regrets. Enjoy!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
    <id>https://alchemists.io/articles/ruby_classes</id>
    <link href="https://alchemists.io/articles/ruby_classes"/>
    <published>2026-05-01T10:24:09+00:00</published>
    <rights>2026</rights>
    <title>Ruby Classes</title>
    <updated>2026-05-01T10:23:01+00:00</updated>
    <dc:date>2026-05-01T10:23:01+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>Brooke Kuhlmann</name>
      <uri>https://alchemists.io/team/brooke_kuhlmann</uri>
    </author>
    <category term="htmx"
      label="htmx"/>
    <category term="ruby"
      label="Ruby"/>
    <category term="hanami"
      label="Hanami"/>
    <content type="html">&lt;div class=&quot;imageblock focal_point&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;a class=&quot;image&quot; href=&quot;https://htmx.org&quot;&gt;&lt;img src=&quot;/images/articles/htmx_configuration/cover.png&quot; alt=&quot;Cover&quot; width=&quot;800&quot; height=&quot;244&quot;&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;htmx Configuration&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Often, you&amp;#8217;ll want to customize the default &lt;a href=&quot;https://htmx.org&quot;&gt;htmx&lt;/a&gt; configuration. There are a couple ways you can do this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Globally.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Per page.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;ll discuss each including how you can make short order of these configuration changes in your &lt;a href=&quot;https://www.ruby-lang.org&quot;&gt;Ruby&lt;/a&gt; applications. For the purposes of this discussion, we&amp;#8217;ll use &lt;a href=&quot;https://hanakai.org/hanami&quot;&gt;Hanami&lt;/a&gt; as our web application but what you see here can be applied to your specific web stack.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;With the above in mind, let&amp;#8217;s get started by first setting up our htmx provider.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot; class=&quot;title&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#_provider&quot;&gt;Provider&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_context&quot;&gt;Context&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_layout&quot;&gt;Layout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_provider&quot;&gt;Provider&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As documented in &lt;a href=&quot;/articles/hanami_containers&quot;&gt;Hanami Containers&lt;/a&gt;, the best way to have access to the &lt;a href=&quot;/projects/htmx&quot;&gt;HTMX&lt;/a&gt; gem is to implement a corresponding provider. Here&amp;#8217;s what this looks like:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# demo/config/providers/htmx.rb&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Hanami&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;register_provider&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:htmx&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;prepare&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;htmx&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;register&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:htmx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;HTMX&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;register&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:htmx_defaults&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
             &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;allowScriptTags&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;defaultSwapStyle&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;outerHTML&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;freeze&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This allows you to quickly inject &lt;code&gt;htmx&lt;/code&gt; as a dependency while providing a default configuration which can be customized further if desired. There&amp;#8217;s a lot of &lt;a href=&quot;https://htmx.org/reference/#config&quot;&gt;htmx Configuration&lt;/a&gt; settings you can tweak but what is shown above is a good starting point for a couple reasons:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;allowScriptTags&lt;/code&gt;: Disabled as a security precaution to prevent random JavaScript being loaded and executed (especially if content is user created).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;defaultSwapStyle&lt;/code&gt;: The default swap style is &lt;code&gt;innerHTML&lt;/code&gt; which means content &lt;em&gt;within&lt;/em&gt; the body of your HTML element will be swapped. In practice, this isn&amp;#8217;t want you want as you generally want to swap out the entire element itself (including it&amp;#8217;s content) when making htmx requests.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_context&quot;&gt;Context&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;With the above provider in hand, we can now inject our htmx dependencies into our view context, via &lt;code&gt;include Deps[:htmx, :htmx_defaults]&lt;/code&gt;, as shown below:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# demo/app/views/context.rb&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# auto_register: false&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;hanami/view&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Demo&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Views&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hanami&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;View&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Context&lt;/span&gt;
      &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Deps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:htmx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:htmx_defaults&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;htmx?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;htmx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;request?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;true&quot;&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;htmx_configuration&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;content_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:htmx_merge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;htmx_defaults&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;merge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;htmx_defaults&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
                                &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_json&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;details&gt;
&lt;summary class=&quot;title&quot;&gt;Specification&lt;/summary&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The following illustrates how simple it is to test the implementation since you can use an instance of &lt;code&gt;Hanami::Action::Request&lt;/code&gt; for requests and plain old hashes for configuration merging.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# demo/spec/app/views/context_spec.rb&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;hanami_helper&quot;&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Demo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Views&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Context&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:view_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;described_class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;#htmx?&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;answers true when htmx request&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hanami&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;env: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;HTTP_HX_REQUEST&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;params: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;view_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;instance_variable_set&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:@request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;view_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;htmx?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;be&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;answers false when not a htmx HTTP request&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hanami&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;env: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;params: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;view_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;instance_variable_set&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:@request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;view_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;htmx?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;be&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;#htmx_configuration&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;answers default configuration&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;view_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;htmx_configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;allowScriptTags&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;defaultSwapStyle&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;outerHTML&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_json&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;answers custom configuration&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;view_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;content_for&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:htmx_merge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;defaultSwapStyle&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;innerHTML&quot;&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;view_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;htmx_configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;allowScriptTags&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;defaultSwapStyle&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;innerHTML&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_json&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The &lt;code&gt;#htmx?&lt;/code&gt; method leverages the &lt;a href=&quot;/projects/htmx&quot;&gt;HTMX&lt;/a&gt; gem to check if the current HTTP request&amp;#8201;&amp;#8212;&amp;#8201;especially handy within &lt;a href=&quot;/articles/hanami_actions&quot;&gt;Hanami Actions&lt;/a&gt;&amp;#8201;&amp;#8212;&amp;#8201;is an htmx request or not. This allows you have access to this information within your &lt;a href=&quot;/articles/hanami_views&quot;&gt;Hanami Views&lt;/a&gt;. Quite powerful.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The &lt;code&gt;#htmx_configuration&lt;/code&gt; method leverages the &lt;code&gt;htmx_defaults&lt;/code&gt; dependency by merging it with whatever is provided by the &lt;code&gt;htmx_merge&lt;/code&gt; hash and then immediately converting this into JSON which is the format that htmx needs in order to configure itself. This allows you to use the &lt;code&gt;content_for&lt;/code&gt; &lt;a href=&quot;/articles/hanami_views&quot;&gt;Hanami Views&lt;/a&gt; helper at the top of any template to alter htmx&amp;#8217;s configuration for specific pages. For example, let&amp;#8217;s say you need to alter the swap style for a specific page. You&amp;#8217;d only need to add the following to the top of one of your HTML templates:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;%=&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;content_for&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;:htmx_merge&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;defaultSwapStyle:&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;innerHTML&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&quot;}&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;That&amp;#8217;s a lot of power for a single line of code.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_layout&quot;&gt;Layout&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;With all of the above in place, the only thing left to do is configure your application layout (i.e. &lt;code&gt;app.html.erb&lt;/code&gt;) to load your custom htmx configuration for all pages:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- demo/app/templates/layouts/app.html.erb --&amp;gt;&lt;/span&gt;

&lt;span class=&quot;cp&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;lang=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;en&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- Truncated for brevity... --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;%=&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;tag.meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;htmx-config&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&quot;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;content:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;htmx_configuration&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- Truncated for brevity... --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- Truncated for brevity... --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The above will ensure htmx is properly configured for every page using your application layout (unless overwritten by the &lt;code&gt;content_for&lt;/code&gt; helper with the &lt;code&gt;htmx_merge&lt;/code&gt; key). 🎉&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;You&amp;#8217;ve learned how to configure &lt;a href=&quot;https://htmx.org&quot;&gt;htmx&lt;/a&gt; that best suites the needs of your web application either globally (default) or per page (specific). Even better, you&amp;#8217;ve learned how you can leverage the &lt;a href=&quot;/projects/htmx&quot;&gt;HTMX&lt;/a&gt; gem to check if you&amp;#8217;re dealing with htmx requests which allows you to provide specialized behavior for htmx and non-htmx requests. Finally, you&amp;#8217;ve learned how you can apply all of these configuration change via your application layout for federated behavior.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Enjoy!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
    <id>https://alchemists.io/articles/htmx_configuration</id>
    <link href="https://alchemists.io/articles/htmx_configuration"/>
    <published>2026-04-01T12:01:25+00:00</published>
    <rights>2026</rights>
    <title>htmx Configuration</title>
    <updated>2026-05-13T20:41:55+00:00</updated>
    <dc:date>2026-05-13T20:41:55+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>Brooke Kuhlmann</name>
      <uri>https://alchemists.io/team/brooke_kuhlmann</uri>
    </author>
    <category term="htmx"
      label="htmx"/>
    <category term="ruby"
      label="Ruby"/>
    <category term="hanami"
      label="Hanami"/>
    <content type="html">&lt;div class=&quot;imageblock focal_point&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;a class=&quot;image&quot; href=&quot;https://htmx.org&quot;&gt;&lt;img src=&quot;/images/articles/htmx_infinite_scroll/cover.png&quot; alt=&quot;Cover&quot; width=&quot;800&quot; height=&quot;244&quot;&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;htmx Infinite Scroll&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Pagination is always a feature you have to tackle in web application development in one form or another. A common approach is to use a gem, like &lt;a href=&quot;https://github.com/ddnexus/pagy&quot;&gt;Pagy&lt;/a&gt;, to provide the following capabilities:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Load records in batches (a.k.a. pages).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Previous and next links.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Numbered links to specific pages.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bookmarkable links complete with browser history.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Definitely useful to decrease server load since you&amp;#8217;re only loading a small subset of records per request. The problem with this approach is:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Most people bail after reading through the first couple of pages and not finding what they want.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Includes a lot visual information, design-wise, that not only consumes real-estate within your UI but is seldom utilized.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We can do better. With infinite scrolling, we can provide a better user experience through a killer combination of search coupled with endless scroll. This allows folks to:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Endlessly scroll until they find what they want or bail when coming up empty after a page or two.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Search plus scroll to further refine the results.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s explore building a gallery of photos for browsing and searching by using an API client to obtain images from a remote resource. We&amp;#8217;ll do this by using &lt;a href=&quot;https://htmx.org&quot;&gt;htmx&lt;/a&gt; to provide an infinite scrolling experience.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot; class=&quot;title&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#_setup&quot;&gt;Setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_implementation&quot;&gt;Implementation&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#_action&quot;&gt;Action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_view&quot;&gt;View&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_template&quot;&gt;Template&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_setup&quot;&gt;Setup&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;To implement this functionality, add the &lt;a href=&quot;https://htmx.org&quot;&gt;htmx&lt;/a&gt; library and the &lt;a href=&quot;/projects/htmx&quot;&gt;HTMX&lt;/a&gt; gem to your stack. Both are compatible with whatever &lt;a href=&quot;https://www.ruby-lang.org&quot;&gt;Ruby&lt;/a&gt; web framework you decide to use.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Additionally, the htmx team has an &lt;a href=&quot;https://htmx.org/examples/infinite-scroll/&quot;&gt;example&lt;/a&gt; if you need a visual primer. When you inspect the example&amp;#8217;s DOM, you&amp;#8217;ll see the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;tr&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;hx-get=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/contacts/?page=2&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;hx-trigger=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;revealed&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;hx-swap=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;afterend&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data-hx-revealed=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;td&amp;gt;&lt;/span&gt;Agent Smith&lt;span class=&quot;nt&quot;&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;td&amp;gt;&lt;/span&gt;void29@null.org&lt;span class=&quot;nt&quot;&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;td&amp;gt;&lt;/span&gt;A0GG98CEAC&lt;span class=&quot;nt&quot;&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This breaks down as follows:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;hx-get&lt;/code&gt;: Contains the HTTP request for the next page of information.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;hx-trigger&lt;/code&gt;: Triggers the GET request when the element is revealed in the viewport.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;hx-swap&lt;/code&gt;: Describes swap behavior. In this case, new content from the HTTP response will be swapped in &lt;em&gt;after&lt;/em&gt; the target element (i.e. the &lt;code&gt;tr&lt;/code&gt; you see above).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;That&amp;#8217;s a lot of power for only a few lines of HTML!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_implementation&quot;&gt;Implementation&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Our implementation will consist of a Hanami action, view, and template. For htmx, you can load either via a CDN, Node package, or an import map. The following sections will focus on the Ruby code.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;_action&quot;&gt;Action&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now that you have an idea of the desired functionality on the frontend, let&amp;#8217;s discuss how to implement this on the backend. For demonstration purposes, we&amp;#8217;ll use a &lt;a href=&quot;/articles/hanami_actions&quot;&gt;Hanami Actions&lt;/a&gt; index action. If using a different framework, the Ruby code should be trivial to adapt for your own implementation. Here&amp;#8217;s the entire action:&lt;/p&gt;
&lt;/div&gt;
&lt;details&gt;
&lt;summary class=&quot;title&quot;&gt;Implementation&lt;/summary&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;initable&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Demo&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Actions&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Gallery&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hanami&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Action&lt;/span&gt;
        &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Deps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:htmx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Initable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;model: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Demo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Models&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Photo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;optional&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filled&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:string&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;optional&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filled&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:integer&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handle&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;parameters&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;params&lt;/span&gt;

          &lt;span class=&quot;nb&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;either&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;photo&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;photo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
                                  &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render_error&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

        &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;load&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parameters&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parameters&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;page: &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;photos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;search: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;query: &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;photos&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;search: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;page: &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;photos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;photos&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;photo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;page&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;values_at&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:page&lt;/span&gt;

          &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;htmx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;request?&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;htmx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;response!&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                           &lt;span class=&quot;ss&quot;&gt;push_url: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;routes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:gallery&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;photo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;layout: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;photo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;page:
          &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;render_error&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;flash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:alert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;photo: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Let&amp;#8217;s explore each section/method of the action since this is worth explaining further. First, we start by injecting the &lt;code&gt;htmx&lt;/code&gt;, API &lt;code&gt;client&lt;/code&gt;, and &lt;code&gt;model&lt;/code&gt; dependencies. We&amp;#8217;ll use an API client to scroll through photos which has pagination built-in. We don&amp;#8217;t need to understand the client implementation, only the interface. Here&amp;#8217;s our dependencies:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Deps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:htmx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Initable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;model: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Demo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Models&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Photo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;💡 The &lt;a href=&quot;/projects/initable&quot;&gt;Initable&lt;/a&gt; gem is used to inject the &lt;code&gt;model&lt;/code&gt; dependency without having to write an initializer since we only need access to the constant (we&amp;#8217;ll make use of the &lt;code&gt;model&lt;/code&gt;, later, when rendering errors).&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Due to our index action being a HTTP GET request we can allow the following optional parameters and type casting:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;optional&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filled&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:string&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;optional&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;filled&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:integer&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The &lt;code&gt;query&lt;/code&gt; parameter allows us to search for specific records while the &lt;code&gt;page&lt;/code&gt; parameter limits the search results by page. Now we can &lt;em&gt;handle&lt;/em&gt; the request and response:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handle&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;parameters&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;params&lt;/span&gt;

  &lt;span class=&quot;nb&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;either&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;photo&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;photo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
                          &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render_error&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The API client answers a monad (i.e. &lt;a href=&quot;https://dry-rb.org/gems/dry-monads&quot;&gt;Dry Monads&lt;/a&gt;), so we&amp;#8217;ll &lt;em&gt;either&lt;/em&gt; have a &lt;code&gt;Success&lt;/code&gt; or &lt;code&gt;Failure&lt;/code&gt; which is an elegant way to handle the inevitable fork in the road when making a HTTP request that may pass or fail.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Next, we can leverage the power of &lt;a href=&quot;/articles/ruby_pattern_matching&quot;&gt;Ruby Pattern Matching&lt;/a&gt; to determine what request we&amp;#8217;ll make of our API client (ℹ️ All methods are private from this point forward):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;load&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parameters&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parameters&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;page: &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;photos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;search: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;query: &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;photos&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;search: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;page: &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;photos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;photos&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Notice how pattern matching makes short work of how we construct our API request based on what parameters are supplied. The result of any of these requests will answer a monad that is either a &lt;code&gt;Success&lt;/code&gt; or &lt;code&gt;Failure&lt;/code&gt;. If you refer to the &lt;code&gt;#handle&lt;/code&gt; method again, this means we&amp;#8217;ll either render the HTML response or an error as implemented via these private methods. Example:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;photo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;page&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;values_at&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:page&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;htmx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;request?&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;htmx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;response!&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                   &lt;span class=&quot;ss&quot;&gt;push_url: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;routes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:gallery&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;photo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;layout: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;photo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;page:
  &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;render_error&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;flash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:alert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;photo: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This flow breaks down as follows:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Success&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If a htmx request, add the appropriate headers (especially the &lt;code&gt;push_url&lt;/code&gt; so browser history is preserved) and render without any layout information because you only want the HTML fragment&amp;#8201;&amp;#8212;&amp;#8201;not the entire page&amp;#8201;&amp;#8212;&amp;#8201;in order to keep your response as small as possible.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Otherwise, when not a htmx request, render the full page as you&amp;#8217;d normally do.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Failure&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Render the page with a flash message as to what went wrong along with an empty photo (i.e. no results found). This is made possible by the &lt;code&gt;model&lt;/code&gt; dependency we injected earlier by sending the &lt;code&gt;.empty&lt;/code&gt; message which answers an empty record which leverages the power of the Null Object Pattern.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;_view&quot;&gt;View&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;For the view, we only need to expose the &lt;code&gt;photo&lt;/code&gt;, &lt;code&gt;query&lt;/code&gt;, and &lt;code&gt;page&lt;/code&gt; to the template. This is done as follows:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Demo&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Views&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Gallery&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hanami&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;View&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;expose&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:photo&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;expose&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;decorate: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;expose&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;decorate: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;By default, Hanami will decorate all parts but for the &lt;code&gt;query&lt;/code&gt; and &lt;code&gt;page&lt;/code&gt; we only need the primitives.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;_template&quot;&gt;Template&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;For the template, we only need to iterate over the photos and render each. Only when there are no photos do we need to display that none were found as shown below:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;% if &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;photo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;any?&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%&amp;gt;
  &amp;lt;ul&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;% photo.data.each &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%&amp;gt;
      &amp;lt;%= render :item, item: %&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;% end &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;%&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;% if &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;photo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;more?&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%&amp;gt;
      &amp;lt;%= tag.li **HTMX[
                   get: routes.path(:gallery, page: photo.meta.next_page, query:),
                   select: &quot;.card&quot;,
                   trigger: &quot;revealed&quot;,
                   swap: &quot;this&quot;,
                   push_url: true
                 ] do %&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;no&quot;&gt;Loading&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;% end &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;% end &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/ul&amp;gt;
&amp;lt;% else %&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;%= render &quot;shared/em&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pty&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;, message: &quot;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;No&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;photos&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;found&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; %&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;% end %&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;With the above we leverage the &lt;a href=&quot;/projects/htmx&quot;&gt;HTMX&lt;/a&gt; gem to build the htmx attributes for us. Additionally, use of the htmx &lt;code&gt;select&lt;/code&gt;, &lt;code&gt;swap&lt;/code&gt; attributes allow you to &lt;em&gt;only&lt;/em&gt; select &lt;code&gt;card&lt;/code&gt; element in the HTTP response then immediately swap the current element (i.e. &lt;code&gt;this&lt;/code&gt;) with the new element. This not only keeps your DOM clean but ensures you are always using list items because the HTML specification doesn&amp;#8217;t allow any other HTML element within a ordered or unordered list. To illustrate further, here&amp;#8217;s what the resulting HTML looks like:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;li&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;card&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;...&lt;span class=&quot;nt&quot;&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;li&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;2&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;card&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;...&lt;span class=&quot;nt&quot;&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;li&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;3&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;card&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;...&lt;span class=&quot;nt&quot;&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;li&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;card&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;hx-get=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/gallery?page=2&amp;amp;query=landscapes&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;hx-select=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;.card&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;hx-trigger=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;revealed&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;hx-swap=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;this&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;hx-push-url=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    Loading...
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;This means you can scroll down the page&amp;#8201;&amp;#8212;&amp;#8201;and when the &lt;code&gt;li&lt;/code&gt; element with the htmx attributes is &lt;em&gt;revealed&lt;/em&gt;&amp;#8201;&amp;#8212;&amp;#8201;a new HTTP request is made to replace that element and pull in new data ending with another &lt;code&gt;li&lt;/code&gt; element with a &lt;code&gt;hx-get&lt;/code&gt; request for the next page.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;You also get a nice &quot;Loading&amp;#8230;&amp;#8203;&quot; message while waiting for the HTTP request to complete. For bonus points, you could swap out the text message with a CSS loader class to enhance the user experience further.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Reducing the use of JavaScript within your application is a good in terms speed, reduced page size, and reduced complexity in your application. With htmx you are able to focus on HTML which is much easier to write and test. More importantly, you get all of the benefits of pagination, browser history, and a cleaner user interface. Enjoy!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
    <id>https://alchemists.io/articles/htmx_infinite_scroll</id>
    <link href="https://alchemists.io/articles/htmx_infinite_scroll"/>
    <published>2026-03-01T13:20:51+00:00</published>
    <rights>2026</rights>
    <title>htmx Infinite Scroll</title>
    <updated>2026-03-01T21:50:08+00:00</updated>
    <dc:date>2026-03-01T21:50:08+00:00</dc:date>
  </entry>
  <entry>
    <author>
      <name>Brooke Kuhlmann</name>
      <uri>https://alchemists.io/team/brooke_kuhlmann</uri>
    </author>
    <category term="ruby"
      label="Ruby"/>
    <category term="hanami"
      label="Hanami"/>
    <content type="html">&lt;div class=&quot;imageblock focal_point&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;img src=&quot;/images/articles/hanami_with_rodauth/cover.png&quot; alt=&quot;Cover&quot; width=&quot;200&quot; height=&quot;204&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;title&quot;&gt;Hanami with Rodauth&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Security is an important part of web application engineering and a good solution for implementing authentication within your application, both in terms of UI and API, is &lt;a href=&quot;https://rodauth.jeremyevans.net/index.html&quot;&gt;Rodauth&lt;/a&gt;. That said, Rodauth does come with a learning curve. The following will walk you through setting up Rodauth in your &lt;a href=&quot;https://hanakai.org/hanami&quot;&gt;Hanami&lt;/a&gt; application because, unless you are a security expert, you won&amp;#8217;t want to do this yourself.&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&quot;toc&quot; class=&quot;toc&quot;&gt;
&lt;div id=&quot;toctitle&quot; class=&quot;title&quot;&gt;Table of Contents&lt;/div&gt;
&lt;ul class=&quot;sectlevel1&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#_setup&quot;&gt;Setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_features&quot;&gt;Features&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_database&quot;&gt;Database&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_slice&quot;&gt;Slice&lt;/a&gt;
&lt;ul class=&quot;sectlevel2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#_feature&quot;&gt;Feature&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_middleware&quot;&gt;Middleware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_view_context&quot;&gt;View Context&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_routes&quot;&gt;Routes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_layoutstemplates&quot;&gt;Layouts/Templates&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_actions&quot;&gt;Actions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_rspec&quot;&gt;RSpec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_tools&quot;&gt;Tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#_conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_setup&quot;&gt;Setup&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;To get started, add the following gems to your application:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;bundle&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bcrypt&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jwt&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rodauth&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Here&amp;#8217;s what each provides:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bcrypt-ruby/bcrypt-ruby&quot;&gt;Bcrypt&lt;/a&gt;: Necessary for authentication, passwords, and encryption in general.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/jwt/ruby-jwt&quot;&gt;JWT&lt;/a&gt;: Necessary for API authentication.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://rodauth.jeremyevans.net/index.html&quot;&gt;Rodauth&lt;/a&gt;: Necessary to implement authentication for both UI and API purposes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;We&amp;#8217;ll use these gems to leverage the Rodauth features.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_features&quot;&gt;Features&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;While there are plenty of features you can enable, we&amp;#8217;ll focus on the following in the order listed as a minimum to get you started:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://rodauth.jeremyevans.net/rdoc/files/doc/base_rdoc.html&quot;&gt;Base&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://rodauth.jeremyevans.net/rdoc/files/doc/active_sessions_rdoc.html&quot;&gt;Active Sessions&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://rodauth.jeremyevans.net/rdoc/files/doc/audit_logging_rdoc.html&quot;&gt;Audit Logging&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://rodauth.jeremyevans.net/rdoc/files/doc/jwt_refresh_rdoc.html&quot;&gt;JWT Refresh&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://rodauth.jeremyevans.net/rdoc/files/doc/recovery_codes_rdoc.html&quot;&gt;Recovery Codes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://rodauth.jeremyevans.net/rdoc/files/doc/remember_rdoc.html&quot;&gt;Remember&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Only the &lt;em&gt;Base&lt;/em&gt; feature is automatic and provided for you by default. The rest you must manually add. You can definitely add or subtract from this list, based on your needs, but this is the feature set we&amp;#8217;ll be working through together.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_database&quot;&gt;Database&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;You&amp;#8217;ll want to spend the most time thinking through which Rodauth &lt;a href=&quot;https://rodauth.jeremyevans.net/documentation.html&quot;&gt;features&lt;/a&gt; you want to support and configure. Once you&amp;#8217;ve mapped out your desired feature set, it&amp;#8217;s helpful to add &lt;code&gt;# Feature: &amp;lt;feature&amp;gt;&lt;/code&gt; code comments so you have a direct reference to the feature associated with each table you are creating. This same code comment can be reused when adding the middleware necessary for enabling and configuring this feature set (more on this soon). Here&amp;#8217;s the associated database migrations based on the previously discussed feature set:&lt;/p&gt;
&lt;/div&gt;
&lt;details&gt;
&lt;summary class=&quot;title&quot;&gt;User Migration&lt;/summary&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Necessary to authenticate and manage users within the system. This supports multiple Rodauth features. A code comment denotes each enabled feature.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;ROM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;migration&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;up&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:date_arithmetic&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;deadline&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;days&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;null: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Sequel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;date_add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Sequel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;days&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Feature (automatic): base&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;create_table&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_status&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;primary_key&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;unique: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;null: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;%i[id name]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Unverified&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Verified&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Closed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]])&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;create_table&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;primary_key&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;type: :Bignum&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;foreign_key&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:status_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;null: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;citext&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;null: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:jsonb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;null: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;{}&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:created_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;null: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Sequel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:updated_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;null: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Sequel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;constraint&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:valid_email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;email: &lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/^[^,;@ \r\n]+@[^,@; \r\n]+\.[^,@; \r\n]+$/&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;unique: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;where: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;status_id: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;type: :gin&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Feature: active_sessions&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;create_table&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_active_session_key&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;foreign_key&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;type: :Bignum&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:session_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:created_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;null: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Sequel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:last_use&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;null: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Sequel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;primary_key&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%i[user_id session_id]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Feature: audit_logging&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;create_table&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_authentication_audit_log&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;primary_key&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;type: :Bignum&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;foreign_key&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;null: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;type: :Bignum&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DateTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;null: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Sequel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;null: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:jsonb&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%i[user_id at]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: :audit_user_at_idx&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: :audit_at_idx&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Feature: jwt_refresh&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;create_table&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_jwt_refresh_key&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;primary_key&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;type: :Bignum&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;foreign_key&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;null: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;type: :Bignum&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;null: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:deadline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DateTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deadline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: :user_jwt_refresh_key_user_id_idx&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Feature: recovery_codes&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;create_table&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_recovery_code&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;foreign_key&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;type: :Bignum&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;primary_key&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%i[id code]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Feature: remember&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;create_table&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_remember_key&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;foreign_key&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;primary_key: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;type: :Bignum&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;null: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:deadline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DateTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deadline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%(GRANT REFERENCES ON &quot;user&quot; TO CURRENT_USER)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;GRANT SELECT, INSERT, UPDATE, DELETE ON user_status TO CURRENT_USER&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%(GRANT SELECT, INSERT, UPDATE, DELETE ON &quot;user&quot; TO CURRENT_USER)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;GRANT SELECT, INSERT, UPDATE, DELETE ON user_active_session_key TO CURRENT_USER&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;GRANT SELECT, INSERT, UPDATE, DELETE ON user_authentication_audit_log TO CURRENT_USER&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;GRANT SELECT, INSERT, UPDATE, DELETE ON user_jwt_refresh_key TO CURRENT_USER&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;GRANT SELECT, INSERT, UPDATE, DELETE ON user_recovery_code TO CURRENT_USER&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;GRANT SELECT, INSERT, UPDATE, DELETE ON user_remember_key TO CURRENT_USER&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;down&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;drop_table&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_remember_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
               &lt;span class=&quot;ss&quot;&gt;:user_recovery_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
               &lt;span class=&quot;ss&quot;&gt;:user_jwt_refresh_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
               &lt;span class=&quot;ss&quot;&gt;:user_authentication_audit_log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
               &lt;span class=&quot;ss&quot;&gt;:user_active_session_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
               &lt;span class=&quot;ss&quot;&gt;:user_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
               &lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;details&gt;
&lt;summary class=&quot;title&quot;&gt;User Hash Migration&lt;/summary&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Necessary to capture user password hashes. This is done as a separate table with different permissions in order to improve security &lt;a href=&quot;https://rodauth.jeremyevans.net/rdoc/files/doc/guides/password_column_rdoc.html&quot;&gt;as documented here&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;rodauth/migrations&quot;&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;ROM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;migration&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;table_name: :user_password_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;up&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Feature (automatic): base&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;create_table&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_password_hash&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;foreign_key&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;primary_key: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;type: :Bignum&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:password_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;null: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;no&quot;&gt;Rodauth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create_database_authentication_functions&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;REVOKE ALL ON user_password_hash FROM public&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;REVOKE ALL ON FUNCTION rodauth_get_salt(int8) FROM public&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;REVOKE ALL ON FUNCTION rodauth_valid_password_hash(int8, text) FROM public&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;GRANT INSERT, UPDATE, DELETE ON user_password_hash TO CURRENT_USER&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;GRANT SELECT(id) ON user_password_hash TO CURRENT_USER&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;GRANT EXECUTE ON FUNCTION rodauth_get_salt(int8) TO CURRENT_USER&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;GRANT EXECUTE ON FUNCTION rodauth_valid_password_hash(int8, text) TO CURRENT_USER&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;down&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rodauth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;drop_database_authentication_functions&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;One of the most important aspects of the above code is that all tables are prefixed as &lt;code&gt;user&lt;/code&gt; tables. This is because, by default, Rodauth assumes &lt;code&gt;account&lt;/code&gt; prefixes. The reason for making these user based tables instead of account tables is because this allows you to scale to more sophisticated user/account management in terms of level of access. For example, this opens up the following possibilities:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Level 1&lt;/strong&gt; (users): Basic user login/logout and resource access.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Level 2&lt;/strong&gt; (accounts): The unit of ownership in terms of system resources for which a user can belong to an account for access.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Level 3&lt;/strong&gt; (memberships): The many to many relationship in which a user can belong to multiple accounts and an account can have multiple users.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Level 4&lt;/strong&gt; (roles): The roles (and permissions) associated with the account.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Level 5&lt;/strong&gt; (organizations): The ultimate super account (enterprise level) in which you group multiple accounts under a single organization.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;For the purposes of this discussion, we&amp;#8217;ll only focus on Level 1.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_slice&quot;&gt;Slice&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Due to Rodauth needing middleware; feature configuration; and multiple templates, it&amp;#8217;s a good idea to encapsulate all of this functionality within an authentication slice. Here&amp;#8217;s what this looks like structurally:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;slices/authentication
├── assets
│   ├── css
│   │   └── portal.css
│   └── js
│       └── app.js
├── db
│   ├── relation.rb
│   ├── repository.rb
│   └── struct.rb
├── templates
│   ├── layouts
│   │   └── app.html.erb
│   ├── login.html.erb
│   ├── login_update.html.erb
│   ├── logout.html.erb
│   ├── password_update.html.erb
│   ├── register.html.erb
│   └── remember.html.erb
├── views
│   └── context.rb
├── feature.rb
├── middleware.rb
└── view.rb&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Some of the above is boilerplate when generating a slice. That said, the following sections will detail the key files and ignore the boilerplate code.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;_feature&quot;&gt;Feature&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://rodauth.jeremyevans.net/index.html&quot;&gt;Rodauth&lt;/a&gt; supports the ability to define custom features. In this case, we need a custom feature specific for bridging the gap between Hanami and Rodauth when we wire up our middleware (more on this shortly). In other words, a translation layer. In our case, we are swapping out Rodauth&amp;#8217;s view rendering with Hanami&amp;#8217;s view rendering but only if one of our Hanami templates exist. Otherwise, we fall back to Rodauth. Here&amp;#8217;s what this looks like:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# slices/authentication/feature.rb&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# auto_register: false&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;rodauth&quot;&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Rodauth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Feature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:hanami&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;auth_value_method&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:hanami_view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;layout_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view_base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;layout_path&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view_base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;layout&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view_rendering&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;scope&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;rodauth: &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;view_rendering&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;layout_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view_template?&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;view_rendering&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view_rendering&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;rodauth: &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;view_template?&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;view_rendering&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;renderer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;__send__&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:lookup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view_base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default_format&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;view_rendering&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@view_rendering&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view_base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rendering&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;format: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;view_base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default_format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                            &lt;span class=&quot;ss&quot;&gt;context: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;view_context&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;view_context&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@view_context&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;begin&lt;/span&gt;
                        &lt;span class=&quot;n&quot;&gt;action_request&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hanami&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                          &lt;span class=&quot;ss&quot;&gt;env: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                          &lt;span class=&quot;ss&quot;&gt;params: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                          &lt;span class=&quot;ss&quot;&gt;session_enabled: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
                        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

                        &lt;span class=&quot;n&quot;&gt;view_base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default_context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;request: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;action_request&lt;/span&gt;
                      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;view_base&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@view_base&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hanami_view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;_middleware&quot;&gt;Middleware&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The middleware layer allows you to inherit &lt;a href=&quot;https://rodauth.jeremyevans.net/index.html&quot;&gt;Rodauth&lt;/a&gt; functionality and configure behavior as &lt;a href=&quot;https://github.com/rack/rack&quot;&gt;Rack&lt;/a&gt; middleware for which you can wire into your application routes. Remember when we added the &lt;code&gt;hanami&lt;/code&gt; feature above? Well, this is where you enable that feature along with all of the features provided by Rodauth. Here&amp;#8217;s the implementation:&lt;/p&gt;
&lt;/div&gt;
&lt;details&gt;
&lt;summary class=&quot;title&quot;&gt;Implementation&lt;/summary&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# slices/authentication/middleware.rb&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# auto_register: false&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;refinements/string&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;roda&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;rodauth&quot;&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;require_relative&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;feature&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Authentication&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Specialized Roda middleware for authentication.&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Middleware&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Roda&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Refinements&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;plugin&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:middleware&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;plugin&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:rodauth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;json: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;enable&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:active_sessions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
             &lt;span class=&quot;ss&quot;&gt;:audit_logging&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
             &lt;span class=&quot;ss&quot;&gt;:change_login&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
             &lt;span class=&quot;ss&quot;&gt;:change_password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
             &lt;span class=&quot;ss&quot;&gt;:create_account&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
             &lt;span class=&quot;ss&quot;&gt;:disallow_common_passwords&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
             &lt;span class=&quot;ss&quot;&gt;:hanami&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
             &lt;span class=&quot;ss&quot;&gt;:jwt_refresh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
             &lt;span class=&quot;ss&quot;&gt;:login&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
             &lt;span class=&quot;ss&quot;&gt;:logout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
             &lt;span class=&quot;ss&quot;&gt;:remember&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
             &lt;span class=&quot;ss&quot;&gt;:recovery_codes&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Authentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;db.gateway&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;connection&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Feature (automatic): base&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;accounts_table&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;after_login&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;remember_login&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;already_logged_in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;redirect&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;flash_error_key&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:alert&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;hmac_secret&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hanami&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;app_secret&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;login_label&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Email&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;password_hash_table&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_password_hash&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;require_login_error_flash&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Please log in to continue.&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;template_opts&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;layout: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;unverified_account_message&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Unverified user, please verify before logging in.&quot;&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Feature (automatic): login_password_requirements_base&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;require_password_confirmation?&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Feature: active_sessions&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;active_sessions_account_id_column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;active_sessions_table&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_active_session_key&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Feature: audit_logging&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;audit_logging_table&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_authentication_audit_log&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;audit_logging_account_id_column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Feature: change_login&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;change_login_route&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;me/login&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;change_login_view&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;login_update&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Feature: change_password&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;change_password_route&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;me/password&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;change_password_view&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;password_update&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;change_password_button&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Save&quot;&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Feature: create_account&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;create_account_button&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Create&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;create_account_link_text&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Register.&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;create_account_route&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;register&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;create_account_view&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;register&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;change_login_button&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Save&quot;&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;after_create_account&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;account&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;user_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;param&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;account_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:account&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;insert&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;label: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;snakecase&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_name&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:membership&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;insert&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;user_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;account_id:
      &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Feature (custom): hanami&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;hanami_view&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;View&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Feature: jwt&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;jwt_secret&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hanami&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;app_secret&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;jwt_refresh_route&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;api/jwt&quot;&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Feature: jwt_refresh&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;jwt_access_token_not_before_period&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hanami&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;api_access_token_period&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;jwt_refresh_token_account_id_column&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;jwt_refresh_token_table&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_jwt_refresh_key&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Feature: login&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;login_error_flash&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;There was an error signing in.&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;login_form_footer_links_heading&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;login_notice_flash&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;You have been logged in.&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;login_return_to_requested_location?&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;multi_phase_login_view&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;view&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;login_multi_phase&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Feature: logout&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;logout_notice_flash&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;You have been logged out.&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;logout_redirect&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/&quot;&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Feature: remember&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;remember_button&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Save&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;remember_table&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_remember_key&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;remember_route&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;me/remember&quot;&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Feature: recovery_codes&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;recovery_codes_table&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:user_recovery_code&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;route&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;rodauth&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rodauth&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rodauth&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The above might seem daunting, at first, but is mostly configuration logic. You can read it from top to bottom as follows:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Define the &lt;code&gt;middleware&lt;/code&gt; plugin because we definitely want Rodauth to behave like middleware so we can wire into our routes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Define the &lt;code&gt;rodauth&lt;/code&gt; plugin and then enable all of the features you want (this includes our custom &lt;code&gt;hanami&lt;/code&gt; feature).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure each feature. Once again, there is a code comment that helps you jump to the associated Rodauth documentation of that feature.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Definitely spend time reading through Rodauth&amp;#8217;s feature documentation to customize further as you can adjust for your needs.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;_view_context&quot;&gt;View Context&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In order to not repeat yourself and leverage common styles from your main application, you&amp;#8217;ll want to include your application&amp;#8217;s main assets. This can be done with this view:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# authentication/views/context.rb&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# auto_register: false&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Authentication&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Views&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# The slice view context.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hanami&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;View&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Context&lt;/span&gt;
      &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Deps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;main_assets: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;main.assets&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The above will allow you to use your application&amp;#8217;s assets (mostly styles) within all templates associated with this slice.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;_routes&quot;&gt;Routes&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Necessary to ensure all routes are properly authenticated by using the new Rodauth middleware at the top of our routes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Demos&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# The application routes.&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Routes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hanami&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Routes&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:authentication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;at: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Authentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Middleware&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect2&quot;&gt;
&lt;h3 id=&quot;_layoutstemplates&quot;&gt;Layouts/Templates&lt;/h3&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;While you can get by with Rodauth&amp;#8217;s default layouts and templates, you&amp;#8217;ll want to customize further. The following documents the layouts and templates used to customize the UI. These are listed by pure file structure only since providing source code for each would be slightly overkill. That said, there is a process for customization which looks like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;ulist&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Open the Rodauth gem in your default text editor. If using &lt;a href=&quot;/projects/gemsmith&quot;&gt;Gemsmith&lt;/a&gt;, this is as simple as running: &lt;code&gt;gemsmith --edit rodauth&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once in your editor, open the &lt;code&gt;templates&lt;/code&gt; folder where you&amp;#8217;ll see multiple &lt;code&gt;*.str&lt;/code&gt; files. These, roughly, map to each feature you enabled and configured earlier. You can then take the source code and convert to ERB syntax that is compatible with &lt;a href=&quot;/articles/hanami_views&quot;&gt;Hanami Views&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Repeat this process for each file you need to customize further.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Here&amp;#8217;s what the file structure looks like after using the above process:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre&gt;# Layouts
app/templates/layouts/authentication.html.erb

# Templates
slices/authentication/templates/layouts/app.html.erb
slices/authentication/templates/login.html.erb
slices/authentication/templates/login_update.html.erb
slices/authentication/templates/logout.html.erb
slices/authentication/templates/password_update.html.erb
slices/authentication/templates/register.html.erb
slices/authentication/templates/remember.html.erb&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In case it helps, here&amp;#8217;s an example of converting the Rodauth &lt;code&gt;logout.str&lt;/code&gt; template to a Hanami &lt;code&gt;logout.html.erb&lt;/code&gt; template:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;From Rodauth&lt;/strong&gt; (&lt;code&gt;logout.str&lt;/code&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;form&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;method=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;post&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;rodauth&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;role=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;form&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;logout-form&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  #{rodauth.logout_additional_form_tags}
  #{rodauth.csrf_tag}
  #{rodauth.button(rodauth.logout_button, :class=&amp;gt;&#39;btn btn-warning&#39;)}
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;&lt;strong&gt;To Hanami&lt;/strong&gt; (&lt;code&gt;logout.html.erb&lt;/code&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;form&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;method=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;post&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;rodauth&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;role=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;form&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;logout-form&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;%==&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rodauth.logout_additional_form_tags&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;%==&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rodauth.csrf_tag&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;%==&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rodauth.button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rodauth.logout_button&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class:&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;btn&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;btn-warning&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_actions&quot;&gt;Actions&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;With the &lt;a href=&quot;https://rodauth.jeremyevans.net/index.html&quot;&gt;Rodauth&lt;/a&gt; Authentication slice in place, you&amp;#8217;ll want to update your root application &lt;code&gt;Action&lt;/code&gt; so all subclasses can inherit authentication behavior. This can be done by defining a &lt;em&gt;protected&lt;/em&gt; &lt;code&gt;authorize&lt;/code&gt; method with a subsequent &lt;em&gt;private&lt;/em&gt; &lt;code&gt;handle_rodauth_redirect&lt;/code&gt; for wiring in flash and redirect support. Example:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# auto_register: false&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;hanami/action&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Demo&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# The application base action.&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Action&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hanami&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Action&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;before&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:authorize&lt;/span&gt;

    &lt;span class=&quot;kp&quot;&gt;protected&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;authorize&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;rodauth&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;rodauth&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rodauth&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;handle_rodauth_redirect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rodauth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rodauth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;require_account&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:current_user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rodauth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;account_id&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;kp&quot;&gt;private&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handle_rodauth_redirect&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rodauth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;halted&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:halt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;halted&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;halted&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;rodauth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;flash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;flash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;redirect&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Location&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;code&lt;/span&gt;

      &lt;span class=&quot;kp&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:halt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The above works for all UI &lt;em&gt;and&lt;/em&gt; API requests. In rare situations where you need to disable the &lt;code&gt;authorize&lt;/code&gt; before callback, you can override &lt;code&gt;authorize&lt;/code&gt; as follows in your subclass:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Demo&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Actions&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;API&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Log&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# The create action.&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Create&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Action&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# Truncated for brevity...&lt;/span&gt;

          &lt;span class=&quot;kp&quot;&gt;protected&lt;/span&gt;

          &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;authorize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The above will completely disable the authentication check for you API log request (POST) with minimal effort.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_rspec&quot;&gt;RSpec&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Now that everything is wired up, you&amp;#8217;ll want to add a couple &lt;a href=&quot;https://rspec.info&quot;&gt;RSpec&lt;/a&gt; shared contexts to aid in writing your feature and request specs. First, here&amp;#8217;s the &lt;em&gt;with login&lt;/em&gt; shared context you can use for all feature (UI) specs:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# demo/spec/support/shared_contexts/login.rb&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;shared_context&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;with login&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:verified&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;before&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_password_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;visit&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/login&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fill_in&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;login&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;with: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;email&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;click_button&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Login&quot;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;fill_in&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Password&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;with: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;password&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;click_button&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Login&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Second, here&amp;#8217;s the &lt;em&gt;with JWT&lt;/em&gt; shared context you can use for all request (API) specs:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;listingblock&quot;&gt;
&lt;div class=&quot;content&quot;&gt;
&lt;pre class=&quot;rouge highlight&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# demo/spec/support/shared_contexts/jwt.rb&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;shared_context&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;with JWT&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:verified&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:access_token&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/login&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;login: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;password: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;CONTENT_TYPE&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;no&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;last_response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;symbolize_names: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:access_token&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;before&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Factory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_password_hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Feel free to customize a needed but the above will allow you to quickly authenticate, for testing purposes, when writing feature and request specs.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_tools&quot;&gt;Tools&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Due to &lt;a href=&quot;https://rodauth.jeremyevans.net/index.html&quot;&gt;Rodauth&lt;/a&gt; having a rich feature set, it quickly becomes tiresome jumping around the docomentation so, for &lt;a href=&quot;https://www.alfredapp.com&quot;&gt;Alfred&lt;/a&gt; fans, you might want to leverage the &lt;a href=&quot;/projects/pennyworth&quot;&gt;Pennyworth&lt;/a&gt; gem which provides a &lt;a href=&quot;/projects/pennyworth#_rodauth&quot;&gt;Rodauth Workflow&lt;/a&gt; for quick access to the documentation. Even better you can fuzzy type to quickly jump to any of the supported features.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;sect1&quot;&gt;
&lt;h2 id=&quot;_conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;div class=&quot;sectionbody&quot;&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;As mentioned earlier, there is definitely a learning curve when folding &lt;a href=&quot;https://rodauth.jeremyevans.net/index.html&quot;&gt;Rodauth&lt;/a&gt; into your web application but is much faster/better than implementing this yourself. Plus, as you&amp;#8217;ve seen, there&amp;#8217;s plenty of opportunity to customize for your specific needs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The core integration happens via the Rodauth feature and corresponding middleware. The rest is getting up to speed with all Rodauth&amp;#8217;s feature then leveraging the power of Hanami slices, routes, views, layouts, and templates to customize as desired.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;May your application be secure!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</content>
    <id>https://alchemists.io/articles/hanami_with_rodauth</id>
    <link href="https://alchemists.io/articles/hanami_with_rodauth"/>
    <published>2026-02-01T13:17:41+00:00</published>
    <rights>2026</rights>
    <title>Hanami with Rodauth</title>
    <updated>2026-05-13T20:41:26+00:00</updated>
    <dc:date>2026-05-13T20:41:26+00:00</dc:date>
  </entry>
  <dc:date>2026-10-06T00:29:40+00:00</dc:date>
</feed>
