<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ayush Newatia</title>
    <description>The latest articles on DEV Community by Ayush Newatia (@ayushn21).</description>
    <link>https://dev.to/ayushn21</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F556231%2Fb4e0577c-3ff5-46f4-afa5-a02e712f5d09.png</url>
      <title>DEV Community: Ayush Newatia</title>
      <link>https://dev.to/ayushn21</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ayushn21"/>
    <language>en</language>
    <item>
      <title>Rack for Ruby: Socket Hijacking</title>
      <dc:creator>Ayush Newatia</dc:creator>
      <pubDate>Wed, 04 Dec 2024 14:00:00 +0000</pubDate>
      <link>https://dev.to/appsignal/rack-for-ruby-socket-hijacking-1ken</link>
      <guid>https://dev.to/appsignal/rack-for-ruby-socket-hijacking-1ken</guid>
      <description>&lt;p&gt;In the first part of this series, we set up a basic Rack app, learned how to process a request and send a response.&lt;/p&gt;

&lt;p&gt;In this post, we'll take over connections from Rack and hold persistent connections to enable pathways such as WebSockets.&lt;/p&gt;

&lt;p&gt;First, though, let's look at how an HTTP connection actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTTP Connections
&lt;/h2&gt;

&lt;p&gt;As this diagram shows, a TCP socket is opened, and a request is sent to a server. The server responds and closes the connection. All communication is in plain text.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0q21j49y5knupromq9j6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0q21j49y5knupromq9j6.png" alt="HTTP sequence diagram" width="800" height="574"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using a technique called socket hijacking, we can take control of a socket from Rack when a request comes in. Rack offers two techniques for socket hijacking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/rack/rack/blob/main/SPEC.rdoc#partial-hijack-" rel="noopener noreferrer"&gt;&lt;strong&gt;Partial hijack&lt;/strong&gt;&lt;/a&gt;: Rack sends the HTTP response headers and hands over the connection to the application.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rack/rack/blob/main/SPEC.rdoc#full-hijack-" rel="noopener noreferrer"&gt;&lt;strong&gt;Full hijack&lt;/strong&gt;&lt;/a&gt;: Rack simply hands over the connection to the client without writing anything to the socket.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Partial Hijacking
&lt;/h2&gt;

&lt;p&gt;This is how you do a partial hijack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;ensure&lt;/span&gt;
      &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"content-type"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"rack.hijack"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;rack.hijack&lt;/code&gt; is a &lt;em&gt;Rack header&lt;/em&gt;, set in the same Hash as the HTTP response headers. Rack will look for such headers and process them as per the specification, instead of writing them to the HTTP response.&lt;/p&gt;

&lt;p&gt;Run the above app and &lt;code&gt;curl&lt;/code&gt; to it. You'll see that it writes the time at one-second intervals.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; localhost:9292
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Full Hijacking
&lt;/h2&gt;

&lt;p&gt;This is how you'd do a full hijack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;"HTTP/1.1 200 OK"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Content-Type: text/plain"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"rack.hijack"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flush&lt;/span&gt;

    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;ensure&lt;/span&gt;
      &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="p"&gt;[]]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, we call the proc passed to us using the &lt;code&gt;rack.hijack&lt;/code&gt; key, instead of setting one ourselves in the response. This gives us complete control over the socket. At the end, we return an array with the status &lt;code&gt;-1&lt;/code&gt; only because Rack expects an array to be returned. The contents of this array are ignored since we've taken over the socket.&lt;/p&gt;

&lt;p&gt;This is a bad practice, rife with gotchas and weird behavior. Don't do it. Samuel Williams, who is a maintainer of Rack, &lt;a href="https://github.com/rack/rack/discussions/2162#discussioncomment-8698730" rel="noopener noreferrer"&gt;recommends against it as well&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streaming Bodies in Rack for Ruby
&lt;/h2&gt;

&lt;p&gt;While full hijacking is a terrible idea, partial hijacking is a useful tool. But it still feels hacky, so Rack 3 formally adopted that approach into the spec by introducing the concept of &lt;em&gt;streaming bodies&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;ensure&lt;/span&gt;
      &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"content-type"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"text/plain"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we provide a block as the response body rather than an array. Rack keeps the connection open until the block finishes executing.&lt;/p&gt;

&lt;p&gt;There's a huge gotcha here when using Puma. Puma is a multi-threaded server that assigns a thread to each incoming request. We're taking over the socket from Rack, but we're still tying up a Puma thread as long as the connection is open.&lt;/p&gt;

&lt;p&gt;Puma concurrency can be configured, but threads are limited, and tying one up for long periods is not a good idea. Let's see this in action first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;puma &lt;span class="nt"&gt;-w&lt;/span&gt; 1 &lt;span class="nt"&gt;-t&lt;/span&gt; 1:1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In two separate terminal windows, run the following command at the same time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl localhost:9292
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One request is immediately served, but the other is held until the first one completes. This is because we started Puma with a single worker and single thread, meaning it can only serve a single request at a time.&lt;/p&gt;

&lt;p&gt;We can get around this by creating our own thread.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
          &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
          &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;ensure&lt;/span&gt;
        &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"content-type"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"text/plain"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if you try the above experiment again, you'll see both &lt;code&gt;curl&lt;/code&gt; requests are served concurrently because they don't tie up a Puma thread.&lt;/p&gt;

&lt;p&gt;Once again, I must warn against this approach, unless you know what you're doing. These demonstrations are largely academic, as systems programming is a deep and complex topic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Falcon Web Server
&lt;/h2&gt;

&lt;p&gt;Since the threading problem is specific to the Puma web server, let's look at another option: Falcon. This is a new, highly concurrent Rack-compliant web server built on the &lt;a href="https://github.com/socketry/async" rel="noopener noreferrer"&gt;&lt;code&gt;async&lt;/code&gt; gem&lt;/a&gt;. It uses Ruby Fibers instead of Threads, which are cheaper to create and have much lower overhead.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;async&lt;/code&gt; gem hooks into all Ruby I/O and other waiting operations, such as &lt;code&gt;sleep&lt;/code&gt;, and uses these to switch between different Fibers (ensuring a program is never held up doing nothing).&lt;/p&gt;

&lt;p&gt;Revert your app to the previous version where we're not spawning a new thread:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;ensure&lt;/span&gt;
      &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"content-type"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"text/plain"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then remove Puma and install Falcon.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle remove puma
&lt;span class="nv"&gt;$ &lt;/span&gt;bundle add falcon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the Falcon server. We need to explicitly bind it because it only serves &lt;code&gt;https&lt;/code&gt; traffic by default.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;falcon serve &lt;span class="nt"&gt;-n&lt;/span&gt; 1 &lt;span class="nt"&gt;-b&lt;/span&gt; http://localhost:9292
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server only uses a single thread, which you can confirm with the command below. You'll need to grab your specific &lt;code&gt;pid&lt;/code&gt; from Falcon's logs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;top &lt;span class="nt"&gt;-pid&lt;/span&gt; &amp;lt;pid&amp;gt; &lt;span class="nt"&gt;-stats&lt;/span&gt; pid,th
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The thread count printed by the above command will be &lt;code&gt;2&lt;/code&gt; because the MRI uses a thread internally.&lt;/p&gt;

&lt;p&gt;Try the earlier experiment again and run two &lt;code&gt;curl&lt;/code&gt; requests simultaneously.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl localhost:9292
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see they're both served at the same time, thanks to Ruby Fibers!&lt;/p&gt;

&lt;p&gt;Falcon is relatively new. Ruby Fibers were only introduced in Ruby 3.0. Since Falcon is Rack-compliant, it can be used with Rails too, but the docs recommend using it with v7.1 or newer only. As such, it's a bit risky to use Falcon in production but it's a very exciting development in the Ruby world, in my opinion. I can't wait to see its progress in the next few years.&lt;/p&gt;

&lt;p&gt;We've now learned how to create persistent connections in Rack and how to run them without blocking other requests, but the use cases so far have been academic and contrived. In the next and final part of this series, we'll examine how we can use this technique in a practical way.&lt;/p&gt;

&lt;p&gt;Until then, happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, &lt;a href="https://blog.appsignal.com/ruby-magic" rel="noopener noreferrer"&gt;subscribe to our Ruby Magic newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rack</category>
    </item>
    <item>
      <title>The Basics of Rack for Ruby</title>
      <dc:creator>Ayush Newatia</dc:creator>
      <pubDate>Wed, 13 Nov 2024 14:06:53 +0000</pubDate>
      <link>https://dev.to/appsignal/the-basics-of-rack-for-ruby-5fkk</link>
      <guid>https://dev.to/appsignal/the-basics-of-rack-for-ruby-5fkk</guid>
      <description>&lt;p&gt;Rack is the foundation for every popular Ruby web framework in existence. It standardizes an interface between a Ruby application and a web server. This mechanism allows us to pair any Rack-compliant web server (such as Puma, Unicorn, or Falcon) with any Rack-compliant web framework (like Rails, Sinatra, Roda, or Hanami).&lt;/p&gt;

&lt;p&gt;Separating the concerns like this is immensely powerful and provides a lot of flexibility. It does, however, also come with limitations.&lt;/p&gt;

&lt;p&gt;Rack 2 operated on the assumption that every request must provide a response and close the connection. It made no facility for persistent connections to enable pathways like WebSockets.&lt;/p&gt;

&lt;p&gt;Developers had to make use of a hacky escape hatch to take over connections from Rack to implement WebSockets or similar persistent connections.&lt;/p&gt;

&lt;p&gt;This all changed with Rack 3. But first, let's backtrack and take a closer look at Rack itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Barebones Rack App
&lt;/h2&gt;

&lt;p&gt;A basic Rack app looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"text/plain"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Hello World"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="no"&gt;App&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/rack/rack/blob/main/SPEC.rdoc#label-The+Environment" rel="noopener noreferrer"&gt;&lt;code&gt;env&lt;/code&gt;&lt;/a&gt; is a hash containing request-specific information such as HTTP headers. When a request is made, the &lt;code&gt;call&lt;/code&gt; method is called, and we return an array representing the response.&lt;/p&gt;

