<?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>Forem: Jon Yurek</title>
    <description>The latest articles on Forem by Jon Yurek (@jyurek).</description>
    <link>https://forem.com/jyurek</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%2F838871%2F1b7bdd16-8a87-4632-be3e-b09ff76530e0.jpeg</url>
      <title>Forem: Jon Yurek</title>
      <link>https://forem.com/jyurek</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jyurek"/>
    <language>en</language>
    <item>
      <title>Hotwired Modals</title>
      <dc:creator>Jon Yurek</dc:creator>
      <pubDate>Fri, 29 Apr 2022 19:19:52 +0000</pubDate>
      <link>https://forem.com/thegnarco/hotwired-modals-196a</link>
      <guid>https://forem.com/thegnarco/hotwired-modals-196a</guid>
      <description>&lt;p&gt;We can use &lt;a href="https://hotwired.dev/"&gt;Hotwire&lt;/a&gt; -- specifically, &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 create some modals that present a nice, dynamic user experience. And they can do this while staying in a mutli-page app structure that Rails is so good at.&lt;/p&gt;

&lt;p&gt;First off, we need to know that, with Turbo, you can load a page with a &lt;code&gt;turbo-frame&lt;/code&gt; element and Turbo will take the element from the newly loaded page and &lt;a href="https://turbo.hotwired.dev/handbook/frames"&gt;inject its contents into the current page&lt;/a&gt;. It works just like &lt;code&gt;frame&lt;/code&gt;s and &lt;code&gt;iframe&lt;/code&gt;s work, but... way better.&lt;/p&gt;

&lt;p&gt;So, what we're talking about here is an index page and an edit page -- oh, and a small Stimulus controller to pop up the modal. The index page lists out your Resources and has an empty modal section. The edit page looks exactly like the index page, but has an edit form in the modal section.&lt;/p&gt;

&lt;p&gt;For the purpose of this example, let's call the Resource a "Gadget", just so it doesn't have a name that's already a core Rails concept.&lt;/p&gt;

&lt;p&gt;On the index page, you have your Gadgets, and you want to list your Gadgets in a cool table (for however cool a table can be, but I digress). You'll likely have something like this in your &lt;code&gt;app/views/gadgets/index.html.erb&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;&amp;lt;table id="gadgets-table"&amp;gt;
  &amp;lt;% @gadgets.each do |gadget| %&amp;gt;
    &amp;lt;tr&amp;gt;
      &amp;lt;td&amp;gt;&amp;lt;%= gadget.name %&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;td&amp;gt;&amp;lt;%= link_to "Edit", edit_gadget_path(gadget), data: { "turbo-frame": "modal-content" } %&amp;gt;&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
  &amp;lt;% end %&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div id="modal" data-controller="modal"&amp;gt;
  &amp;lt;turbo-frame id="modal-content"&amp;gt;&amp;lt;/turbo-frame&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, there we go. Now we have an index that displays our Gadgets and a link that will load the edit page and replace the content of the modal via Turbo. Plus, also, a nice container for the modal itself. Gotta have that.&lt;/p&gt;

&lt;p&gt;Similarly, we need the edit page. That will look very similar, but with an important difference.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;table id="gadgets-table"&amp;gt;
  &amp;lt;% @gadgets.each do |gadget| %&amp;gt;
    &amp;lt;tr&amp;gt;
      &amp;lt;td&amp;gt;&amp;lt;%= gadget.name %&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;td&amp;gt;&amp;lt;%= link_to "Edit", edit_gadget_path(gadget), data: { "turbo-frame": "modal-content" } %&amp;gt;&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
  &amp;lt;% end %&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;div id="modal" data-controller="modal"&amp;gt;
  &amp;lt;turbo-frame id="modal-content"&amp;gt;
    &amp;lt;%= form_with model: @gadget do |form| %&amp;gt;
      &amp;lt;%= form.text_field :name %&amp;gt;
      &amp;lt;%= form.submit "Save" %&amp;gt;
    &amp;lt;% end %&amp;gt;
  &amp;lt;/turbo-frame&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference, as you probably noticed, is that the modal has a form in it, set to edit the specified &lt;code&gt;Gadget&lt;/code&gt;. Importantly, [the &lt;code&gt;data-turbo-frame&lt;/code&gt; attributes on the links let Turbo know to load the page and swap out the &lt;code&gt;modal-content&lt;/code&gt; frame &lt;a href="https://turbo.hotwired.dev/handbook/frames#targeting-navigation-into-or-out-of-a-frame"&gt;targeting&lt;/a&gt;. So when the user clicks on the link in the table, the edit form's contents will just pop into that &lt;code&gt;turbo-frame&lt;/code&gt;. You can see this if you did it right now, because we don't have any CSS to make this look like a modal, or to hide the modal.&lt;/p&gt;

&lt;p&gt;Oh, right! CSS! Ok, let's get on that. Put this inside &lt;code&gt;app/assets/stylesheets/modal.css&lt;/code&gt; (or wherever you're putting your CSS):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#modal {
  background: rgba(#002045, 0.85)
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
}