&lt;p&gt;The first element is the HTTP response code, in this case &lt;code&gt;200&lt;/code&gt;. The second element is a hash containing any &lt;a href="https://github.com/rack/rack/blob/main/SPEC.rdoc#label-The+Headers" rel="noopener noreferrer"&gt;Rack and HTTP response headers&lt;/a&gt; we wish to send. Finally, the last element is an array of strings representing the response body.&lt;/p&gt;

&lt;p&gt;Let's organize this app into a folder and run it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;rack-demo
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;rack-demo
&lt;span class="nv"&gt;$ &lt;/span&gt;bundle init
&lt;span class="nv"&gt;$ &lt;/span&gt;bundle add rack rackup
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;app.rb
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;config.ru
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;Fill in &lt;code&gt;app.rb&lt;/code&gt; with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"content-type"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"text/plain"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Hello World"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;config.ru&lt;/code&gt; with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s2"&gt;"app"&lt;/span&gt;

&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="no"&gt;App&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can run this app using the default WEBrick server by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rackup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server will run on port &lt;code&gt;9292&lt;/code&gt;. We can verify this with a &lt;code&gt;curl&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl localhost:9292
Hello World
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's got the basic app running!&lt;/p&gt;

&lt;h2&gt;
  
  
  Changing Web Servers
&lt;/h2&gt;