#modal.visible {
  display: flex;
  align-items: center;
  justify-content: center;
}

#modal-content {
  width: 50%;
  background: #fff;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the modal will look and act like a modal. This is the last part I mentioned at the start: The small Stimulus controller that watches for changes. Here's where we add the &lt;code&gt;ModalController&lt;/code&gt; that the &lt;code&gt;data-controller="modal"&lt;/code&gt; attribute on the modal container alluded to. This will go in &lt;code&gt;app/javascript/controllers/modal_controller.js&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;import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["open"]

  openTargetConnected() {
    this.open()
  }

  openTargetDisconnected() {
    this.close()
  }

  open() {
    this.element.classList.add("visible")
  }

  close() {
    this.element.classList.remove("visible")
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works because Stimulus controllers have a number of &lt;a href="https://stimulus.hotwired.dev/reference/lifecycle-callbacks"&gt;lifecycle callbacks&lt;/a&gt;. In this case, we want the callbacks that activate when a &lt;code&gt;target&lt;/code&gt; is connected and disconnected (that is, added to or removed from the children of the controller element). A &lt;code&gt;target&lt;/code&gt; is marked with an attribute: &lt;code&gt;data-&amp;lt;controller-name&amp;gt;-target="&amp;lt;target-name&amp;gt;"&lt;/code&gt;. We have the &lt;code&gt;ModalController&lt;/code&gt; above, and it has an &lt;code&gt;open&lt;/code&gt; target, so the attribute would be &lt;code&gt;data-modal-target="open"&lt;/code&gt;. When a DOM node is either added that already has this attribute, or the attribute is added to an existing element, the &lt;code&gt;openTargetConnected&lt;/code&gt; method on the &lt;code&gt;ModalController&lt;/code&gt; is called.&lt;/p&gt;

&lt;p&gt;This means that when Turbo loads the edit page and swaps in the modal, the &lt;code&gt;ModalController&lt;/code&gt; will see the &lt;code&gt;open&lt;/code&gt; target and give the whole thing the &lt;code&gt;visible&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;Cool, so now what happens when we submit? Well, &lt;code&gt;turbo-frame&lt;/code&gt;s are limited to one interaction per page load, basically. And since what we want to do is close the modal &lt;em&gt;and&lt;/em&gt; update the contents of the index, we'll need something a little more flexible: &lt;a href="https://turbo.hotwired.dev/handbook/streams"&gt;Turbo Streams&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Turbo Streams let you batch up multiple changes into one response. And, despite their name, you don't have to use them in a streaming context. You can return them from an action, same as anything else.&lt;/p&gt;

&lt;p&gt;In our &lt;code&gt;GadgetsController&lt;/code&gt;'s &lt;code&gt;update&lt;/code&gt; action, we need to save the &lt;code&gt;Gadget&lt;/code&gt; we just changed in the browser. Then, instead of rendering, we redirect to the &lt;code&gt;index&lt;/code&gt;. The &lt;code&gt;index&lt;/code&gt;'s &lt;code&gt;turbo-stream&lt;/code&gt; response will do what we need it to: it will replace the modal with an empty modal, and it will replace the table with a table that contains the new &lt;code&gt;Gadget&lt;/code&gt;'s information.&lt;/p&gt;

&lt;p&gt;This change to the &lt;code&gt;index&lt;/code&gt; action is pretty small. It's using already-existing functionality:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;respond_to do |format|
  format.html
  format.turbo_stream
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are using the &lt;a href="https://rubygems.org/gems/turbo-rails"&gt;&lt;code&gt;turbo-rails&lt;/code&gt; gem&lt;/a&gt;, this format will be created for you. This, then, should be what &lt;code&gt;app/views/gadgets/index.turbo_stream.erb&lt;/code&gt; looks like (and you can, of course, extract whatever you want into partials):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;turbo-stream action="replace" target="modal-content"&amp;gt;
  &amp;lt;template&amp;gt;
    &amp;lt;turbo-frame id="modal-content"&amp;gt;&amp;lt;/turbo-frame&amp;gt;
  &amp;lt;/template&amp;gt;
&amp;lt;/turbo-stream&amp;gt;
&amp;lt;turbo-stream action="replace" target="gadgets-table"&amp;gt;
  &amp;lt;template&amp;gt;
    &amp;lt;table id="gadgets-table"&amp;gt;
      &amp;lt;% @gadgets.each do |gadget| %&amp;gt;
        &amp;lt;tr&amp;gt;
          &amp;lt;td&amp;gt;&amp;lt;%= gadget.name %&amp;gt;&amp;lt;/td&amp;gt;
          &amp;lt;td&amp;gt;&amp;lt;%= link_to "Edit", edit_gadget_path(gadget), data: { "turbo-frame": "modal-content" } %&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
      &amp;lt;% end %&amp;gt;
    &amp;lt;/table&amp;gt;
  &amp;lt;/template&amp;gt;
&amp;lt;/turbo-stream&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when the user submits the form and it saves successfully, the &lt;code&gt;update&lt;/code&gt; action will redirect to &lt;code&gt;index&lt;/code&gt; and &lt;code&gt;index&lt;/code&gt; will render the above &lt;code&gt;turbo-stream&lt;/code&gt;. Turbo will replace the &lt;code&gt;modal-content&lt;/code&gt; which will make Stimulus close the modal, and it will update the table to reflect the new value of the &lt;code&gt;Gadget&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The best part of all of this is that this is just using existing web paradigms. And if the user (for whatever reason) doesn't have javascript enabled, this will Just Work in exactly the same manner (albeit a little slower) because of the progressive enhancement on top of the multi-request cycle. And with Javascript, it never reloads the page.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Learn more about how The Gnar &lt;a href="https://www.thegnar.com/software-development/ruby-on-rails-development"&gt;builds Ruby on Rails applications&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Validating Views with Capybara Queries</title>
      <dc:creator>Jon Yurek</dc:creator>
      <pubDate>Tue, 29 Mar 2022 15:52:33 +0000</pubDate>
      <link>https://forem.com/thegnarco/validating-views-with-capybara-queries-50gj</link>
      <guid>https://forem.com/thegnarco/validating-views-with-capybara-queries-50gj</guid>
      <description>&lt;p&gt;When you write a system test (or, as we prefer, a &lt;a href="https://relishapp.com/rspec/rspec-rails/docs/system-specs/system-spec"&gt;system spec&lt;/a&gt;) with Ruby on Rails, you're exercising the whole stack from the point of view of the user. So, naturally, you have to do things like make sure that certain elements are on the page and work as you expect when you click on then, type in them, and drag them around. &lt;a href="https://teamcapybara.github.io/capybara/"&gt;Capybara&lt;/a&gt; works exceedingly well for this, giving you a lovely API for querying HTML.&lt;/p&gt;

&lt;p&gt;But system specs are heavy. They need everything spun up. They need a browser. They're faster than a person clicking around, but they need to do things like wait for pages to load, animations to finish, and Promises to resolve. If you can, it's much cleaner to test whether or not you have data in your HTML by using a &lt;a href="https://relishapp.com/rspec/rspec-rails/v/5-0/docs/view-specs/view-spec"&gt;view spec&lt;/a&gt;. You only have to use exactly what the template needs, and stubbing or mocking to get ensure coverage is far less onerous and invasive. Since a view spec only has to worry about rendering the view (and returning a String), it doesn't have to load a whole browser.&lt;/p&gt;

&lt;p&gt;Even in the Relish documentation linked above, we see that a lot of view specs involve straight text matching. You could try Rails' &lt;a href="https://apidock.com/rails/ActionController/Assertions/SelectorAssertions/assert_select"&gt;assert_select&lt;/a&gt;, which makes more structured queries, but an even easier solution exists.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rubydoc.info/gems/capybara/Capybara.string"&gt;&lt;code&gt;Capybara.string&lt;/code&gt;&lt;/a&gt; is a very straight forward method that turns a string into a queryable document. Working with it involves little overhead, especially if you have Capybara installed already (and even if you don't).&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;it&lt;/span&gt; &lt;span class="s2"&gt;"renders the account link when signed in"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;FactoryBot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build_stubbed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:current_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s2"&gt;"layouts/nav"&lt;/span&gt;

  &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rendered&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"nav a[href='/users/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;']"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="s2"&gt;"Account"&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;it&lt;/span&gt; &lt;span class="s2"&gt;"does not render the account link when signed out"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:current_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s2"&gt;"layouts/nav"&lt;/span&gt;

  &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rendered&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_no_css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"nav a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="s2"&gt;"Account"&lt;/span&gt;&lt;span class="p"&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;You get access to all the Capybara matchers, and, with RSpec, you get the nice inflections that make your specs very readable. And since &lt;code&gt;render&lt;/code&gt; already puts its result into &lt;code&gt;rendered&lt;/code&gt;, it's very easy to make that into a usable document with &lt;code&gt;Capybara.string&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Learn more about how The Gnar &lt;a href="https://www.thegnar.com/software-development/ruby-on-rails-development"&gt;builds Ruby on Rails applications&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