&lt;p&gt;WEBrick is a development-only server, so let's swap it out for Puma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle add puma
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now try running &lt;code&gt;rackup&lt;/code&gt; again. You'll see it has automatically detected Puma in the bundle and started that instead of WEBrick!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rackup
Puma starting &lt;span class="k"&gt;in &lt;/span&gt;single mode...
&lt;span class="k"&gt;*&lt;/span&gt; Puma version: 6.4.2 &lt;span class="o"&gt;(&lt;/span&gt;ruby 3.2.2-p53&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"The Eagle of Durango"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt;  Min threads: 0
&lt;span class="k"&gt;*&lt;/span&gt;  Max threads: 5
&lt;span class="k"&gt;*&lt;/span&gt;  Environment: development
&lt;span class="k"&gt;*&lt;/span&gt;          PID: 45877
&lt;span class="k"&gt;*&lt;/span&gt; Listening on http://127.0.0.1:9292
&lt;span class="k"&gt;*&lt;/span&gt; Listening on http://[::1]:9292
Use Ctrl-C to stop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I recommend starting Puma directly instead of using &lt;code&gt;rackup&lt;/code&gt;, as that allows us to pass configuration arguments should we want to. The &lt;code&gt;-w 4&lt;/code&gt; below starts Puma using 4 workers, meaning 4 instances of Puma are started up simultaneously.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;puma &lt;span class="nt"&gt;-w&lt;/span&gt; 4
&lt;span class="o"&gt;[&lt;/span&gt;45968] Puma starting &lt;span class="k"&gt;in &lt;/span&gt;cluster mode...
&lt;span class="o"&gt;[&lt;/span&gt;45968] &lt;span class="k"&gt;*&lt;/span&gt; Puma version: 6.4.2 &lt;span class="o"&gt;(&lt;/span&gt;ruby 3.2.2-p53&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"The Eagle of Durango"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;45968] &lt;span class="k"&gt;*&lt;/span&gt;  Min threads: 0
&lt;span class="o"&gt;[&lt;/span&gt;45968] &lt;span class="k"&gt;*&lt;/span&gt;  Max threads: 5
&lt;span class="o"&gt;[&lt;/span&gt;45968] &lt;span class="k"&gt;*&lt;/span&gt;  Environment: development
&lt;span class="o"&gt;[&lt;/span&gt;45968] &lt;span class="k"&gt;*&lt;/span&gt;   Master PID: 45968
&lt;span class="o"&gt;[&lt;/span&gt;45968] &lt;span class="k"&gt;*&lt;/span&gt;      Workers: 4
&lt;span class="o"&gt;[&lt;/span&gt;45968] &lt;span class="k"&gt;*&lt;/span&gt;     Restarts: &lt;span class="o"&gt;(&lt;/span&gt;✔&lt;span class="o"&gt;)&lt;/span&gt; hot &lt;span class="o"&gt;(&lt;/span&gt;✔&lt;span class="o"&gt;)&lt;/span&gt; phased
&lt;span class="o"&gt;[&lt;/span&gt;45968] &lt;span class="k"&gt;*&lt;/span&gt; Listening on http://0.0.0.0:9292
&lt;span class="o"&gt;[&lt;/span&gt;45968] Use Ctrl-C to stop
&lt;span class="o"&gt;[&lt;/span&gt;45968] - Worker 0 &lt;span class="o"&gt;(&lt;/span&gt;PID: 45981&lt;span class="o"&gt;)&lt;/span&gt; booted &lt;span class="k"&gt;in &lt;/span&gt;0.0s, phase: 0
&lt;span class="o"&gt;[&lt;/span&gt;45968] - Worker 1 &lt;span class="o"&gt;(&lt;/span&gt;PID: 45982&lt;span class="o"&gt;)&lt;/span&gt; booted &lt;span class="k"&gt;in &lt;/span&gt;0.0s, phase: 0
&lt;span class="o"&gt;[&lt;/span&gt;45968] - Worker 2 &lt;span class="o"&gt;(&lt;/span&gt;PID: 45983&lt;span class="o"&gt;)&lt;/span&gt; booted &lt;span class="k"&gt;in &lt;/span&gt;0.0s, phase: 0
&lt;span class="o"&gt;[&lt;/span&gt;45968] - Worker 3 &lt;span class="o"&gt;(&lt;/span&gt;PID: 45984&lt;span class="o"&gt;)&lt;/span&gt; booted &lt;span class="k"&gt;in &lt;/span&gt;0.0s, phase: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This basic app demonstrates the Rack interface. An incoming HTTP request is parsed into the &lt;code&gt;env&lt;/code&gt; hash and provided to the application. The application processes the request and supplies an array as the response that the server formats and sends to the client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rack Compliance In Frameworks
&lt;/h2&gt;

&lt;p&gt;Every compliant web framework follows the Rack spec under the hood and provides an access point to go down to this level.&lt;/p&gt;

&lt;p&gt;In Rails, we can send a Rack response in a controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomeController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"I'm Home!"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly, in Roda:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt; &lt;span class="s2"&gt;"home"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;halt&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"I'm Home!"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every Rack-compliant framework will have a slightly different syntax for accomplishing this, but since they're all sending Rack responses under the hood, they will have an API for you to access that response.&lt;/p&gt;

&lt;p&gt;You can find the full, relatively accessible &lt;a href="https://github.com/rack/rack/blob/main/SPEC.rdoc" rel="noopener noreferrer"&gt;Rack specification on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;As this demo shows, Rack operates under the assumption that a request comes in, is processed by a web application, and a response is sent back. Throwing persistent connections into the mix totally breaks this model, yet Rack-compliant frameworks like Rails implement WebSockets.&lt;/p&gt;

&lt;p&gt;In the next post, part two of a three-part series, we'll cover how to take over connections from Rack so we can hold persistent connections to enable pathways such as WebSockets.&lt;/p&gt;

&lt;p&gt;Until then, happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, &lt;a href="https://blog.appsignal.com/ruby-magic" rel="noopener noreferrer"&gt;subscribe to our Ruby Magic newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
    </item>
    <item>
      <title>Turbo Streaming Modals in Ruby on Rails</title>
      <dc:creator>Ayush Newatia</dc:creator>
      <pubDate>Wed, 27 Mar 2024 15:00:00 +0000</pubDate>
      <link>https://dev.to/appsignal/turbo-streaming-modals-in-ruby-on-rails-4ehi</link>
      <guid>https://dev.to/appsignal/turbo-streaming-modals-in-ruby-on-rails-4ehi</guid>
      <description>&lt;p&gt;In part one of this series, we used Hotwire's Stimulus and Turbo Frames to present modals in Rails.&lt;/p&gt;

&lt;p&gt;Now, we'll dive into another method we can use to present modals: Turbo Streams.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Turbo Streams in Ruby on Rails?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://turbo.hotwired.dev/handbook/streams"&gt;Turbo Streams&lt;/a&gt; is a subset of Turbo. It allows us to make fine-grained, targeted updates to a page. By default, it contains seven CRUD actions, but we're free to add more actions within our applications.&lt;/p&gt;

&lt;p&gt;Now, we'll create a &lt;code&gt;show_remote_modal&lt;/code&gt; action which renders and presents the &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; from our previous post.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a Custom Action
&lt;/h3&gt;

&lt;p&gt;Create a folder to place all custom Stream Actions in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;app/javascript/stream_actions
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;app/javascript/stream_actions/index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a file for the Action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;app/javascript/stream_actions/show_remote_modal.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Import the Stream Actions into the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/javascript/stream_actions/index.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./show_remote_modal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/javascript/application.js&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stream_actions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're using import maps, you'll need to update the config and restart the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/importmap.rb&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="n"&gt;pin_all_from&lt;/span&gt; &lt;span class="s2"&gt;"app/javascript/stream_actions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;under: &lt;/span&gt;&lt;span class="s2"&gt;"stream_actions"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change the global remote modal container to an HTML element instead of a Turbo Frame:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;%# app/views/layouts/application.html.erb %&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;%# ... %&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;remote-modal-container&amp;gt;&amp;lt;/remote-modal-container&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The custom Stream Action can be implemented as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/javascript/stream_actions/show_remote_modal.js&lt;/span&gt;

&lt;span class="nx"&gt;Turbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StreamActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;show_remote_modal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;remote-modal-container&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replaceChildren&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;templateContent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dialog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;showModal&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above snippet, &lt;code&gt;this&lt;/code&gt; refers to &lt;a href="https://github.com/hotwired/turbo/blob/main/src/elements/stream_element.js"&gt;&lt;code&gt;StreamElement&lt;/code&gt;&lt;/a&gt;, which is the &lt;a href="https://javascript.info/custom-elements"&gt;custom element&lt;/a&gt; underpinning &lt;code&gt;&amp;lt;turbo-stream&amp;gt;&lt;/code&gt;. The &lt;code&gt;templateContent&lt;/code&gt; getter is defined by this element.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the Action with a Rails Helper
&lt;/h3&gt;

&lt;p&gt;Since this is a custom Action, we'll need to manually create a Rails helper to use it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bin/rails generate helper TurboStreamActions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/helpers/turbo_stream_actions.rb&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;TurboStreamActionsHelper&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show_remote_modal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;turbo_stream_action_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;:show_remote_modal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;template: &lt;/span&gt;&lt;span class="vi"&gt;@view_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Turbo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Streams&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TagBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;TurboStreamActionsHelper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now use this helper in our views.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;%# app/views/support/tickets/new.html.erb %&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show_remote_modal&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;dialog&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"contact_form_modal"&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"modal_title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"modal_title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Contact us
      &lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"dialog"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"close"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;X&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;

    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="n"&gt;support_tickets_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Your message"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text_area&lt;/span&gt; &lt;span class="ss"&gt;:message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;autofocus: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;button&lt;/span&gt; &lt;span class="s2"&gt;"Close"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;formmethod: :dialog&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;button&lt;/span&gt; &lt;span class="s2"&gt;"Send"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/dialog&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember to remove the &lt;code&gt;data-controller&lt;/code&gt; attribute: we don't need it anymore. In fact, we can delete the controller itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;rm &lt;/span&gt;app/javascript/controllers/remote_modal_controller.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll also need to change the template's name so it renders as a Turbo Stream.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    app/views/support/tickets/new.html.erb &lt;span class="se"&gt;\&lt;/span&gt;
    app/views/support/tickets/new.turbo_stream.erb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Turbo Streams are disabled by default for &lt;code&gt;GET&lt;/code&gt; requests, so we'll need to manually enable them for the link:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;%# app/views/support/show.html.erb %&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;%# ... %&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="n"&gt;new_support_ticket_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;turbo_stream: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  Show contact form
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;%# ... %&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Refresh the page and click &lt;em&gt;Show contact form&lt;/em&gt;. It should still work as before, but now it's rendered using a custom Stream Action!&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;In this two-part series, we explored three different methods to present modals using Hotwire: Stimulus, Turbo Frames, and Turbo Streams. More importantly, the modals were presented with accessibility as the main consideration.&lt;/p&gt;

&lt;p&gt;The web should be usable by everyone and it's important for us, as web developers, to put in the effort to make websites accessible.&lt;/p&gt;

&lt;p&gt;Basecamp's &lt;a href="https://github.com/basecamp/accessibility"&gt;accessibility guide&lt;/a&gt; is publicly available and a fantastic resource to learn the ropes.&lt;/p&gt;

&lt;p&gt;I also recommend checking out the docs for &lt;a href="https://stimulus.hotwired.dev"&gt;Stimulus&lt;/a&gt; and &lt;a href="https://turbo.hotwired.dev"&gt;Turbo&lt;/a&gt; to familiarise yourself with all their features and the APIs used in this series.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, &lt;a href="https://blog.appsignal.com/ruby-magic"&gt;subscribe to our Ruby Magic newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>Hotwire Modals in Ruby on Rails with Stimulus and Turbo Frames</title>
      <dc:creator>Ayush Newatia</dc:creator>
      <pubDate>Wed, 06 Mar 2024 11:17:29 +0000</pubDate>
      <link>https://dev.to/appsignal/hotwire-modals-in-ruby-on-rails-with-stimulus-and-turbo-frames-3jml</link>
      <guid>https://dev.to/appsignal/hotwire-modals-in-ruby-on-rails-with-stimulus-and-turbo-frames-3jml</guid>
      <description>&lt;p&gt;Modals are widely used on the web, but they are rarely built with accessibility in mind. When a modal is displayed, the background is dimmed visually but it's still visible to screen readers and keyboard-only users.&lt;/p&gt;

&lt;p&gt;In this post, the first of a two-part series, we'll look at presenting accessible modals in Rails using two different approaches powered by Hotwire's Turbo and Stimulus libraries.&lt;/p&gt;

&lt;p&gt;But first, let's see what we need to do to make modals accessible.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Can We Make Modals Accessible?
&lt;/h2&gt;

&lt;p&gt;To make modals accessible, we need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hide the background from screen readers.&lt;/li&gt;
&lt;li&gt;Implement "focus trapping" so keyboard users cannot focus on elements outside the modal.&lt;/li&gt;
&lt;li&gt;Shift focus to the first focusable element in the modal (if there is one).&lt;/li&gt;
&lt;li&gt;Dismiss the modal with the &lt;code&gt;Esc&lt;/code&gt; key.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We could accomplish the first two by setting &lt;code&gt;inert="true"&lt;/code&gt; on the background elements, but this attribute can't be set on an ancestor of the modal. We can do the last two with some more custom JavaScript. As you can imagine this is rather tedious.&lt;/p&gt;

&lt;p&gt;The HTML &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element gives us most of the above list for free, and is supported for &lt;a href="https://caniuse.com/?search=dialog"&gt;94.25% of global users&lt;/a&gt;, meaning it's ideal in most cases.&lt;/p&gt;

&lt;p&gt;Let's look a bit more closely at the &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; Element
&lt;/h2&gt;

&lt;p&gt;According to &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog"&gt;MDN&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The HTML &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element is used to create both modal and non-modal dialog boxes. Modal dialog boxes interrupt interaction with the rest of the page being inert, while non-modal dialog boxes allow interaction with the rest of the page.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means that a &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; isn't necessarily a modal. It can be presented within the document flow as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Presenting a &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;To present a dialog as a modal, we need to use JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dialog&amp;gt;&lt;/span&gt;Lorem ipsum ....&lt;span class="nt"&gt;&amp;lt;/dialog&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;modal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dialog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showModal&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When presenting a dialog this way, the background is made inert, focus is trapped in the modal, and it can be dismissed using the &lt;code&gt;Esc&lt;/code&gt; key.&lt;/p&gt;

&lt;p&gt;To present a &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; in a non-modal context, and hence make it visible by default, we use the &lt;code&gt;open&lt;/code&gt; attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dialog&lt;/span&gt; &lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Lorem ipsum ....&lt;span class="nt"&gt;&amp;lt;/dialog&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We won't get the accessibility features using this method, though, as the dialog hasn't been presented "modally".&lt;/p&gt;

&lt;h3&gt;
  
  
  Dismissing a &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A modally presented &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; can be dismissed using JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;modal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dialog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showModal&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can also be closed using a &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; with the &lt;code&gt;dialog&lt;/code&gt; method. This is a great way to implement a "close" button.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dialog&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;A modal dialog&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"dialog"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Close&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;

  Lorem ipsum...
&lt;span class="nt"&gt;&amp;lt;/dialog&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That covers the basics of the &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element, so let's look at using it with Hotwire.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modal Presentation Using Stimulus in Ruby on Rails
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://stimulus.hotwired.dev"&gt;Stimulus&lt;/a&gt; is a JavaScript library under the &lt;a href="https://hotwired.dev"&gt;Hotwire&lt;/a&gt; umbrella. It allows us to attach pieces of JavaScript logic to HTML elements encapsulated in &lt;em&gt;controllers&lt;/em&gt;. This post assumes a basic familiarity with its API.&lt;/p&gt;

&lt;p&gt;Let's start with a Rails controller and view. As an example use case, we'll use a &lt;em&gt;support&lt;/em&gt; page which has a button to display some contact details in a modal. Generate a controller and action using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bin/rails generate controller support show
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Amend the created route to a more user-friendly path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/routes.rb&lt;/span&gt;
&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'/support'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s2"&gt;"support#show"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sketch out a button and dialog in the newly generated view file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;%# app/views/support/show.html.erb %&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;
  Show contact details
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;dialog&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"modal_title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"modal_title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Contact details
    &lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"dialog"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"close"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;X&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
    incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
    nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
  &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dialog&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the Rails server and navigate to the &lt;code&gt;/support&lt;/code&gt; path. You should see a button there, but it doesn't do anything at the moment. Let's create a Stimulus controller and wire it up.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Stimulus Controller in Rails
&lt;/h3&gt;

&lt;p&gt;Use the generator to create a Stimulus controller.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bin/rails generate stimulus modal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the button is clicked, we need to get a reference to the &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; and call &lt;code&gt;showModal()&lt;/code&gt; on it. To keep the controller generic, we'll pass in the element's &lt;code&gt;id&lt;/code&gt; as a &lt;em&gt;param&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/javascript/controllers/modal_controller.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@hotwired/stimulus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Connects to data-controller="modal"&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dialog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dialog&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;dialog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showModal&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now decorate the button with the required &lt;code&gt;data-&lt;/code&gt; attributes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;%# app/views/support/show.html.erb %&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
  &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"modal"&lt;/span&gt;
  &lt;span class="na"&gt;data-action=&lt;/span&gt;&lt;span class="s"&gt;"modal#show"&lt;/span&gt;
  &lt;span class="na"&gt;data-modal-dialog-param=&lt;/span&gt;&lt;span class="s"&gt;"contact_details_modal"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Show contact details
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;dialog&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"contact_details_modal"&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"modal_title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;%# ... %&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dialog&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Refresh the page and the button should now work!&lt;/p&gt;

&lt;p&gt;There's an opportunity to remove some boilerplate code here. The &lt;code&gt;modal&lt;/code&gt; controller should always be attached to a button that shows the modal. We can remove the &lt;code&gt;data-action&lt;/code&gt; from the markup and set it in the controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/javascript/controllers/modal_controller.js&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;modal#show&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Styling the Modal
&lt;/h3&gt;

&lt;p&gt;The dialog looks fairly basic, so let's add some styles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* app/assets/stylesheets/application.scss */&lt;/span&gt;

&lt;span class="nt"&gt;dialog&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80vw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;::backdrop&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="mi"&gt;.2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nt"&gt;header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nt"&gt;h2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;::backdrop&lt;/code&gt; pseudo-element is a really great way to style the modal's background!&lt;/p&gt;

&lt;p&gt;Try using the &lt;code&gt;Tab&lt;/code&gt; key to navigate around the page and you'll see that the focus is trapped within the modal. It's worth reviewing the page &lt;a href="https://github.com/basecamp/accessibility/blob/master/Section-1/Autiditing-Accessibility-On-The-Web.md#review-the-page-using-voiceover-mac-or-nvda-win-screen-reader"&gt;with a screen reader&lt;/a&gt; as well.&lt;/p&gt;

&lt;p&gt;This is the simplest way to modally present a &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt;. Next, we'll look at a server-driven way to present modals: Turbo Frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turbo Frame Powered Modals for Ruby
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://turbo.hotwired.dev/reference/frames"&gt;Turbo Frames&lt;/a&gt; are a subset of the &lt;a href="https://turbo.hotwired.dev"&gt;Turbo&lt;/a&gt; library which is also under the Hotwire umbrella. It allows us to scope navigation to specific parts of a page, updating them in isolation from the rest of the page.&lt;/p&gt;

&lt;p&gt;We can use Turbo Frames to present a modal rendered from the server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up
&lt;/h3&gt;

&lt;p&gt;Let's add another button to display a modal &lt;em&gt;contact form&lt;/em&gt;. We'll need a controller and action to render the form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bin/rails generate controller support/tickets new create
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the auto-generated routes with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:support&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="ss"&gt;:tickets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:create&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll also need a global Turbo Frame to render modals, so let's put it in the main &lt;em&gt;application&lt;/em&gt; layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;%# app/views/layouts/application.html.erb %&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;%# ... %&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="ss"&gt;:remote_modal&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a link to show the contact form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;%# app/views/support/show.html.erb %&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
  &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"modal"&lt;/span&gt;
  &lt;span class="na"&gt;data-modal-dialog-param=&lt;/span&gt;&lt;span class="s"&gt;"contact_details_modal"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Show contact details
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="n"&gt;new_support_ticket_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;turbo_frame: :remote_modal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  Show contact form
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;%# ... %&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you're done!&lt;/p&gt;

&lt;h3&gt;
  
  
  Rendering and Presenting the Form
&lt;/h3&gt;

&lt;p&gt;Clicking the &lt;em&gt;Show contact form&lt;/em&gt; link will expect a &lt;code&gt;&amp;lt;turbo-frame&amp;gt;&lt;/code&gt; with &lt;code&gt;id="remote_modal"&lt;/code&gt; in the response and update the global Turbo Frame with its content. Fill in the view with the form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;%# app/views/support/tickets/new.html.erb %&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="ss"&gt;:remote_modal&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;dialog&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"contact_form_modal"&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"modal_title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"modal_title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Contact us
      &lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"dialog"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"close"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;X&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;

    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="n"&gt;support_tickets_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Your message"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text_area&lt;/span&gt; &lt;span class="ss"&gt;:message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;autofocus: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;button&lt;/span&gt; &lt;span class="s2"&gt;"Close"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;formmethod: :dialog&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;button&lt;/span&gt; &lt;span class="s2"&gt;"Send"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/dialog&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've now got a text area in the modal which is a focusable element. For accessibility, it should be focused by default when the modal is presented. The &lt;code&gt;autofocus&lt;/code&gt; attribute is used to accomplish this.&lt;/p&gt;

&lt;p&gt;Refresh the page and try clicking &lt;em&gt;Show contact form&lt;/em&gt;. Nothing will happen visually, but on inspecting the HTML, you'll notice the &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; has been rendered in the &lt;code&gt;remote_modal&lt;/code&gt; Turbo Frame. We haven't presented it yet, which is why it's invisible.&lt;/p&gt;

&lt;p&gt;We could render it with the &lt;code&gt;open&lt;/code&gt; attribute, but that would defeat the purpose as it'd be presented in a non-modal context without the modal accessibility features.&lt;/p&gt;

&lt;p&gt;Let's create another Stimulus controller to present the form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bin/rails generate stimulus remote_modal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/javascript/controllers/remote_modal_controller.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@hotwired/stimulus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Connects to data-controller="remote-modal"&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showModal&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And hook it up to the dialog:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;%# app/views/support/tickets/new.html.erb %&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="ss"&gt;:remote_modal&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;dialog&lt;/span&gt;
    &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"contact_form_modal"&lt;/span&gt;
    &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"modal_title"&lt;/span&gt;
    &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"remote-modal"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;%# ... %&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/dialog&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Refresh the page and try viewing the contact form again. This time it should work. We've just rendered a modal from the server!&lt;/p&gt;

&lt;p&gt;While this is quite convenient, it's not ideal to create a new Stimulus controller just to present a modal. There's another method we can use as well: Turbo Streaming Modals. We'll take a look at those in part two of this series.&lt;/p&gt;

&lt;h2&gt;
  
  
  Up Next: Turbo Streaming Modals
&lt;/h2&gt;

&lt;p&gt;In this post, we explored two different methods to present modals using Hotwire: Stimulus and Turbo Frames.&lt;/p&gt;

&lt;p&gt;In part two, we'll look at another way to present modals: using Turbo Streams.&lt;/p&gt;

&lt;p&gt;Until then, happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, &lt;a href="https://blog.appsignal.com/ruby-magic"&gt;subscribe to our Ruby Magic newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>How I manage my git history</title>
      <dc:creator>Ayush Newatia</dc:creator>
      <pubDate>Fri, 26 May 2023 11:28:19 +0000</pubDate>
      <link>https://dev.to/ayushn21/how-i-manage-my-git-history-236e</link>
      <guid>https://dev.to/ayushn21/how-i-manage-my-git-history-236e</guid>
      <description>&lt;p&gt;I'm generally a rather pedantic person and this is supercharged when it comes to managing the git history on my projects. I used GitHub's &lt;a href="https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges#squash-and-merge-your-commits"&gt;squash and merge&lt;/a&gt; for a while before &lt;a href="https://mastodon.social/@cdmwebs@ruby.social"&gt;Chris Moore&lt;/a&gt; taught me a few tricks.  &lt;/p&gt;

&lt;p&gt;I'm not a fan of &lt;em&gt;squash and merge&lt;/em&gt; because it squashes an entire Pull Request into a single commit, no matter how large it is. This means that rather large changes could live under a single commit. I believe the commit history should tell a linear story, as pretentious as that might sound.   &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--f3UUA56u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6msrq2ukks1h6bn13bih.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--f3UUA56u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6msrq2ukks1h6bn13bih.png" alt="" width="800" height="767"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A clean commit history also makes life easier for code reviewers. A glance at the commits should give an overview of the changes and viewing the diff for each individual commit should make logical sense, allowing the reviewer to peruse the PR in smaller chunks.   &lt;/p&gt;

&lt;p&gt;In the long term, it's useful to delve into a project's history and see how and why certain changes were main. A carefully curated git history makes this easier.   &lt;/p&gt;

&lt;p&gt;I primarily use two git features to manage my commit history: &lt;code&gt;git reset --mixed&lt;/code&gt; and &lt;code&gt;git rebase -i&lt;/code&gt; (&lt;a href="https://www.sitepoint.com/git-interactive-rebase-guide/"&gt;interactive rebase&lt;/a&gt;).  &lt;/p&gt;

&lt;h2&gt;
  
  
  Git reset
&lt;/h2&gt;

&lt;p&gt;Let's say we're working on a branch called &lt;code&gt;feature/dashboard&lt;/code&gt; which is based on the &lt;code&gt;main&lt;/code&gt; branch. While I'm building out a feature, my git history is a complete mess. It's usually something like:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WIP&lt;/li&gt;
&lt;li&gt;Some stuff&lt;/li&gt;
&lt;li&gt;Fix shit&lt;/li&gt;
&lt;li&gt;Complete dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yeah, really. That's definitely not shippable, so I run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git fetch
$ git reset --mixed origin/main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will reset the state of the branch to &lt;code&gt;main&lt;/code&gt; and retain all my changes in the working copy (the branch needs to be up to date with &lt;code&gt;main&lt;/code&gt; before this, use rebase or merge). I'll then commit them in a logical order to produce something like:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add new dashboard layout&lt;/li&gt;
&lt;li&gt;Add new assets&lt;/li&gt;
&lt;li&gt;Stimulus controller for drawer animations&lt;/li&gt;
&lt;li&gt;Create dashboard controller actions and views&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's more like it. A reviewer can just see the commit messages and instantly have an idea of what's changed. The diff for each individual commit also makes sense. Since we've rewritten the git history, we'll need to force push this branch using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git push --force
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, I'll have invariably forgotten to run Rubocop and any other linters used on the project. After running those, I'd need to add another commit with the fixes but that messes up the linear story. This is when I use interactive rebase.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Interactive rebase
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.sitepoint.com/git-interactive-rebase-guide/"&gt;Interactive rebase&lt;/a&gt; lets us pick and choose which commits to retain on the branch, and also lets us squash multiple commits together. Running &lt;code&gt;git rebase -i origin/main&lt;/code&gt; opens up a text editor containing the commits which exist on the current branch, but not on &lt;code&gt;main&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pick 6246a52ba Add new dashboard layout
pick 0fd58314b Add new assets
pick 25d5bafa3 Stimulus controller for drawer animations
pick 1432fc34a Create dashboard controller actions and views
pick cbf35b39f Rubocop and ESlint

# Rebase e88d9298b..cbf35b39f onto e83d9294b (3 commands)
#
# Commands:
# p, pick &amp;lt;commit&amp;gt; = use commit
# r, reword &amp;lt;commit&amp;gt; = use commit, but edit the commit message
# e, edit &amp;lt;commit&amp;gt; = use commit, but stop for amending
# s, squash &amp;lt;commit&amp;gt; = use commit, but meld into previous commit
# f, fixup [-C | -c] &amp;lt;commit&amp;gt; = like "squash" but keep only the previous
# commit's log message, unless -C is used, in which case
# keep only this commit's message; -c is same as -C but
# opens the editor
# x, exec &amp;lt;command&amp;gt; = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop &amp;lt;commit&amp;gt; = remove commit
# l, label &amp;lt;label&amp;gt; = label current HEAD with a name
# t, reset &amp;lt;label&amp;gt; = reset HEAD to a label
# m, merge [-C &amp;lt;commit&amp;gt; | -c &amp;lt;commit&amp;gt;] &amp;lt;label&amp;gt; [# &amp;lt;oneline&amp;gt;]
# create a merge commit using the original merge commit's
# message (or the oneline, if no original merge commit was
# specified); use -c &amp;lt;commit&amp;gt; to reword the commit message
# u, update-ref &amp;lt;ref&amp;gt; = track a placeholder for the &amp;lt;ref&amp;gt; to be updated
# to this position in the new commits. The &amp;lt;ref&amp;gt; is
# updated at the end of the rebase
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All the available commands are conveniently documented within this file. I'd now use the &lt;code&gt;fixup&lt;/code&gt; command to squash the linter fixes into one of the other commits where it makes logical sense. Closing the file completes the rebase. Once again, we've rewritten history so a force push is required: &lt;code&gt;git push --force&lt;/code&gt;.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Other uses of interactive rebase
&lt;/h2&gt;

&lt;p&gt;As a contractor, I often work on a fork of the main project, or off a separate branch rather than &lt;code&gt;main&lt;/code&gt; depending on my client's requirements. This means sometimes the base branch I'm working off has been rebased.   &lt;/p&gt;

&lt;p&gt;For example, if I'm working off an &lt;code&gt;integration&lt;/code&gt; branch and it was rebased to bring it up to date with &lt;code&gt;main&lt;/code&gt;, I need to now bring my local &lt;code&gt;integration&lt;/code&gt; branch in sync with the remote branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git fetch
$ git checkout integration
$ git reset --hard origin/integration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensure you don't have any changes on your local &lt;code&gt;integration&lt;/code&gt; branch when you do this or you will lose them. Next, I need to rebase my feature branch to bring it up to date with &lt;code&gt;integration&lt;/code&gt;. Since rebase rewrites history, there could be duplicate commits on the feature branch that have snuck in there because their hash changed after &lt;code&gt;integration&lt;/code&gt; was rebased. These need to be excluded these from the feature branch as it should only contain changes related to that feature.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git fetch
$ git checkout feature/dashboard
$ git rebase -i integration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This might now produce something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pick 2342bcaf3 Fix CI
pick 6246a52ba Add new dashboard layout
pick 0fd58314b Add new assets
pick 25d5bafa3 Stimulus controller for drawer animations
pick 1432fc34a Create dashboard controller actions and views
pick cbf35b39f Rubocop and ESlint

# Rebase e88d9298b..cbf35b39f onto e83d9294b (3 commands)
#

# ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait a sec, that &lt;code&gt;Fix CI&lt;/code&gt; commit has nothing do to with this work. That's snuck in because it lives in &lt;code&gt;integration&lt;/code&gt; and its hash changed during the rebase. To exclude it, delete that line and close the file so the branch is brought up to date and only contains the intended changes. It'll need a force push once again.  &lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Rewriting git history is risky. If you're unsure about what you're doing, ALWAYS create a backup copy of your branch before attempting destructive changes.  &lt;/p&gt;

&lt;p&gt;Never ever force push to branches used by your entire team unless you're all aware that this is common practice and know how to deal with it. If you're working alongside another developer on a feature branch, ensure you're on the same page regarding force pushing and communicate force pushes promptly so neither of you accidentally lose any work.  &lt;/p&gt;

&lt;p&gt;Despite having used git for over 10 years now, and this particular workflow involving interactive rebase for 3 years or so, I still don't really understand it properly. I've developed a feel for it with extended use. If you're a bit confused, don't worry about it, it confused the hell out of me initially and it still does, just to a lesser degree. You'll get the hang of it with practice. It's a great power tool to have in your arsenal.&lt;/p&gt;

</description>
      <category>git</category>
      <category>github</category>
    </item>
    <item>
      <title>Directing Turbo Native apps from the server</title>
      <dc:creator>Ayush Newatia</dc:creator>
      <pubDate>Thu, 01 Dec 2022 12:42:29 +0000</pubDate>
      <link>https://dev.to/ayushn21/directing-turbo-native-apps-from-the-server-187c</link>
      <guid>https://dev.to/ayushn21/directing-turbo-native-apps-from-the-server-187c</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was extracted and adapted from &lt;a href="https://railsandhotwirecodex.com"&gt;The Rails and Hotwire Codex&lt;/a&gt;. It also assumes some familiarity with Turbo Native.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When developing for the web, we can send the user to any location during a web request using an HTTP redirect (&lt;code&gt;3xx&lt;/code&gt; status codes). Turbo Native effectively wraps the website within native navigation. This means a redirect may not always do the trick.&lt;/p&gt;

&lt;p&gt;Let's take an example. A &lt;strong&gt;Login&lt;/strong&gt; screen is presented modally in the native apps. After a successful login, we want to redirect the user to the home page. In the app, we'd want to dismiss the modal to reveal the screen under it allowing the user to continue with whatever they were doing. A conventional redirect won't work, we need to tell the app to just dismiss the modal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Server driven native navigation
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;turbo-rails&lt;/code&gt; gem draws three routes which instruct the native app to do something. These routes don't return any meaningful content; the app is meant to intercept &lt;em&gt;visit proposals&lt;/em&gt; to these routes and implement logic to execute the instruction. The routes, and what they instruct, are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Route&lt;/th&gt;
&lt;th&gt;Instruction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/recede_historical_location&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The app should go back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/resume_historical_location&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The app should do nothing in terms of navigation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/refresh_historical_location&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The app should refresh the current page&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Check out the source code to see the &lt;a href="https://github.com/hotwired/turbo-rails/blob/main/config/routes.rb"&gt;routes&lt;/a&gt; and the &lt;a href="https://github.com/hotwired/turbo-rails/blob/main/app/controllers/turbo/native/navigation_controller.rb"&gt;actions they point to&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These routes are exclusively for the native apps and have no meaning on web. To help with this, &lt;code&gt;turbo-rails&lt;/code&gt; has methods to conditionally redirect a user if they're using the app. For example, instead of redirecting as below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;root_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: :see_other&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We would use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;recede_or_redirect_to&lt;/span&gt; &lt;span class="n"&gt;root_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: :see_other&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will redirect to &lt;code&gt;/&lt;/code&gt; on the web, and to &lt;code&gt;/recede_historical_location&lt;/code&gt; in the native apps. Check out all the available redirect methods &lt;a href="https://github.com/hotwired/turbo-rails/blob/main/app/controllers/turbo/native/navigation.rb"&gt;in the source code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Using these methods, the app can be directed to dismiss the login modal after logging in while still redirecting to the root path on web.&lt;/p&gt;

&lt;p&gt;Next, redirects to the aforementioned paths need to be intercepted and handled in the apps. We'll call these paths: &lt;em&gt;path directives&lt;/em&gt;, since they direct the app to do something.&lt;/p&gt;

&lt;h2&gt;
  
  
  Intercept and handle path directives
&lt;/h2&gt;

&lt;p&gt;Let's look at iOS. The following delegate method is triggered for every web request from the app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="kt"&gt;RoutingController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;SessionDelegate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;didProposeVisit&lt;/span&gt; &lt;span class="nv"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;VisitProposal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In here, we can check if a visit is a &lt;em&gt;path directive&lt;/em&gt; and act accordingly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="kt"&gt;RoutingController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;SessionDelegate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;didProposeVisit&lt;/span&gt; &lt;span class="nv"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;VisitProposal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isPathDirective&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;executePathDirective&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;executePathDirective&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;VisitProposal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isPathDirective&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"/recede_historical_location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nf"&gt;dismissOrPopViewController&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"/refresh_historical_location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nf"&gt;refreshWebView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;dismissOrPopViewController&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;refreshWebView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="kt"&gt;VisitProposal&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;isPathDirective&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"_historical_location"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That'll do the trick on iOS. &lt;/p&gt;

&lt;p&gt;Building a Turbo Native navigation system is non-trivial, so I've had to omit a lot of surrounding code to keep this post on point. &lt;a href="https://railsandhotwirecodex.com"&gt;My book&lt;/a&gt; fills in all the gaps if you're interested in digging deeper.&lt;/p&gt;

&lt;p&gt;Next, let's look at Android. Custom navigation is handled by creating an &lt;code&gt;interface&lt;/code&gt; which inherits from &lt;code&gt;TurboNavDestination&lt;/code&gt;. In this interface, the &lt;code&gt;shouldNavigateTo(newLocation: String)&lt;/code&gt; method is called for every web request. We can handle &lt;em&gt;path directives&lt;/em&gt; in here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;NavDestination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TurboNavDestination&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;shouldNavigateTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newLocation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;isPathDirective&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newLocation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;executePathDirective&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newLocation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;false&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;executePathDirective&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s"&gt;"/recede_historical_location"&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;navigateUp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="s"&gt;"/refresh_historical_location"&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;isPathDirective&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"_historical_location"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;That's how Turbo Native powered apps can be controller from the server! This gives us an immense amount of flexibility and extensibility without ever deploying app updates.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you liked this post, check out my book, &lt;a href="https://railsandhotwirecodex.com"&gt;The Rails and Hotwire Codex&lt;/a&gt;, to level-up your Rails and Hotwire skills! It'll teach you how to build a Turbo Native navigation system and fills in the gaps in this blog post.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>turbo</category>
      <category>ios</category>
      <category>android</category>
      <category>rails</category>
    </item>
    <item>
      <title>Turbo Streams meets Action Cable</title>
      <dc:creator>Ayush Newatia</dc:creator>
      <pubDate>Wed, 16 Nov 2022 16:30:47 +0000</pubDate>
      <link>https://dev.to/ayushn21/turbo-streams-meets-action-cable-4poj</link>
      <guid>https://dev.to/ayushn21/turbo-streams-meets-action-cable-4poj</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was extracted and adapted from &lt;a href="https://railsandhotwirecodex.com"&gt;The Rails and Hotwire Codex&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://turbo.hotwired.dev/handbook/streams"&gt;Turbo Streams&lt;/a&gt; are a great match-up for Action Cable as it makes it easy to broadcast DOM updates to the browser over WebSockets. The &lt;a href="http://github.com/hotwired/turbo-rails"&gt;&lt;code&gt;turbo-rails&lt;/code&gt;&lt;/a&gt; gem integrates with Action Cable to facilitate such updates.&lt;/p&gt;

&lt;p&gt;In this blog post, we'll look at how this integration works. Starting with code examples to establish a connection and broadcast Turbo Streams, we'll then dig into the individual client and server components of the integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  An overview
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;turbo-rails&lt;/code&gt; contains a &lt;a href="https://javascript.info/custom-elements"&gt;JavaScript custom element&lt;/a&gt; which encapsulates the logic to create an Action Cable connection, subscribe to a channel and execute received Turbo Streams. On the server-side, it provides a default channel and a plethora of helper methods to broadcast data based on models.&lt;/p&gt;

&lt;p&gt;On the client, an Action Cable connection and subscription is established using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream_from&lt;/span&gt; &lt;span class="vi"&gt;@conversation&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will render a &lt;code&gt;&amp;lt;turbo-cable-stream-source&amp;gt;&lt;/code&gt; element which subscribes to a stream for the &lt;code&gt;@conversation&lt;/code&gt; object. Turbo Streams can then be broadcast to it using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Turbo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;StreamsChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;broadcast_action_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="vi"&gt;@conversation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;action: :append&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;target: &lt;/span&gt;&lt;span class="n"&gt;dom_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@conversation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:messages&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s2"&gt;"messages/message"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are helper methods to broadcast the stock Turbo Stream actions. The above snippet can be rewritten as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Turbo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;StreamsChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;broadcast_append_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="vi"&gt;@conversation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;target: &lt;/span&gt;&lt;span class="n"&gt;dom_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@conversation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:messages&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s2"&gt;"messages/message"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also broadcast a Turbo Stream template containing multiple actions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Turbo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;StreamsChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;broadcast_render_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="vi"&gt;@conversation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;template: &lt;/span&gt;&lt;span class="s2"&gt;"messages/create"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All the above examples render templates and broadcast them synchronously. This isn't usually desirable. It should be offloaded to a background job so the request-response cycle isn't held up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# enqueues a `Turbo::Streams::ActionBroadcastJob`&lt;/span&gt;
&lt;span class="no"&gt;Turbo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;StreamsChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;broadcast_append_later_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="vi"&gt;@conversation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;target: &lt;/span&gt;&lt;span class="n"&gt;dom_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@conversation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:messages&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s2"&gt;"messages/message"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# enqueues a `Turbo::Streams::BroadcastJob`&lt;/span&gt;
&lt;span class="no"&gt;Turbo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;StreamsChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;broadcast_render_later_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="vi"&gt;@conversation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;template: &lt;/span&gt;&lt;span class="s2"&gt;"messages/create"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check out the docs for all available helpers: &lt;a href="https://rubydoc.info/github/hotwired/turbo-rails/Turbo/Streams/Broadcasts"&gt;https://rubydoc.info/github/hotwired/turbo-rails/Turbo/Streams/Broadcasts&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In addition to this, there's also a &lt;a href="https://rubydoc.info/github/hotwired/turbo-rails/Turbo/Broadcastable"&gt;&lt;code&gt;Broadcastable&lt;/code&gt;&lt;/a&gt; concern which is &lt;code&gt;include&lt;/code&gt;d in Active Record. It applies Rails conventions to succinctly broadcast model-specific Turbo Streams. Some example use cases are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Broadcasts an `append` action containing the partial&lt;/span&gt;
&lt;span class="c1"&gt;# `conversations/conversation` targeted at the&lt;/span&gt;
&lt;span class="c1"&gt;# ID `conversations`.&lt;/span&gt;
&lt;span class="no"&gt;Conversation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;broadcast_append&lt;/span&gt;

&lt;span class="c1"&gt;# The update action targets the specific model's HTML element.&lt;/span&gt;
&lt;span class="c1"&gt;# In this case, it will target `conversation_1`. The content&lt;/span&gt;
&lt;span class="c1"&gt;# will be the partial `conversations/conversation`.&lt;/span&gt;
&lt;span class="no"&gt;Conversation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;broadcast_update&lt;/span&gt;

&lt;span class="c1"&gt;# The partial can be explicitly defined if required.&lt;/span&gt;
&lt;span class="no"&gt;Conversation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;broadcast_append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s2"&gt;"messages/message"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I recommend taking a peek at the docs to familiarize yourself with all the model-based broadcast methods: &lt;a href="https://rubydoc.info/github/hotwired/turbo-rails/Turbo/Broadcastable"&gt;https://rubydoc.info/github/hotwired/turbo-rails/Turbo/Broadcastable&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The broadcast helpers in &lt;code&gt;Turbo::StreamsChannel&lt;/code&gt;, which we looked at earlier, are all available in &lt;code&gt;Broadcastable&lt;/code&gt; as well.&lt;/p&gt;

&lt;p&gt;Under the hood, all the broadcast helpers use &lt;code&gt;ActionCable.server.broadcast(...)&lt;/code&gt;. Hence, they just provide some syntactic sugar on top of Action Cable.&lt;/p&gt;

&lt;p&gt;Next, let's take a closer look at the custom element which receives and processes Turbo Streams.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turbo Cable Stream Source element
&lt;/h2&gt;

&lt;p&gt;As mentioned earlier, this element is rendered using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream_from&lt;/span&gt; &lt;span class="vi"&gt;@conversation&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will render as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;turbo-cable-stream-source&lt;/span&gt;
  &lt;span class="na"&gt;channel=&lt;/span&gt;&lt;span class="s"&gt;"Turbo::StreamsChannel"&lt;/span&gt;
  &lt;span class="na"&gt;signed-stream-name=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/turbo-cable-stream-source&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This element is underpinned by the class &lt;a href="https://github.com/hotwired/turbo-rails/blob/main/app/javascript/turbo/cable_stream_source_element.js"&gt;&lt;code&gt;TurboCableStreamSourceElement&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The subscription is established using the two HTML attributes. We can specify a custom channel if the default one isn't suitable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream_from&lt;/span&gt; &lt;span class="vi"&gt;@conversation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;channel: &lt;/span&gt;&lt;span class="s2"&gt;"ConversationsChannel"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;em&gt;signed stream name&lt;/em&gt; is generated from the &lt;code&gt;@conversation&lt;/code&gt; object. You can use multiple arguments for this as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream_from&lt;/span&gt; &lt;span class="vi"&gt;@conversation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;channel: &lt;/span&gt;&lt;span class="s2"&gt;"ConversationsChannel"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's dig into how the stream names are generated.&lt;/p&gt;

&lt;h3&gt;
  
  
  A closer look at stream names
&lt;/h3&gt;

&lt;p&gt;Any type of object can used to generate the stream name as long as it responds to &lt;code&gt;to_gid_param&lt;/code&gt; or &lt;code&gt;to_param&lt;/code&gt;. Hence, it's usually an Active Record object, string or symbol.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;to_gid_param&lt;/code&gt; generates the &lt;a href="https://github.com/rails/globalid"&gt;Global ID&lt;/a&gt; for a model and then Base64 encodes it. For strings, &lt;code&gt;to_param&lt;/code&gt; returns the object itself. For symbols, it returns the object's string representation.&lt;/p&gt;

&lt;p&gt;The stream name for a &lt;code&gt;Conversation&lt;/code&gt; object would look something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Z2lkOi8vcGlhenphL0NvbnZlcnNhdGlvbi8x
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Base64 decoding the above value will reveal the object's Global ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Z2lkOi8vcGlhenphL0NvbnZlcnNhdGlvbi8x"&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlsafe_decode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt; "gid://piazza/Conversation/1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The stream name is run through a &lt;code&gt;MessageVerifier&lt;/code&gt; to obtain the &lt;em&gt;signed stream name&lt;/em&gt; which is rendered in the HTML attribute. This ensures the stream name can't be tampered with on the client.&lt;/p&gt;

&lt;p&gt;When using multiple arguments, each one will have &lt;code&gt;to_gid_param&lt;/code&gt; or &lt;code&gt;to_param&lt;/code&gt; called on it individually. The return values are then joined with a &lt;code&gt;:&lt;/code&gt;. The stream name for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream_from&lt;/span&gt; &lt;span class="vi"&gt;@conversation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;channel: &lt;/span&gt;&lt;span class="s2"&gt;"ConversationsChannel"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;will look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Z2lkOi8vcGlhenphL0NvbnZlcnNhdGlvbi8x:messages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the signed stream name and channel name, the custom element establishes an Action Cable connection and subscribes to the desired stream, ready to receive Turbo Streams over a WebSocket.&lt;/p&gt;

&lt;h2&gt;
  
  
  Processing Turbo Streams
&lt;/h2&gt;

&lt;p&gt;The easiest way to execute Turbo Streams is by adding them to the DOM. Another way to is by using the &lt;code&gt;connectStreamSource&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;connectStreamSource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;disconnectStreamSource&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@hotwired/turbo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="nx"&gt;connectStreamSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="nx"&gt;disconnectStreamSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;source&lt;/code&gt; can be any object capable of dispatching &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent"&gt;&lt;code&gt;MessageEvent&lt;/code&gt;&lt;/a&gt;s. Turbo will listen for these events and execute the Turbo Streams they contain.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;TurboCableStreamSourceElement&lt;/code&gt; connects itself as a &lt;em&gt;stream source&lt;/em&gt; when it's connected to the DOM. When it receives a Turbo Stream over the WebSocket connection, it dispatches a &lt;code&gt;MessageEvent&lt;/code&gt; containing it, which Turbo executes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The default channel
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;turbo-rails&lt;/code&gt; has a default channel called &lt;a href="https://github.com/hotwired/turbo-rails/blob/main/app/channels/turbo/streams_channel.rb"&gt;&lt;code&gt;Turbo::StreamsChannel&lt;/code&gt;&lt;/a&gt; to handle broadcasts and subscriptions. We briefly encountered it while discussing the broadcast helpers. It verifies the signed stream name and subscribes the client to the appropriate stream.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Turbo::StreamsChannel&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionCable&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Channel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;Turbo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Streams&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Broadcasts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Turbo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Streams&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;StreamName&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Turbo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Streams&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;StreamName&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ClassMethods&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subscribed&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;stream_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;verified_stream_name_from_params&lt;/span&gt;
      &lt;span class="n"&gt;stream_from&lt;/span&gt; &lt;span class="n"&gt;stream_name&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;reject&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is generic code and hence contains no authorization logic. I highly recommend writing your own channel based off &lt;code&gt;Turbo::StreamsChannel&lt;/code&gt; where you run authorization checks before approving the stream.&lt;/p&gt;

&lt;p&gt;Most of the channel's logic in encapsulated in concerns making it easy to reuse in your own channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;That's the Turbo Streams integration with Action Cable in a nutshell. It packs in immense power with relatively little code. The docs for &lt;code&gt;turbo-rails&lt;/code&gt; are sparse at best, but I recommend keeping the YARD docs handy for reference: &lt;a href="https://rubydoc.info/gems/turbo-rails"&gt;https://rubydoc.info/gems/turbo-rails&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you liked this post, check out my book, &lt;a href="https://railsandhotwirecodex.com"&gt;The Rails and Hotwire Codex&lt;/a&gt;, to level-up your Rails and Hotwire skills!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rails</category>
      <category>hotwire</category>
      <category>websockets</category>
      <category>turbo</category>
    </item>
    <item>
      <title>Testing controller concerns in Rails</title>
      <dc:creator>Ayush Newatia</dc:creator>
      <pubDate>Fri, 28 Oct 2022 17:14:28 +0000</pubDate>
      <link>https://dev.to/ayushn21/testing-controller-concerns-in-rails-12m5</link>
      <guid>https://dev.to/ayushn21/testing-controller-concerns-in-rails-12m5</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was extracted and adapted from &lt;a href="https://railsandhotwirecodex.com"&gt;The Rails and Hotwire Codex&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://api.rubyonrails.org/classes/ActiveSupport/Concern.html"&gt;Concerns&lt;/a&gt; are a great way to organize and de-duplicate code in Rails. &lt;/p&gt;

&lt;p&gt;One of my favorite use cases is to abstract everything out of the &lt;code&gt;ApplicationController&lt;/code&gt; into concerns. This begs the question: &lt;em&gt;how do you test these concerns?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The approach for testing concerns is a bit of a judgement call as they're always &lt;em&gt;mixed in&lt;/em&gt; with a class and aren't used on their own. Ideally we'd write tests to verify the behavior of a class which would also test the concern in the bargain.&lt;/p&gt;

&lt;p&gt;But what if the concern adds the same functionality to a large number of classes? For example, a concern which &lt;code&gt;Authenticate&lt;/code&gt;s every request. It isn't pragmatic to test each and every controller action for authentication logic. In such cases, I believe the best approach is to test the concern in isolation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test harness for controller concerns
&lt;/h2&gt;

&lt;p&gt;Let's stick with the hypothetical example of a concern called &lt;code&gt;Authenticate&lt;/code&gt;. It's usable only within a controller, so we'll need a &lt;em&gt;test harness&lt;/em&gt; to test it independently.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;support/&lt;/code&gt; folder under &lt;code&gt;test/&lt;/code&gt; and create some files for the test harness within it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mkdir test/support
$ touch test/support/test_controller.rb
$ touch test/support/routes_helper.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;TestController&lt;/code&gt; doesn't need to do much by itself. It will be subclassed in individual tests. Each action will render the name of the controller and action which can then be asserted in the test cases.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;

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

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;default_render&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;plain: &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:controller&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;#&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:action&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need a way to draw some test-only routes to point to &lt;code&gt;TestController&lt;/code&gt; subclasses in test cases. The test routes will be scoped to &lt;code&gt;/test&lt;/code&gt; so they don't clash with existing routes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# test/support/routes_helper.rb&lt;/span&gt;

&lt;span class="c1"&gt;# Ensure you call `reload_routes!` in your test's `teardown`&lt;/span&gt;
&lt;span class="c1"&gt;# to erase all the routes drawn by your test case.&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;RoutesHelpers&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw_test_routes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Don't clear routes when calling `Rails.application.routes.draw`&lt;/span&gt;
    &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disable_clear_and_finalize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;

    &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;instance_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reload_routes!&lt;/span&gt;
    &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reload_routes!&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;draw_test_routes&lt;/code&gt; helper takes a block which is executed inside the context of &lt;code&gt;Rails.application.routes.draw&lt;/code&gt;. In essence, it's doing the exact same thing as &lt;code&gt;config/routes.rb&lt;/code&gt;, but in the context of the test suite.&lt;/p&gt;

&lt;p&gt;Files under the &lt;code&gt;test/&lt;/code&gt; folder in Rails are not &lt;em&gt;autoloaded&lt;/em&gt;, so these files need to be &lt;code&gt;require&lt;/code&gt;d and &lt;code&gt;include&lt;/code&gt;d manually.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# test/test_helper.rb&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"support"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"**"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"*.rb"&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ActionDispatch::IntegrationTest&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;RoutesHelpers&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the test harness in place, we can now write some tests for the &lt;code&gt;Authenticate&lt;/code&gt; concern.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ touch test/controllers/concerns/authenticate_test.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'test_helper'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthenticateTestsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;TestController&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Authenticate&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthenticateTest&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;IntegrationTest&lt;/span&gt;
  &lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;draw_test_routes&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="ss"&gt;:authenticate_test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:create&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:show&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:edit&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;teardown&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;reload_routes!&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

   &lt;span class="c1"&gt;# ...&lt;/span&gt;
   &lt;span class="c1"&gt;# Test cases go here ...&lt;/span&gt;
   &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test-specific &lt;code&gt;AuthenticateTestsController&lt;/code&gt; strips away any peripheral functionality and enables us to focus on testing the code in &lt;code&gt;Authenticate&lt;/code&gt;. It can now be tested just like any other controller! &lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you liked this post, check out my book, &lt;a href="https://railsandhotwirecodex.com"&gt;The Rails and Hotwire Codex&lt;/a&gt;, to level-up your Rails and Hotwire skills!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>testing</category>
    </item>
    <item>
      <title>Rails system tests for multiple screen sizes</title>
      <dc:creator>Ayush Newatia</dc:creator>
      <pubDate>Wed, 19 Oct 2022 14:41:10 +0000</pubDate>
      <link>https://dev.to/ayushn21/rails-system-tests-for-multiple-screen-sizes-29o6</link>
      <guid>https://dev.to/ayushn21/rails-system-tests-for-multiple-screen-sizes-29o6</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was extracted and adapted from &lt;a href="https://railsandhotwirecodex.com"&gt;The Rails and Hotwire Codex&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Rails system tests simulate a user's actions in a web browser so it's akin to the app's real world usage. In a responsive app, this means testing on multiple screen sizes as well.&lt;/p&gt;

&lt;p&gt;Capybara, the tool used by system tests under the hood, has a method to resize the current window during a test run. But, since the tests are not run in a set order, any test that resizes the window needs to restore the size after it's done. Otherwise, subsequent tests run in that window could fail.&lt;/p&gt;

&lt;p&gt;Let's take a look at how to write a set of system tests for mobile screens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mobile system tests
&lt;/h2&gt;

&lt;p&gt;Create a subclass of &lt;code&gt;ApplicationSystemTestCase&lt;/code&gt; specifically for mobile. It will resize the window in the &lt;code&gt;setup&lt;/code&gt; and &lt;code&gt;teardown&lt;/code&gt; methods that are invoked before and after each test case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"test_helper"&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApplicationSystemTestCase&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SystemTestCase&lt;/span&gt;
  &lt;span class="no"&gt;WINDOW_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1400&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;driven_by&lt;/span&gt; &lt;span class="ss"&gt;:selenium&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;using: :chrome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;screen_size: &lt;/span&gt;&lt;span class="no"&gt;WINDOW_SIZE&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MobileSystemTestCase&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationSystemTestCase&lt;/span&gt;
  &lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="n"&gt;root_path&lt;/span&gt;
    &lt;span class="n"&gt;current_window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;375&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;812&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;teardown&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;current_window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="no"&gt;ApplicationSystemTestCase&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WINDOW_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can use this new base class to write tests aimed at mobile screens. Let's say the navigation bar shows a burger menu only on mobile. We can test that as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"application_system_test_case"&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Mobile&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NavigationBarTest&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;MobileSystemTestCase&lt;/span&gt;
    &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"can access sign up page via burger menu"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="n"&gt;root_path&lt;/span&gt;

      &lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;".navbar-burger"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;
      &lt;span class="n"&gt;click_on&lt;/span&gt; &lt;span class="s2"&gt;"Sign Up"&lt;/span&gt;

      &lt;span class="n"&gt;assert_current_path&lt;/span&gt; &lt;span class="n"&gt;sign_up_path&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"can access login page via burger menu"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="n"&gt;root_path&lt;/span&gt;

      &lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;".navbar-burger"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;
      &lt;span class="n"&gt;click_on&lt;/span&gt; &lt;span class="s2"&gt;"Log in"&lt;/span&gt;

      &lt;span class="n"&gt;assert_current_path&lt;/span&gt; &lt;span class="n"&gt;login_path&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running the system tests will now resize the window for the above two tests and restore the size back to desktop after they're done!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ bin/rails test:system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;If you liked this post, check out my book, &lt;a href="https://railsandhotwirecodex.com"&gt;The Rails and Hotwire Codex&lt;/a&gt;, to level-up your Rails and Hotwire skills!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>capybara</category>
      <category>testing</category>
    </item>
    <item>
      <title>Custom error pages in Rails</title>
      <dc:creator>Ayush Newatia</dc:creator>
      <pubDate>Thu, 06 Oct 2022 13:07:10 +0000</pubDate>
      <link>https://dev.to/ayushn21/custom-error-pages-in-rails-4i43</link>
      <guid>https://dev.to/ayushn21/custom-error-pages-in-rails-4i43</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was extracted and adapted from &lt;a href="https://railsandhotwirecodex.com" rel="noopener noreferrer"&gt;The Rails and Hotwire Codex&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When something goes wrong in Rails, the user sees a rather boring default error page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ci885mioj4f88adokea.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ci885mioj4f88adokea.png" alt="The default Rails 404 error page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This page lives in the &lt;code&gt;/public&lt;/code&gt; folder and hence isn't rendered through the Rails stack. &lt;/p&gt;

&lt;p&gt;To jazz up this page a bit, we'll create a controller to render errors so the Rails infrastructure can be used.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up
&lt;/h2&gt;

&lt;p&gt;A configuration change needs to be made so public facing errors are rendered in development rather than the exception and stack trace.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# config/environments/development.rb&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"active_support/core_ext/integer/time"&lt;/span&gt;

&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;consider_all_requests_local&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Create the controller.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ bin/rails g controller errors --no-helper --no-test-framework


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This controller will have a single &lt;code&gt;show&lt;/code&gt; action where we'll extract the error code for the raised exception and render the appropriate view. Error codes &lt;code&gt;403&lt;/code&gt;, &lt;code&gt;404&lt;/code&gt; and &lt;code&gt;500&lt;/code&gt; will have dedicated error pages and we'll fall back to the &lt;code&gt;404&lt;/code&gt; page for everything else. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ErrorsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;layout&lt;/span&gt; &lt;span class="s2"&gt;"error"&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
    &lt;span class="vi"&gt;@exception&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"action_dispatch.exception"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="vi"&gt;@status_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;try&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:status_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
                   &lt;span class="no"&gt;ActionDispatch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ExceptionWrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@exception&lt;/span&gt;
                  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;status_code&lt;/span&gt;

    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="n"&gt;view_for_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@status_code&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="vi"&gt;@status_code&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;view_for_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;supported_error_codes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"404"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;supported_error_codes&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="mi"&gt;403&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"403"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;404&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"404"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"500"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you can see, we're using a bespoke &lt;code&gt;"error"&lt;/code&gt; layout as well. These pages will be simple and won't need the usual baggage from the application layout. &lt;/p&gt;

&lt;p&gt;Create the new error layout.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ touch app/views/layouts/error.html.erb


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;

&lt;span class="c"&gt;&amp;lt;%# app/views/layouts/error.html.erb %&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="s2"&gt;"layouts/head"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Create the views and fill them in as you wish.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ touch app/views/errors/403.html.erb
$ touch app/views/errors/404.html.erb
$ touch app/views/errors/500.html.erb


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We need to now tell Rails to use this controller to render errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exceptions app
&lt;/h2&gt;

&lt;p&gt;Rails provides a hook to render custom errors through a configuration property called &lt;code&gt;exceptions_app&lt;/code&gt;. This property has to be assigned a &lt;a href="https://thoughtbot.com/upcase/videos/rack" rel="noopener noreferrer"&gt;Rack app&lt;/a&gt; which is invoked when an exception is raised.&lt;/p&gt;

&lt;p&gt;Every Rails controller action is actually its own Rack application! The Rack endpoint is returned by the &lt;code&gt;action&lt;/code&gt; method on a controller class.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;MyApp&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_defaults&lt;/span&gt; &lt;span class="mf"&gt;7.0&lt;/span&gt;

    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exceptions_app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="no"&gt;ErrorsController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:show&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Restart your server and then try visiting a page that doesn't exist. Or manually trigger an error in a controller action. You'll see your custom error pages in action!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you liked this post, check out my book, &lt;a href="https://railsandhotwirecodex.com" rel="noopener noreferrer"&gt;The Rails and Hotwire Codex&lt;/a&gt;, to level-up your Rails and Hotwire skills!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Remote debugging in Rails 7</title>
      <dc:creator>Ayush Newatia</dc:creator>
      <pubDate>Fri, 23 Sep 2022 15:04:11 +0000</pubDate>
      <link>https://dev.to/ayushn21/remote-debugging-in-rails-7-49nh</link>
      <guid>https://dev.to/ayushn21/remote-debugging-in-rails-7-49nh</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was extracted and adapted from &lt;a href="https://railsandhotwirecodex.com"&gt;The Rails and Hotwire Codex&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Rails 7 includes the &lt;a href="http://github.com/ruby/debug"&gt;official Ruby debugger&lt;/a&gt;. It also uses &lt;a href="http://blog.daviddollar.org/2011/05/06/introducing-foreman.html"&gt;Foreman&lt;/a&gt; to orchestrate multiple processes in development. This way you can run the Rails server along with processes to watch and compile your frontend assets using a single command.&lt;/p&gt;

&lt;p&gt;Annoyingly, this makes debugging trickier. You can't just add a breakpoint in your app and run commands in the console. The same Terminal window is running multiple processes, so won't always be interactive.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;debug&lt;/code&gt; gem supports remote debugging which is useful in such cases. Using this setup, an external, independent debugger process attaches to another &lt;em&gt;debuggee&lt;/em&gt; Ruby process.&lt;/p&gt;

&lt;p&gt;Enable remote debugging in your Rails app by adding the below lines to your &lt;code&gt;application.rb&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"rails/all"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;defined?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;development?&lt;/span&gt;
  &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"debug/open_nonstop"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We verify the Rails environment is development and that the &lt;code&gt;Rails::Server&lt;/code&gt; constant is defined before enabling remote debugging. The latter check ensures remote debugging is enabled only when the app runs in the context of a web server. It shouldn't be enabled in other contexts like the Rails console or a background job processor like Sidekiq.&lt;/p&gt;

&lt;p&gt;Now when you start the Rails server in development, the &lt;code&gt;debug&lt;/code&gt; gem will open a &lt;a href="https://en.wikipedia.org/wiki/Unix_domain_socket"&gt;UNIX domain socket&lt;/a&gt; which the debugger process can connect to.&lt;/p&gt;

&lt;p&gt;Start the server using &lt;code&gt;bin/dev&lt;/code&gt; and in another Terminal window, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ bundle exec rdbg -a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the Rails server is the only debuggable process running (it should be), the debugger will connect. You can add then breakpoints in the app and run commands in the debugger process!&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Ctrl+D&lt;/code&gt; will exit the debugger and leave the Rails server running. I recommend doing this after you've finished debugging. Check out the docs for the full list of available commands: &lt;a href="https://github.com/ruby/debug#control-flow"&gt;https://github.com/ruby/debug#control-flow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: &lt;em&gt;You can also use the statement &lt;code&gt;require "debug/open"&lt;/code&gt; instead of &lt;code&gt;require "debug/open_nonstop"&lt;/code&gt;. The downside here is the program will be paused on launch until you attach the debugger and &lt;code&gt;continue&lt;/code&gt; it. This can get very fiddly given how often the server needs to be restarted during development.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you liked this post, check out my book, &lt;a href="https://railsandhotwirecodex.com"&gt;The Rails and Hotwire Codex&lt;/a&gt;, to level-up your Rails and Hotwire skills!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>webdev</category>
    </item>
    <item>
      <title>HTTP redirects in a Turbo-Rails app</title>
      <dc:creator>Ayush Newatia</dc:creator>
      <pubDate>Thu, 15 Sep 2022 12:39:27 +0000</pubDate>
      <link>https://dev.to/ayushn21/http-redirects-in-a-turbo-rails-app-43jc</link>
      <guid>https://dev.to/ayushn21/http-redirects-in-a-turbo-rails-app-43jc</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was extracted and adapted from &lt;a href="https://railsandhotwirecodex.com"&gt;The Rails and Hotwire Codex&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In Rails, a conventional redirect in a controller action looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;root_path&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sends a response with an HTTP status code of &lt;code&gt;302 Found&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href="http://turbo.hotwired.dev"&gt;Turbo&lt;/a&gt; &lt;a href="https://turbo.hotwired.dev/handbook/drive#redirecting-after-a-form-submission"&gt;docs&lt;/a&gt;, however, state that the response to a form submission must be &lt;code&gt;303 See Other&lt;/code&gt; unless something goes wrong.&lt;/p&gt;

&lt;p&gt;The reason behind this is the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302"&gt;spec&lt;/a&gt; for &lt;code&gt;302 Found&lt;/code&gt; states that the redirected request should use the same HTTP method as the original request. Due to legacy reasons, the &lt;a href="https://javascript.info/fetch"&gt;&lt;code&gt;fetch&lt;/code&gt;&lt;/a&gt; (used by Turbo under the hood) implementation in most browsers will respond to a &lt;code&gt;302 Found&lt;/code&gt; response from a &lt;code&gt;POST&lt;/code&gt; request by issuing a &lt;code&gt;GET&lt;/code&gt; request to the redirected path. For all other HTTP methods, it will use the same method as the original request when redirecting.&lt;/p&gt;

&lt;p&gt;Responding with a &lt;code&gt;303 See Other&lt;/code&gt; status ensures that the method used for the redirect is always &lt;code&gt;GET&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;root_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: :see_other&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This anomaly with redirect codes isn't a massive problem in Rails because Rails forms use the only two browser native methods: &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;POST&lt;/code&gt;. Forms with different methods are rendered to use &lt;code&gt;POST&lt;/code&gt; with the method inserted as a hidden field which is then parsed by Rails before the request hits a controller. &lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="n"&gt;posts_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;method: :put&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text_field&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;will render&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/posts"&lt;/span&gt; &lt;span class="na"&gt;accept-charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;  
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"_method"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"put"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"authenticity_token"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"commit"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Save"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, this could potentially change as Rails integrates more tightly with Turbo. There are already a couple of use cases where &lt;code&gt;302&lt;/code&gt; redirects can be problematic.&lt;/p&gt;

&lt;p&gt;Turbo submits the form using the method specified in the &lt;code&gt;formmethod&lt;/code&gt; attribute when it's defined. The below form would submit using a &lt;code&gt;PUT&lt;/code&gt; request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="n"&gt;posts_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text_field&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt; &lt;span class="ss"&gt;formmethod: :put&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your controller redirected using a &lt;code&gt;302 Found&lt;/code&gt;, that would result in another &lt;code&gt;PUT&lt;/code&gt; request to the redirected path.&lt;/p&gt;

&lt;p&gt;Since Turbo intercepts form submissions, you can use any HTTP method in a &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;'s &lt;code&gt;method&lt;/code&gt; attribute, not just &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;POST&lt;/code&gt;. This form will also submit with a &lt;code&gt;PUT&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/posts"&lt;/span&gt; &lt;span class="na"&gt;accept-charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"put"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;  
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Save"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rails form helpers won't render this, but it's good to know what's possible!&lt;/p&gt;

&lt;p&gt;As such, I believe it's good practice to ALWAYS redirect with a &lt;code&gt;303 See Other&lt;/code&gt; status code when using Turbo in a Rails app.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you liked this post, check out my book, &lt;a href="https://railsandhotwirecodex.com"&gt;The Rails and Hotwire Codex&lt;/a&gt;, to level-up your Rails and Hotwire skills!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rails</category>
      <category>hotwire</category>
      <category>turbo</category>
      <category>http</category>
    </item>
  </channel>
</rss>
