<?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: Doug Stull</title>
    <description>The latest articles on Forem by Doug Stull (@dstull).</description>
    <link>https://forem.com/dstull</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%2F9325%2F3387827.jpeg</url>
      <title>Forem: Doug Stull</title>
      <link>https://forem.com/dstull</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/dstull"/>
    <language>en</language>
    <item>
      <title>How to use modals with forms in Rails using Turbo</title>
      <dc:creator>Doug Stull</dc:creator>
      <pubDate>Fri, 08 Jan 2021 05:04:09 +0000</pubDate>
      <link>https://forem.com/dstull/how-to-use-modals-with-forms-in-rails-using-turbo-14n7</link>
      <guid>https://forem.com/dstull/how-to-use-modals-with-forms-in-rails-using-turbo-14n7</guid>
      <description>&lt;h1&gt;
  
  
  Goal
&lt;/h1&gt;

&lt;p&gt;Using the &lt;a href="https://hotwire.dev/" rel="noopener noreferrer"&gt;Hotwire&lt;/a&gt;, show how to setup a basic implementation of a modal with a form.&lt;/p&gt;

&lt;p&gt;We'll break this down into talking about the controller, view templates and javascript.  &lt;/p&gt;

&lt;p&gt;There are many other things that were setup in the example application due to the choices made in the tech stack and some setup/configuration has been derived from things learned on &lt;a href="https://gorails.com/" rel="noopener noreferrer"&gt;Go Rails&lt;/a&gt;.  We'll assume some familiarity with &lt;a href="https://stimulus.hotwire.dev/" rel="noopener noreferrer"&gt;Stimulus&lt;/a&gt; in order to keep focused on the benefits of &lt;a href="https://turbo.hotwire.dev/" rel="noopener noreferrer"&gt;Turbo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gitlab.com/doug.stull/turbo_modal/" rel="noopener noreferrer"&gt;Source code for this guide - updated to turbo-rails 0.5.9&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Posts page being referenced
&lt;/h2&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%2Fi%2Ft6g29yw70dndt1565baq.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%2Fi%2Ft6g29yw70dndt1565baq.png" alt="Screen Shot 2021-01-07 at 11.21.43 PM"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a new post using the modal
&lt;/h2&gt;

&lt;h3&gt;
  
  
  New link and modal setup
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; &lt;a href="https://github.com/hotwired/turbo-site/pull/40#discussion_r578763199" rel="noopener noreferrer"&gt;this github comment&lt;/a&gt; is what I used as guide for this setup.&lt;/p&gt;

&lt;p&gt;This all starts in &lt;a href="https://gitlab.com/doug.stull/turbo_modal/-/blob/master/app/views/posts/index.html.erb" rel="noopener noreferrer"&gt;index.html.erb&lt;/a&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;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mt-8"&lt;/span&gt; &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"post-modal"&lt;/span&gt; &lt;span class="na"&gt;data-post-modal-prevent-default-action-opening=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;render&lt;/span&gt; &lt;span class="na"&gt;partial:&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="na"&gt;posts&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;modal_form&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
...
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;link_to&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="na"&gt;New&lt;/span&gt; &lt;span class="na"&gt;Post&lt;/span&gt;&lt;span class="err"&gt;',&lt;/span&gt; &lt;span class="na"&gt;new_post_path&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;class:&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;btn&lt;/span&gt; &lt;span class="na"&gt;btn-primary&lt;/span&gt;&lt;span class="err"&gt;",&lt;/span&gt; &lt;span class="na"&gt;data:&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action:&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;click-&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;post-modal#open", 'turbo-frame': 'post' } %&amp;gt;


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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;turbo_frame_tag&lt;/code&gt; allows us to tell &lt;code&gt;turbo&lt;/code&gt; that links with a matching &lt;code&gt;data-turbo-frame&lt;/code&gt; value matching it will return an html response including a matching frame tag(not sure on this wording, as we supply it here 
to merely tell &lt;code&gt;turbo&lt;/code&gt; to leave the url unchanged when the link is clicked).  Our action upon &lt;code&gt;New Post&lt;/code&gt; click will be a replace that is defined in the &lt;a href="https://gitlab.com/doug.stull/turbo_modal/-/blob/master/app/views/posts/new.html.erb" rel="noopener noreferrer"&gt;new.html.erb&lt;/a&gt; template. This will populate the modal via use of the &lt;code&gt;target&lt;/code&gt; value on the &lt;code&gt;turbo-stream&lt;/code&gt; element in the template.&lt;/li&gt;
&lt;li&gt;render our &lt;code&gt;modal_form&lt;/code&gt; partial which will contain the modal definition and define a &lt;code&gt;div&lt;/code&gt; with an &lt;code&gt;id&lt;/code&gt; that the &lt;code&gt;turbo-stream&lt;/code&gt; response replaces.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;data-controller="post-modal"&lt;/code&gt; names our &lt;code&gt;post-modal&lt;/code&gt; controller and causes it to connect on render.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;data-post-modal-prevent-default-action-opening="false"&lt;/code&gt; will allow us to open the modal with the link and also fetch &lt;code&gt;new_post_path&lt;/code&gt; from the controller and render our modal content.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Clicking the &lt;code&gt;New Post&lt;/code&gt; link
&lt;/h3&gt;

&lt;p&gt;This action will take us into the &lt;a href="https://gitlab.com/doug.stull/turbo_modal/-/blob/master/app/controllers/posts_controller.rb" rel="noopener noreferrer"&gt;posts_controller.rb&lt;/a&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;new&lt;/span&gt;
  &lt;span class="vi"&gt;@post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&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;end&lt;/span&gt;


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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;We render the html response of &lt;a href="https://gitlab.com/doug.stull/turbo_modal/-/blob/master/app/views/posts/new.html.erb" rel="noopener noreferrer"&gt;new.html.erb&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= turbo_frame_tag 'post' do %&amp;gt;
  &amp;lt;turbo-stream target=&lt;/span&gt;&lt;span class="s2"&gt;"post_form"&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"replace"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= render partial: "posts/form", locals: { post: @post } %&amp;gt;
    &amp;lt;/template&amp;gt;
  &amp;lt;/turbo-stream&amp;gt;
&amp;lt;% end %&amp;gt;


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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;We render the &lt;code&gt;turbo_stream&lt;/code&gt; response inside the template with a target that matches the &lt;code&gt;id&lt;/code&gt; inside the modal.  This will then replace that element inside the modal with our form content. &lt;/li&gt;
&lt;li&gt;Below is an example response&lt;/li&gt;
&lt;/ul&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%2Fgtv0hxx1hm1tkobtlabo.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%2Fgtv0hxx1hm1tkobtlabo.png" alt="Screen shot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;While the data is being fetched, the modal is given the signal to open, so we'll listen for an event from another Stimulus controller. That controller is the  &lt;a href="https://gitlab.com/doug.stull/turbo_modal/-/blob/master/app/javascript/controllers/post_form_controller.js" rel="noopener noreferrer"&gt;post_form_controller.js&lt;/a&gt;, and it keeps us from seeing unprepared html and stale data from an invalid form submission.&lt;/li&gt;
&lt;li&gt;We can achieve that by adding an &lt;code&gt;eventListener&lt;/code&gt; to  &lt;a href="https://gitlab.com/doug.stull/turbo_modal/-/blob/master/app/javascript/controllers/post_modal_controller.js" rel="noopener noreferrer"&gt;post_modal_controller.js&lt;/a&gt;'s open function.

&lt;ul&gt;
&lt;li&gt;this is extended from the original modal supplied from &lt;a href="https://github.com/excid3/tailwindcss-stimulus-components" rel="noopener noreferrer"&gt;tailwindcss-stimulus-components&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

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

&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;postForm:load&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="o"&gt;=&amp;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;containerTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&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;toggleClass&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;ul&gt;
&lt;li&gt;The above is done in order to take advantage of the &lt;a href="https://github.com/hotwired/turbo/blob/aae160bed764ca7322be1e3a9e00366787d96f7f/src/elements/stream_element.ts#L7" rel="noopener noreferrer"&gt;connectCallback&lt;/a&gt; that seems to be the only way to know when the &lt;code&gt;replace&lt;/code&gt; action from &lt;code&gt;turbo-stream&lt;/code&gt; is &lt;a href="https://github.com/hotwired/turbo/blob/aae160bed764ca7322be1e3a9e00366787d96f7f/src/elements/stream_element.ts#L77" rel="noopener noreferrer"&gt;completed&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Rendering the response
&lt;/h3&gt;

&lt;p&gt;At this point the modal that is defined in &lt;a href="https://gitlab.com/doug.stull/turbo_modal/-/blob/master/app/views/posts/_modal_form.html.erb" rel="noopener noreferrer"&gt;_modal_form.html.erb&lt;/a&gt; is opening and this element below is being replaced with the &lt;code&gt;turbo-stream&lt;/code&gt; response that has the rendered form.&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;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;tag.div&lt;/span&gt; &lt;span class="na"&gt;nil&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;id:&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="na"&gt;post_form&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;


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

&lt;/div&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%2Fi%2F9bd1cs8lxie09wozuoqr.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%2Fi%2F9bd1cs8lxie09wozuoqr.png" alt="Screen Shot 2021-01-07 at 11.34.34 PM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Submitting the form
&lt;/h3&gt;

&lt;p&gt;When we submit the form, the modal closes via &lt;code&gt;close&lt;/code&gt; function in the extended &lt;code&gt;post-modal&lt;/code&gt; stimulus controller and reach the &lt;code&gt;create&lt;/code&gt; method in the &lt;a href="https://gitlab.com/doug.stull/turbo_modal/-/blob/master/app/controllers/posts_controller.rb" rel="noopener noreferrer"&gt;posts_controller.rb&lt;/a&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="vi"&gt;@post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&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;post_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;posts_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;notice: &lt;/span&gt;&lt;span class="s1"&gt;'Post was successfully created.'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;turbo_stream&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;turbo_stream: &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;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'post_form'&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;"posts/form"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                                    &lt;span class="ss"&gt;locals: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;post: &lt;/span&gt;&lt;span class="vi"&gt;@post&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;end&lt;/span&gt;


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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;If successful, we'll just redirect to the page the modal is launched from, and flash a message.  This will all feel seamless since Turbo takes care of replacing items in place; negating the need for a full page reload.&lt;/li&gt;
&lt;li&gt;When the form submission is not successful, &lt;code&gt;turbo_stream&lt;/code&gt; format is rendered and the form is replaced(leaving the modal open and adding in the errors automatically)&lt;/li&gt;
&lt;/ul&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%2Fi%2F2yavtdv5r7sd8ofk2iwa.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%2Fi%2F2yavtdv5r7sd8ofk2iwa.png" alt="Screen Shot 2021-01-07 at 11.44.33 PM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Editing a post using the modal
&lt;/h2&gt;

&lt;p&gt;This uses the same concepts as the new submission and starts off in &lt;a href="https://gitlab.com/doug.stull/turbo_modal/-/blob/master/app/views/posts/index.html.erb" rel="noopener noreferrer"&gt;index.html.erb&lt;/a&gt; as seen below.&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"align-middle min-w-full overflow-x-auto shadow overflow-hidden sm:rounded-lg"&lt;/span&gt; &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"post-modal"&lt;/span&gt; &lt;span class="na"&gt;data-post-modal-prevent-default-action-opening=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;table&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"min-w-full divide-y divide-cool-gray-200"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;thead&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"px-6 py-3 bg-gray-50 text-left text-xs leading-4 font-medium text-cool-gray-500 uppercase tracking-wider"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Title&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"px-6 py-3 bg-gray-50 text-left text-xs leading-4 font-medium text-cool-gray-500 uppercase tracking-wider"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Body&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;colspan=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"px-6 py-3 bg-gray-50 text-left text-xs leading-4 font-medium text-cool-gray-500 uppercase tracking-wider"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/thead&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tbody&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"bg-white divide-y divide-cool-gray-200"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;posts.each&lt;/span&gt; &lt;span class="na"&gt;do&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;td&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"px-6 py-4 whitespace-nowrap text-sm leading-5 text-cool-gray-900"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;post.title&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;td&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"px-6 py-4 whitespace-nowrap text-sm leading-5 text-cool-gray-500"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;post.body&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;td&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"px-6 py-4 whitespace-nowrap text-sm leading-5 text-cool-gray-500"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;link_to&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="na"&gt;Edit&lt;/span&gt;&lt;span class="err"&gt;',&lt;/span&gt; &lt;span class="na"&gt;edit_post_path&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="err"&gt;),&lt;/span&gt; &lt;span class="na"&gt;class:&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;btn&lt;/span&gt; &lt;span class="na"&gt;btn-secondary&lt;/span&gt;&lt;span class="err"&gt;",&lt;/span&gt; &lt;span class="na"&gt;data:&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action:&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;click-&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;post-modal#open", 'turbo-frame': 'post' } %&amp;gt;
        &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The rest of edit and delete can be found in the &lt;a href="https://gitlab.com/doug.stull/turbo_modal/" rel="noopener noreferrer"&gt;example application&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  In closing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer: The code above is far from perfect&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In some places the implementation could definitely use refinement(feel free to suggest an improvement).&lt;/p&gt;

&lt;p&gt;I chose a modal here as I had that particular issue to solve on a project and didn't see a ready-made solution/guide.&lt;/p&gt;

&lt;p&gt;Overall I am happy with what Turbo enables and appreciate how the hard work of others to build these types of things, make it easier for everyone else to produce things quickly.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>turbo</category>
      <category>stimulus</category>
      <category>hotwire</category>
    </item>
    <item>
      <title>Setting up Rails with Docker and Caddyserver</title>
      <dc:creator>Doug Stull</dc:creator>
      <pubDate>Wed, 26 Jun 2019 20:05:05 +0000</pubDate>
      <link>https://forem.com/dstull/setting-up-rails-with-docker-and-caddyserver-1li7</link>
      <guid>https://forem.com/dstull/setting-up-rails-with-docker-and-caddyserver-1li7</guid>
      <description>&lt;h1&gt;
  
  
  Goal
&lt;/h1&gt;

&lt;p&gt;Show how to setup a basic implementation of Ruby on Rails with Docker, utilizing &lt;a href="https://caddyserver.com/" rel="noopener noreferrer"&gt;Caddyserver&lt;/a&gt; as the reverse proxy, &lt;a href="https://en.wikipedia.org/wiki/Transport_Layer_Security" rel="noopener noreferrer"&gt;tls&lt;/a&gt;, and &lt;a href="https://en.wikipedia.org/wiki/Load_balancing_(computing)" rel="noopener noreferrer"&gt;load balancer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For this tutorial, I will be using a simple demo Rails application, which you can find the source code for &lt;a href="https://github.com/dstull/docker-rails/tree/caddy" rel="noopener noreferrer"&gt;here&lt;/a&gt;.  There is also a &lt;a href="https://github.com/dstull/caddy_rails" rel="noopener noreferrer"&gt;repo&lt;/a&gt; for the basic Caddyserver setup.&lt;/p&gt;

&lt;p&gt;I'll break this down into 2 steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create Caddyserver Docker image&lt;/li&gt;
&lt;li&gt;Configure Rails and Caddyserver with Docker&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Create Caddyserver Docker image
&lt;/h1&gt;

&lt;p&gt;For this part we will go over the caddyfile, Dockerfile and docker-compose.yml files.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/dstull/caddy_rails/blob/master/src/caddyfile" rel="noopener noreferrer"&gt;caddyfile&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Note: all items below will reference the file linked above, and would be helpful if it were open in a separate window to reference.&lt;/p&gt;

&lt;h3&gt;
  
  
  Site/host declaration
&lt;/h3&gt;

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

https://localhost


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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;for our simple demo, we'll just set this up for our local development.  In production, this would be set to your dns name that is validated in your tls certificate&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://caddyserver.com/docs/proxy" rel="noopener noreferrer"&gt;Proxy&lt;/a&gt; setup
&lt;/h3&gt;

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

proxy / http://rails:3000 http://rails2:3000


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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;this will instruct Caddy to route all requests to our 2 containers running puma/rails, both listening on port 3000 for requests on the docker network where rails and rails2 are setup as &lt;a href="https://docs.docker.com/compose/compose-file/#aliases" rel="noopener noreferrer"&gt;aliases&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

transparent


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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;according to the &lt;a href="https://caddyserver.com/docs/proxy" rel="noopener noreferrer"&gt;docs&lt;/a&gt;, this is shorthand for:
```
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;header_upstream Host {host}&lt;br&gt;
header_upstream X-Real-IP {remote}&lt;br&gt;
header_upstream X-Forwarded-For {remote}&lt;br&gt;
header_upstream X-Forwarded-Port {server_port}&lt;br&gt;
header_upstream X-Forwarded-Proto {scheme}&lt;/p&gt;



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

&lt;/div&gt;



&lt;p&gt;websocket&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- according to the [docs](https://caddyserver.com/docs/proxy), this is shorthand for:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;header_upstream Connection {&amp;gt;Connection}&lt;br&gt;
header_upstream Upgrade {&amp;gt;Upgrade}&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- in rails we would need this if using [Action Cable](https://guides.rubyonrails.org/action_cable_overview.html)

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

&lt;/div&gt;



&lt;p&gt;policy round_robin&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- I changed this from the default of `random` to have a little more predictability, which helps when troubleshooting.

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

&lt;/div&gt;



&lt;p&gt;fail_timeout 30s&lt;br&gt;
max_fails 1&lt;br&gt;
try_duration 90s&lt;br&gt;
health_check /stats?token=stats&lt;br&gt;
health_check_interval 30s&lt;br&gt;
health_check_port 9191&lt;br&gt;
health_check_timeout 10s&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- I was confused/couldn't wrap my head around the [official documentation](https://caddyserver.com/docs/proxy) on how this all worked together, so here is my attempt at explaining in a way that made sense to me...
   - fail_timeout: 
      - try for X amount of time before declaring a request failed and moving on to try another backend, kicking in the `try_duration`.
   - try_duration: 
      - after `fail_timeout` is reached for the first time, this will then pick up and will try and find another backend so that the request doesn't fail.
      - so if `fail_timeout` is 30s and `try_duration` is 90s, and both backends are down...it would be 2 minutes before you get a bad gateway (502) response from Caddy.
      - `try_duration` must be greater than `fail_timeout` + time to find backend and serve request(rails request part), else it will respond with 502 on the first response from a failed backend.
   - health_check:
      - hitting a `puma` control app to get status, therefore the port needs to be specifically set.  For more insight into this setting, see the backend rails app settings in the [puma config](https://github.com/dstull/docker-rails/blob/caddy/config/puma.rb#L60)

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

&lt;/div&gt;



&lt;p&gt;errors stdout&lt;br&gt;
header / {&lt;br&gt;
  Strict-Transport-Security "max-age=31536000"&lt;br&gt;
}&lt;br&gt;
log / stdout "{combined} cache={cache_status}"&lt;br&gt;
gzip&lt;br&gt;
tls self_signed&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Docker prefers logging to `stdout`, so we'll do that here, adding some formatting for cacheing, in case we turn it on in the future.
- Caching is off for now due to this [issue](https://github.com/nicolasazrak/caddy-cache/issues/18) on the current Caddyserver image we are using.
- gzip content to speed everything up.  I thought there might be issues with this and Rails, but there wasn't and it provides great speed improvements in page load.
- use a simple self signed cert for this demo

## [Dockerfile](https://github.com/dstull/caddy_rails/blob/master/Dockerfile)
In this file, I will be highlight a few items that might not be strait forward.

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

&lt;/div&gt;



&lt;p&gt;RUN apk add --no-cache \&lt;br&gt;
    libcap \&lt;br&gt;
    &amp;amp;&amp;amp; \&lt;br&gt;
    :&lt;/p&gt;

&lt;p&gt;RUN setcap cap_net_bind_service=+ep /usr/sbin/caddy&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- install the alpine linux package libcap, which will enable us in the next line to grant binding privileges to the caddy binary.
- this will in turn allow us to run a caddy container as an unprivileged user(non root) on a port below 1024(443).

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

&lt;/div&gt;



&lt;p&gt;VOLUME /tmp&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- if using the caddy [cache module](https://caddyserver.com/docs/http.cache), this needs to be writeable for the cache to be written.
   - note: we are running the container as [read only](https://nickjanetakis.com/blog/docker-tip-55-creating-read-only-containers) by default, therefore, any area that needs to have files written to it will need be declared as [volumes](https://docs.docker.com/storage/volumes/).

## [docker-compose.yaml](https://github.com/dstull/caddy_rails/blob/master/docker-compose.yaml)
In this file we are declaring some items that will help build and test out our app:

### build locally
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;15:25 $ docker-compose build caddy_rails&lt;br&gt;
Building caddy_rails&lt;br&gt;
Step 1/10 : FROM jumanjiman/caddy:v0.11.0-20181002T1350-git-3d0ba71&lt;br&gt;
 ---&amp;gt; 6b039a312afc&lt;br&gt;
Step 2/10 : USER root&lt;br&gt;
 ---&amp;gt; Using cache&lt;br&gt;
 ---&amp;gt; ab346bccfd06&lt;br&gt;
Step 3/10 : COPY src/caddyfile /etc/caddy/caddyfile&lt;br&gt;
 ---&amp;gt; Using cache&lt;br&gt;
 ---&amp;gt; 27bcf28474ae&lt;br&gt;
Step 4/10 : COPY src/init.sh /usr/bin&lt;br&gt;
 ---&amp;gt; Using cache&lt;br&gt;
 ---&amp;gt; 047528cc4a11&lt;br&gt;
Step 5/10 : COPY src/healthcheck /var/opt/healthcheck&lt;br&gt;
 ---&amp;gt; Using cache&lt;br&gt;
 ---&amp;gt; d15a10faa15c&lt;br&gt;
Step 6/10 : RUN apk add --no-cache          libcap          &amp;amp;&amp;amp;      :&lt;br&gt;
 ---&amp;gt; Using cache&lt;br&gt;
 ---&amp;gt; e0a9d0b2b44e&lt;br&gt;
Step 7/10 : RUN setcap cap_net_bind_service=+ep /usr/sbin/caddy&lt;br&gt;
 ---&amp;gt; Using cache&lt;br&gt;
 ---&amp;gt; d23679349861&lt;br&gt;
Step 8/10 : VOLUME /tmp&lt;br&gt;
 ---&amp;gt; Using cache&lt;br&gt;
 ---&amp;gt; 2e94a84380e6&lt;br&gt;
Step 9/10 : USER caddy&lt;br&gt;
 ---&amp;gt; Using cache&lt;br&gt;
 ---&amp;gt; b2d343318687&lt;br&gt;
Step 10/10 : ENTRYPOINT ["/usr/bin/init.sh"]&lt;br&gt;
 ---&amp;gt; Using cache&lt;br&gt;
 ---&amp;gt; 8b6a28c0ab20&lt;/p&gt;

&lt;p&gt;Successfully built 8b6a28c0ab20&lt;br&gt;
Successfully tagged caddy_rails:latest&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
### Run locally
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;15:25 $ docker-compose up -d&lt;br&gt;
Creating network "caddy_rails_default" with the default driver&lt;br&gt;
Creating caddy_rails_caddy_rails_1 ... done&lt;br&gt;
15:26 $ docker ps&lt;br&gt;
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS                  NAMES&lt;br&gt;
13e9cf1dd292        caddy_rails         "/usr/bin/init.sh"   4 seconds ago       Up 4 seconds        0.0.0.0:443-&amp;gt;443/tcp   caddy_rails_caddy_rails_1&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
# Configure Rails and Caddyserver with Docker
In this section, I am going to focus on the setup required for Rails to work with our Caddy setup, glossing over more specific Rails/Docker items at times.

## [Dockerfile](https://github.com/dstull/docker-rails/blob/caddy/Dockerfile)

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

&lt;/div&gt;



&lt;p&gt;ENTRYPOINT ["/web/script/entrypoint"]&lt;br&gt;
CMD ["puma", "-C", "config/puma.rb"]&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- set the entrypoint script, which will merely exec the command passed and allow it to take over [PID 1](https://tandrepires.wordpress.com/2016/11/15/the-importance-of-pid-1-in-containers/)

## [docker-compose.yml](https://github.com/dstull/docker-rails/blob/caddy/docker-compose.yml)
In this file, we setup our local build and runtime environments.

We define:
- 2 Rails instances so that caddy can load balance across `rails` and `rails2`.  We declare aliases on our networks for these instances, which then enables caddy to reference them as backends in the [caddyfile](https://github.com/dstull/caddy_rails/blob/master/src/caddyfile#L2)
- Basic Healthcheck settings for docker to determine the container health status as seen from `docker ps`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;healthcheck:
  test: ["CMD", "curl", "http://localhost:3000"]
  interval: 10s
  timeout: 10s
  retries: 20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- I have configured the caddy_rails example above to automatically build images on update of master branch on [dockerhub](https://cloud.docker.com/repository/docker/hammer098/caddy_rails), and reference it now in the file.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;image: hammer098/caddy_rails:latest&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
- On Caddy, expost port 443 to the underlying host
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Finish setting up our dependency chain so that when we start everything with a `docker-compose up -d caddy`, it will start in sequence of `rails` -&amp;gt; `rails2` -&amp;gt; `caddy`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;/div&gt;



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

&lt;/div&gt;



&lt;p&gt;15:54 $ sdlc/build&lt;br&gt;
Building rails&lt;br&gt;
Step 1/11 : FROM hammer098/ruby_24&lt;br&gt;
 ---&amp;gt; ba1ba3ccbaca&lt;br&gt;
Step 2/11 : WORKDIR /web&lt;br&gt;
 ---&amp;gt; Using cache&lt;br&gt;
 ---&amp;gt; 1d49ff0018bb&lt;br&gt;
Step 3/11 : COPY .ruby-version /web/.ruby-version&lt;br&gt;
 ---&amp;gt; Using cache&lt;br&gt;
 ---&amp;gt; f187344f9c6c&lt;br&gt;
Step 4/11 : COPY Gemfile /web/Gemfile&lt;br&gt;
 ---&amp;gt; Using cache&lt;br&gt;
 ---&amp;gt; 5592d6874d91&lt;br&gt;
Step 5/11 : COPY Gemfile.lock /web/Gemfile.lock&lt;br&gt;
 ---&amp;gt; Using cache&lt;br&gt;
 ---&amp;gt; 96ae9333a90e&lt;br&gt;
Step 6/11 : RUN /bin/bash -l -c "bundle install"&lt;br&gt;
 ---&amp;gt; Using cache&lt;br&gt;
 ---&amp;gt; c593b6c4f41e&lt;br&gt;
Step 7/11 : COPY . /web&lt;br&gt;
 ---&amp;gt; 7ab2c7c6ac96&lt;br&gt;
Step 8/11 : ENV TEMP /web/tmp&lt;br&gt;
 ---&amp;gt; Running in 610cb512b3d1&lt;br&gt;
Removing intermediate container 610cb512b3d1&lt;br&gt;
 ---&amp;gt; ba400993482d&lt;br&gt;
Step 9/11 : VOLUME /web/tmp&lt;br&gt;
 ---&amp;gt; Running in aa2e71bc9236&lt;br&gt;
Removing intermediate container aa2e71bc9236&lt;br&gt;
 ---&amp;gt; 4489c90a69bb&lt;br&gt;
Step 10/11 : ENTRYPOINT ["/web/script/entrypoint"]&lt;br&gt;
 ---&amp;gt; Running in 9bb7c471c455&lt;br&gt;
Removing intermediate container 9bb7c471c455&lt;br&gt;
 ---&amp;gt; 602ff0fd2660&lt;br&gt;
Step 11/11 : CMD ["puma", "-C", "config/puma.rb"]&lt;br&gt;
 ---&amp;gt; Running in 99f2a4c59410&lt;br&gt;
Removing intermediate container 99f2a4c59410&lt;br&gt;
 ---&amp;gt; cc6672106b95&lt;br&gt;
Successfully built cc6672106b95&lt;br&gt;
Successfully tagged rails:latest&lt;/p&gt;

&lt;p&gt;real    0m2.346s&lt;br&gt;
user    0m0.328s&lt;br&gt;
sys 0m0.098s&lt;/p&gt;

&lt;p&gt;REPOSITORY              TAG                                 IMAGE ID            CREATED                  SIZE&lt;br&gt;
rails                   latest                              cc6672106b95        Less than a second ago   1.13GB&lt;/p&gt;



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

&lt;/div&gt;



&lt;p&gt;15:54 $ sdlc/run&lt;/p&gt;

&lt;p&gt;Starting application container(s)&lt;/p&gt;

&lt;p&gt;Creating network "docker-rails_railsnet" with driver "bridge"&lt;br&gt;
Creating docker-rails_rails_1 ... done&lt;br&gt;
Creating docker-rails_rails2_1 ... done&lt;br&gt;
Creating docker-rails_caddy_1  ... done&lt;/p&gt;

&lt;p&gt;15:55 $ docker ps&lt;br&gt;
CONTAINER ID        IMAGE                          COMMAND                  CREATED             STATUS                            PORTS                  NAMES&lt;br&gt;
a85a08b577c1        hammer098/caddy_rails:latest   "/usr/bin/init.sh"       5 seconds ago       Up 3 seconds                      0.0.0.0:443-&amp;gt;443/tcp   docker-rails_caddy_1&lt;br&gt;
20e44a42434a        rails                          "/web/script/entrypo…"   7 seconds ago       Up 4 seconds (health: starting)                          docker-rails_rails2_1&lt;br&gt;
f07ac602d1d4        rails                          "/web/script/entrypo…"   18 seconds ago      Up 16 seconds (healthy)                                  docker-rails_rails_1&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
## Open Browser to see Rails running through Caddy
open to `https://localhost` and accept the certificate warning.

![](https://thepracticaldev.s3.amazonaws.com/i/9kk0w5fp20a2e7gzcdl9.png)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>rails</category>
      <category>caddyserver</category>
      <category>docker</category>
      <category>puma</category>
    </item>
    <item>
      <title>StimulusJS with Rails Action Cable and a bit of Sidekiq</title>
      <dc:creator>Doug Stull</dc:creator>
      <pubDate>Fri, 31 Aug 2018 19:48:11 +0000</pubDate>
      <link>https://forem.com/dstull/stimulusjs-with-rails-action-cable-and-a-bit-of-sidekiq-i0a</link>
      <guid>https://forem.com/dstull/stimulusjs-with-rails-action-cable-and-a-bit-of-sidekiq-i0a</guid>
      <description>&lt;h1&gt;
  
  
  Goal
&lt;/h1&gt;

&lt;p&gt;Show a simple implementation of &lt;a href="https://guides.rubyonrails.org/action_cable_overview.html"&gt;Rails Action Cable&lt;/a&gt; with the modest js framework named &lt;a href="https://stimulusjs.org/"&gt;StimulusJS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For this tutorial, I will be using a simple demo Rails application, which you can find the source code for &lt;a href="https://github.com/dstull/sidekiq-actioncable-stimulus-demo/tree/actioncable"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I am going to gloss over the particulars of &lt;a href="https://sidekiq.org/"&gt;Sidekiq&lt;/a&gt; here, and focus on the StimulusJS and Action Cable pieces.  I believe that will be most valuable here, as the other pieces have been covered many times on numerous blogs and tutorials. However, I may revisit the other items at a later date.&lt;/p&gt;

&lt;p&gt;I'll break this down into 2 steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create Sidekiq worker and Action Cable Channel&lt;/li&gt;
&lt;li&gt;Setup StimulusJS &lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Create Sidekiq worker and Action Cable Channel
&lt;/h1&gt;

&lt;p&gt;For this part, I want to show how an index page listing cars that have many drivers can be updated when a driver's name changes or the particular car they belong to.  &lt;/p&gt;

&lt;p&gt;To accomplish that for this example, I decided to make the Sidekiq job trigger off and &lt;code&gt;after_touch&lt;/code&gt; callback on the Car model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Car/Driver Models - the Active Record Models
&lt;/h2&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;Car&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:drivers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependent: :destroy&lt;/span&gt;

  &lt;span class="n"&gt;after_touch&lt;/span&gt; &lt;span class="ss"&gt;:update_driver_names&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;drivers_list&lt;/span&gt;
    &lt;span class="n"&gt;drivers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pluck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&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="s1"&gt;','&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;update_driver_names&lt;/span&gt;
    &lt;span class="no"&gt;CarsWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&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;class&lt;/span&gt; &lt;span class="nc"&gt;Driver&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:car&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;touch: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="n"&gt;delegate&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: :car&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;prefix: &lt;/span&gt;&lt;span class="kp"&gt;true&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 the above file, I am triggering the &lt;code&gt;after_touch&lt;/code&gt; callback named &lt;code&gt;update_driver_names&lt;/code&gt; on the Car model by adding &lt;code&gt;touch: true&lt;/code&gt; to the Driver model. &lt;br&gt;
 The &lt;code&gt;update_driver_names&lt;/code&gt; method reaches out to Sidekiq and calls an async job called &lt;code&gt;CarsWorker.perform_async&lt;/code&gt;, sending the &lt;code&gt;id&lt;/code&gt; of the Car that the Driver has assigned.&lt;/p&gt;
&lt;h2&gt;
  
  
  CarsWorker (&lt;a href="https://github.com/dstull/sidekiq-actioncable-stimulus-demo/blob/actioncable/app/workers/cars_worker.rb"&gt;cars_worker.rb&lt;/a&gt;) - the Sidekiq Worker
&lt;/h2&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;CarsWorker&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;car_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# some contrived work...&lt;/span&gt;
    &lt;span class="n"&gt;car&lt;/span&gt;                &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Car&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt; &lt;span class="n"&gt;car_id&lt;/span&gt;
    &lt;span class="n"&gt;new_driver_changes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;car&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;driver_changes&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;car&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:driver_changes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_driver_changes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;car_drivers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;car&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drivers_list&lt;/span&gt;
    &lt;span class="no"&gt;ActionCable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'cars'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;drivers: &lt;/span&gt;&lt;span class="n"&gt;car_drivers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;car_id: &lt;/span&gt;&lt;span class="n"&gt;car_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;driver_changes: &lt;/span&gt;&lt;span class="n"&gt;new_driver_changes&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 the above file, I am:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Incrementing the &lt;code&gt;driver_changes&lt;/code&gt; on the driver's car and committing that on the car.&lt;/li&gt;
&lt;li&gt;Finding all the drivers for that car and broadcasting out to the Action Cable channel the new drivers list as a string in &lt;code&gt;car_drivers&lt;/code&gt;, along with the number of &lt;code&gt;driver_changes&lt;/code&gt;.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is an example of the transmission from the CarsChannel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CarsChannel transmitting {"drivers"=&amp;gt;"Jacalyn Bauchblah,Wes Goodwin,Guy Keeling,Miss Pasquale Doyle,Candy Welch", "car_id"=&amp;gt;23, "driver_changes"=&amp;gt;4} (via streamed from cars)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  CarsChannel (&lt;a href="https://github.com/dstull/sidekiq-actioncable-stimulus-demo/blob/actioncable/app/channels/cars_channel.rb"&gt;cars_channel.rb&lt;/a&gt;) - the Action Cable Channel definition
&lt;/h2&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;CarsChannel&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationCable&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Channel&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subscribed&lt;/span&gt;
    &lt;span class="n"&gt;stream_from&lt;/span&gt; &lt;span class="s1"&gt;'cars'&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 above is merely the standard boilerplate channel definition that is defined &lt;a href="https://guides.rubyonrails.org/action_cable_overview.html#streams"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Setup StimulusJS
&lt;/h1&gt;

&lt;p&gt;For this part I will show the HTML erb pieces and the StimulusJS setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cars Index (&lt;a href="https://github.com/dstull/sidekiq-actioncable-stimulus-demo/blob/actioncable/app/views/cars/index.html.erb"&gt;cars/index.html&lt;/a&gt;) - the HTML piece
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"notice"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= notice %&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;div class=&lt;/span&gt;&lt;span class="s2"&gt;"page-header"&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"cars"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;Cars&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&amp;gt;
&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"table table-hover"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;thead&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;th&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/th&amp;gt;
    &amp;lt;th&amp;gt;Drivers&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;th&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;th&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;Driver&lt;/span&gt; &lt;span class="no"&gt;Changes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/th&amp;gt;
    &amp;lt;th&amp;gt;Make&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;th&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;th&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/th&amp;gt;
    &amp;lt;th&amp;gt;Model&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;th&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;th&lt;/span&gt; &lt;span class="n"&gt;colspan&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"3"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/th&amp;gt;
  &amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/thead&amp;gt;

  &amp;lt;tbody&amp;gt;
  &amp;lt;% @cars.each do |car| %&amp;gt;
    &amp;lt;tr id="car_id_&amp;lt;%= car.id %&amp;gt;"&amp;gt;
      &amp;lt;td&amp;gt;&amp;lt;%= car.name %&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"cars--drivers"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= car.drivers_list %&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;td class=&lt;/span&gt;&lt;span class="s2"&gt;"cars--driver-changes"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= car.driver_changes %&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;td&amp;gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;car&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt; &lt;span class="sx"&gt;%&amp;gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= car.color %&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;td&amp;gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;car&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model&lt;/span&gt; &lt;span class="sx"&gt;%&amp;gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= link_to 'Show', car %&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;td&amp;gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="s1"&gt;'Edit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edit_car_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;car&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/td&amp;gt;
      &amp;lt;td&amp;gt;&amp;lt;%= link_to 'Destroy', car, method: :delete, data: { confirm: 'Are you sure?' } %&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/tr&amp;gt;
  &amp;lt;% end %&amp;gt;
  &amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/table&amp;gt;

&amp;lt;br&amp;gt;

&amp;lt;%= link_to 'New Car', new_car_path %&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part to point out in the above is the &lt;code&gt;data-controller&lt;/code&gt; data attribute.  Setting this to the name of the StimulusJS controller, &lt;code&gt;cars&lt;/code&gt;, will then cause it to reach out and invoke the &lt;code&gt;cars_controller.js&lt;/code&gt; connect function upon page render; subscribing the user to the cars action cable channel.  There is documentation on the StimulusJS &lt;a href="https://stimulusjs.org/"&gt;website&lt;/a&gt; explaining how that part works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cars Controller (&lt;a href="https://github.com/dstull/sidekiq-actioncable-stimulus-demo/blob/actioncable/app/javascript/controllers/cars_controller.js"&gt;cars_controller.js&lt;/a&gt;) - the StimulusJS Controller
&lt;/h2&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;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="s1"&gt;stimulus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;createChannel&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../exports/cable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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="kd"&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="nx"&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;initChannel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;initChannel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;createChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CarsChannel&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="nx"&gt;received&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;carRow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`#car_id_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;car_id&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;driverChanges&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;carRow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.cars--driver-changes&lt;/span&gt;&lt;span class="dl"&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;drivers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;carRow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.cars--drivers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;driverChanges&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;driver_changes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;drivers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;drivers&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above, we:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Importing the basic cable setup from &lt;code&gt;exports/cable.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Init the Channel from the connect function, which is fired whenever we land on the cars index page due to the &lt;code&gt;data-controller&lt;/code&gt; data attribute.&lt;/li&gt;
&lt;li&gt;Update the cars index page when a messages is received on the &lt;code&gt;CarsChannel&lt;/code&gt;. &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Cable Javascript (&lt;a href="https://github.com/dstull/sidekiq-actioncable-stimulus-demo/blob/actioncable/app/javascript/exports/cable.js"&gt;cable.js&lt;/a&gt;) - the basic Action Cable JS setup that is imported when needed
&lt;/h2&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="nx"&gt;cable&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;actioncable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;consumer&lt;/span&gt;&lt;span class="p"&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;function&lt;/span&gt; &lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&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="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;consumer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createConsumer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&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 above is the initial/standard Action Cable setup.  I went the 'extra mile' here and also used yarn to install &lt;a href="https://github.com/dstull/sidekiq-actioncable-stimulus-demo/blob/actioncable/yarn.lock#L166"&gt;actioncable&lt;/a&gt;, ensuring it was the same version as Rails.  This helps keep me completely out of the asset pipeline/sprockets area.&lt;/p&gt;

&lt;h1&gt;
  
  
  In Closing...
&lt;/h1&gt;

&lt;p&gt;I particularly wanted to get completely out of the Rails asset pipeline/sprockets setup and be page specific about my channel subscription.&lt;/p&gt;

&lt;p&gt;I hope this short demo is helpful to someone.  I searched many places to gather the bits and pieces of how to string this together, and felt I should share with the community that I have benefited so much from myself. I had only a few hours to throw this together before I had to get back to being a parent :) ...perhaps later I will add a blog on how I went about implementing testing all of this from soup to nuts.&lt;/p&gt;

&lt;p&gt;The basis for some of this work came from &lt;a href="https://evilmartians.com/chronicles/evil-front-part-3"&gt;this&lt;/a&gt; wonderful blog by Evil Martians.&lt;/p&gt;

</description>
      <category>stimulus</category>
      <category>actioncable</category>
      <category>rails</category>
      <category>sidekiq</category>
    </item>
    <item>
      <title>A simple Stimulus table filter demo</title>
      <dc:creator>Doug Stull</dc:creator>
      <pubDate>Sun, 04 Feb 2018 13:01:03 +0000</pubDate>
      <link>https://forem.com/dstull/a-simple-stimulus-table-filter-demo-3gmb</link>
      <guid>https://forem.com/dstull/a-simple-stimulus-table-filter-demo-3gmb</guid>
      <description>&lt;h1&gt;
  
  
  Goal
&lt;/h1&gt;

&lt;p&gt;Show the before and after implementation of table filtering with the new modest js framework named &lt;a href="https://stimulusjs.org/"&gt;stimulus&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We'll focus on a simple page with a table and filter capability.&lt;/p&gt;

&lt;p&gt;I'll break this down into 2 steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implementation without Stimulus&lt;/li&gt;
&lt;li&gt;Implementation with Stimulus&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Implementation without Stimulus
&lt;/h1&gt;

&lt;p&gt;To accomplish this part I created a &lt;a href="https://github.com/dstull/stimulus-demo/tree/without-stimulus"&gt;simple project&lt;/a&gt;.  There are 2 files that are involved:&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/dstull/stimulus-demo/blob/without-stimulus/app/views/hello/index.html"&gt;HTML File&lt;/a&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/dstull/stimulus-demo/blob/without-stimulus/app/assets/javascripts/hello.coffee"&gt;CoffeeScript file&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Here I am demonstrating the filtering of table rows when a user presses enter.&lt;/p&gt;

&lt;h1&gt;
  
  
  Implementation with Stimulus
&lt;/h1&gt;

&lt;p&gt;To accomplish this part I utilized the &lt;a href="https://github.com/dstull/stimulus-demo/"&gt;master branch&lt;/a&gt;. &lt;a href="https://github.com/dstull/stimulus-demo/commit/7681b4d0ab01c39ce09aaa8d562590a0b9539fb1"&gt;This&lt;/a&gt; commit shows the diff of the conversion. Final demo app can be found &lt;a href="https://stimulus-demo.herokuapp.com/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are 3 files that are involved:&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/dstull/stimulus-demo/blob/master/app/views/hello/index.html"&gt;HTML File&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The notable changes here to notice are the utilization of the data attributes of target, controller, and action.  The action specifically defines the javascript event keyup and the controller and function it will invoke.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/dstull/stimulus-demo/blob/master/app/javascript/packs/application.js"&gt;Application JS file&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Not much to say about this file, as it is the basic wiring needed as the entry-point for Stimulus use.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/dstull/stimulus-demo/blob/master/app/javascript/controllers/hello_controller.js"&gt;Hello Controller JS file&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Here I am demonstrating the conversion into Stimulus from original CoffeeScript.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusions
&lt;/h1&gt;

&lt;p&gt;Stimulus really shines when you can use it to describe the event handlers in the data attributes instead of having to deal with them in JavaScript.  If you are using server side rendering of html, this framework can really help you organize your javascript and manage events.&lt;/p&gt;

</description>
      <category>stimulus</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Stimulus with Turbolinks, select2 and formValidation</title>
      <dc:creator>Doug Stull</dc:creator>
      <pubDate>Sat, 03 Feb 2018 23:14:08 +0000</pubDate>
      <link>https://forem.com/dstull/stimulus-with-turbolinks-select2-and-formvalidation-1i7l</link>
      <guid>https://forem.com/dstull/stimulus-with-turbolinks-select2-and-formvalidation-1i7l</guid>
      <description>&lt;h1&gt;
  
  
  Goal
&lt;/h1&gt;

&lt;p&gt;Show the before and after implementation of &lt;a href="https://select2.org/"&gt;select2&lt;/a&gt;/&lt;a href="http://formvalidation.io/"&gt;formValidation&lt;/a&gt;/&lt;a href="https://github.com/turbolinks/turbolinks"&gt;Turbolinks&lt;/a&gt; with the new modest js framework named &lt;a href="https://stimulusjs.org/"&gt;stimulus&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am utilizing Rails as my framework here, but the setup really isn't about the framework.  However, this demonstrates a good use case of server side rendered html without frontend framework separation.&lt;/p&gt;

&lt;p&gt;I'll break this down into 2 steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implementation without Stimulus&lt;/li&gt;
&lt;li&gt;Implementation with Stimulus&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Implementation without Stimulus
&lt;/h1&gt;

&lt;p&gt;To accomplish this part I created a &lt;a href="https://github.com/dstull/stimulus-formvalidation/tree/without-stimulus"&gt;simple project&lt;/a&gt;.  There are 2 files that are involved:&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/dstull/stimulus-formvalidation/blob/without-stimulus/app/views/cars/_form.html.erb"&gt;Erb File&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In this file I am following the &lt;a href="http://formvalidation.io/examples/attribute/"&gt;declarative/html5 data attributes&lt;/a&gt; way of using FormValidation as I prefer to keep the javascript as generic as possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/dstull/stimulus-formvalidation/blob/without-stimulus/app/assets/javascripts/cars.coffee"&gt;CoffeeScript file&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Here I am demonstrating:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select2 with normal, multiple, and tags with createTag as a way to constrain allowable input&lt;/li&gt;
&lt;li&gt;FormValidation on Select2&lt;/li&gt;
&lt;li&gt;FormValidation with custom callback on name field&lt;/li&gt;
&lt;li&gt;FormValidation using revalidation and hidden input change when select2 change event is triggered&lt;/li&gt;
&lt;li&gt;Removing Select2 from DOM before Turbolinks caches the page, which keeps double Select2's from showing up in the page&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Implementation with Stimulus
&lt;/h1&gt;

&lt;p&gt;To accomplish this part I utilized the &lt;a href="https://github.com/dstull/stimulus-formvalidation/tree/master"&gt;master branch&lt;/a&gt;. &lt;a href="https://github.com/dstull/stimulus-formvalidation/commit/1739fc0ae05a739a6b4a3cb77c547085209c819f"&gt;This&lt;/a&gt; commit shows the diff of the conversion. Final demo app can be found &lt;a href="https://stimulus-formvalidation.herokuapp.com/cars"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are 3 files that are involved:&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/dstull/stimulus-formvalidation/blob/master/app/views/cars/_form.html.erb"&gt;Erb File&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The notable changes here to notice are the utilization of the data attributes of target, controller, and placeholder.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/dstull/stimulus-formvalidation/blob/master/app/javascript/packs/application.js"&gt;Application JS file&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Not much to say about this file, as it is the basic wiring needed as the entry-point for Stimulus use.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/dstull/stimulus-formvalidation/blob/master/app/javascript/controllers/cars_controller.js"&gt;Cars Controller JS file&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Here I am demonstrating:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Conversion into Stimulus from original CoffeeScript&lt;/li&gt;
&lt;li&gt;Additional storing of the selected items so they are preserved on forward and back buttons&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Conclusions
&lt;/h1&gt;

&lt;p&gt;I really like how this gives an easy way to leverage data attributes and hook easily into Javascript.  While I didn't show it here, but will in another blog, Stimulus really shines when you can use it to describe the event handlers in the data attributes instead of having to deal with them in JavaScript.&lt;/p&gt;

&lt;p&gt;I'd encourage people to give it a look.  As can be seen here, you can easily hook into existing jquery libraries using it.&lt;/p&gt;

&lt;p&gt;Thanks to &lt;a href="https://github.com/pascallaliberte/stimulus-turbolinks-select2"&gt;this repo&lt;/a&gt; for getting me past a few initial humps.&lt;/p&gt;

</description>
      <category>stimulus</category>
      <category>select2</category>
      <category>formvalidation</category>
      <category>turbolinks</category>
    </item>
    <item>
      <title>Docker + Rails + System tests with Headless Chrome</title>
      <dc:creator>Doug Stull</dc:creator>
      <pubDate>Sun, 12 Nov 2017 12:50:17 +0000</pubDate>
      <link>https://forem.com/dstull/docker--rails--system-tests-with-headless-chrome-d00</link>
      <guid>https://forem.com/dstull/docker--rails--system-tests-with-headless-chrome-d00</guid>
      <description>&lt;h1&gt;
  
  
  Goal
&lt;/h1&gt;

&lt;p&gt;Show a simple setup of headless system testing inside a container with Ruby on Rails.  &lt;/p&gt;

&lt;p&gt;There are likely many ways to achieve this goal.  However, my aim was to hook into an eventual CI system where the Rails application could be validated by building the docker image and running the test suite.&lt;/p&gt;

&lt;p&gt;I'll break this down into 3 steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create docker image with CentOS and ruby 2.4&lt;/li&gt;
&lt;li&gt;Create docker images for Rails and headless chrome testing&lt;/li&gt;
&lt;li&gt;Configure Rails for headless chrome system testing&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Create docker image with CentOS and ruby 2.4
&lt;/h1&gt;

&lt;p&gt;To accomplish this part I created a &lt;a href="https://github.com/dstull/ruby_24"&gt;simple project&lt;/a&gt; which provides a base image for the Rails application. I decided to create this base image because I wanted to stick with CentOS, which at the time of creating, I wasn't able to find on &lt;a href="https://hub.docker.com/"&gt;Docker Hub&lt;/a&gt; for ruby 2.4.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dockerfile
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM centos:centos7

# Install the appropriate software
RUN yum -y update &amp;amp;&amp;amp; yum clean all
RUN yum -y install \
            epel-release \
        which \
            &amp;amp;&amp;amp; \
        yum clean all \
            &amp;amp;&amp;amp; \
        :

# Install rvm, default ruby version and bundler.

RUN gpg --keyserver hkp://keys.gnupg.net --recv-keys D39DC0E3
RUN /bin/bash -l -c "curl -L get.rvm.io | bash -s stable"
RUN /bin/bash -l -c "echo 'source /etc/profile.d/rvm.sh' &amp;gt;&amp;gt; /etc/profile"
RUN /bin/bash -l -c "rvm install 2.4.2"
RUN /bin/bash -l -c "rvm cleanup all"
RUN /bin/bash -l -c "echo 'gem: --no-ri --no-rdoc' &amp;gt; .gemrc"
RUN /bin/bash -l -c "gem install bundler --no-ri --no-rdoc"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I then created a repository on &lt;a href="https://hub.docker.com/"&gt;Docker Hub&lt;/a&gt;, and setup an automated build against the &lt;a href="https://github.com/dstull/ruby_24"&gt;Github repo&lt;/a&gt;.  This allows it to automatically rebuild the image when changes are pushed to Github.&lt;/p&gt;

&lt;h1&gt;
  
  
  Create docker images for Rails and headless chrome testing
&lt;/h1&gt;

&lt;p&gt;Now that the base image for ruby is built and available on &lt;a href="https://hub.docker.com/r/hammer098/ruby_24/"&gt;Docker Hub&lt;/a&gt;, I can use it as the base in my example &lt;a href="https://github.com/dstull/docker-rails"&gt;Rails application&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here starts the tricky part:&lt;/p&gt;

&lt;p&gt;I wanted to keep the headless chrome pieces outside of the Rails application image that would go to production.  In order to do that, there are a few ways this can be accomplished:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Setup a container side by side with my application, and integrate using prebuilt &lt;a href="http://blog.tomazy.com/2017/05/testing-rails-app-in-docker-containers.html"&gt;Selenium containers&lt;/a&gt;.  I initially tried this, with the help of some Docker pros, but the test suite speed was just too slow (non headless setup/impatient developer/didn't try too hard to diagnose).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Setup a container with chrome installed along with the Rails application.  I ended up choosing this route, while accepting that the application image and the testing application image were not exactly the same.  &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Rails Application image setup
&lt;/h2&gt;

&lt;p&gt;In a &lt;a href="https://github.com/dstull/docker-rails/blob/master/sdlc/build"&gt;build script&lt;/a&gt; I performed the steps that would create the needed Dockerfile at runtime, and execute the build of the image.  This allows the specific application build steps to be kept common to this image and the test image.  Note that the Dockerfile should not be checked into the repo, but is in this application for demonstration purposes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build script(rails image function)
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;build_rails&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;cat &lt;/span&gt;Dockerfile.prefix.in &lt;span class="se"&gt;\&lt;/span&gt;
  sdlc/Dockerfile.in &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Dockerfile

  &lt;span class="nb"&gt;time &lt;/span&gt;docker-compose build &lt;span class="se"&gt;\&lt;/span&gt;
  rails
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As seen above, I am concatenating the Ruby base piece (&lt;a href="https://github.com/dstull/docker-rails/blob/master/Dockerfile.prefix.in"&gt;Dockerfile.prefix.in&lt;/a&gt;) with the common Rails part (&lt;a href="https://github.com/dstull/docker-rails/blob/master/sdlc/Dockerfile.in"&gt;sdlc/Dockerfile.in&lt;/a&gt;), and then into the final Dockerfile that acts as the input for the docker-compose build.  In a real world setup the Dockerfile 'in' file pieces maybe a bit larger, and have a postfix with variables defined to tag the image.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/dstull/docker-rails/blob/master/Dockerfile"&gt;Final Dockerfile&lt;/a&gt;
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM hammer098/ruby_24
# common rails build steps
WORKDIR /web

# Install rvm, default ruby version and bundler.
COPY .ruby-version /web/.ruby-version
COPY Gemfile /web/Gemfile
COPY Gemfile.lock /web/Gemfile.lock

RUN /bin/bash -l -c "bundle install"

COPY . /web
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Chrome + Rails Application image setup
&lt;/h2&gt;

&lt;p&gt;In the &lt;a href="https://github.com/dstull/docker-rails/blob/master/sdlc/build"&gt;build script&lt;/a&gt; similar steps were performed to craft the Dockerfile for the test image at time of build.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;build_rails_test&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;cat &lt;/span&gt;sdlc/Dockerfile.rails-test.in &lt;span class="se"&gt;\&lt;/span&gt;
  sdlc/Dockerfile.in &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; sdlc/Dockerfile.rails-test

  &lt;span class="nb"&gt;time &lt;/span&gt;docker-compose build &lt;span class="se"&gt;\&lt;/span&gt;
  rails-test
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In this function similar steps are being performed as in the Rails application build.  The steps unique to the test image are included in &lt;a href="https://github.com/dstull/docker-rails/blob/master/sdlc/Dockerfile.rails-test.in"&gt;sdlc/Dockerfile.rails-test.in&lt;/a&gt;. The common Rails part are then added, and sdlc/Dockerfile.rails-test is created to be used by the rails-test image.  Do not add sdlc/Dockerfile.rails-test to the repo as it is created 'on the fly' by this process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Dockerfile.rails-test
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM hammer098/ruby_24

RUN yum -y install \
            chromedriver \
            chromium \
            gnu-free-sans-fonts \
            xorg-x11-server-Xvfb \
            &amp;amp;&amp;amp; \
            yum clean all \
            &amp;amp;&amp;amp; \
            :
# common rails build steps
WORKDIR /web

# Install rvm, default ruby version and bundler.
COPY .ruby-version /web/.ruby-version
COPY Gemfile /web/Gemfile
COPY Gemfile.lock /web/Gemfile.lock

RUN /bin/bash -l -c "bundle install"

COPY . /web
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Configure Rails for headless chrome system testing
&lt;/h1&gt;

&lt;p&gt;For this part, I needed to configure 2 pieces:&lt;/p&gt;

&lt;h2&gt;
  
  
  Rails test configuration
&lt;/h2&gt;

&lt;p&gt;In this part we need to setup the headless chrome driver and enable specific chrome options.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/dstull/docker-rails/blob/master/test/application_system_test_case.rb"&gt;application_system_test_case.rb&lt;/a&gt;
&lt;/h3&gt;



&lt;div class="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="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'selenium-webdriver'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'capybara'&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;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register_driver&lt;/span&gt; &lt;span class="ss"&gt;:headless_chrome&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;app&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;

    &lt;span class="c1"&gt;# options explained https://peter.sh/experiments/chromium-command-line-switches/&lt;/span&gt;
    &lt;span class="c1"&gt;# no-sandbox&lt;/span&gt;
    &lt;span class="c1"&gt;#   because the user namespace is not enabled in the container by default&lt;/span&gt;
    &lt;span class="c1"&gt;# headless&lt;/span&gt;
    &lt;span class="c1"&gt;#   run w/o actually launching gui&lt;/span&gt;
    &lt;span class="c1"&gt;# disable-gpu&lt;/span&gt;
    &lt;span class="c1"&gt;#   Disables graphics processing unit(GPU) hardware acceleration&lt;/span&gt;
    &lt;span class="c1"&gt;# window-size&lt;/span&gt;
    &lt;span class="c1"&gt;#   sets default window size in case the smaller default size is not enough&lt;/span&gt;
    &lt;span class="c1"&gt;#   we do not want max either, so this is a good compromise&lt;/span&gt;

    &lt;span class="n"&gt;capabilities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Selenium&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WebDriver&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Remote&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Capabilities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="ss"&gt;chromeOptions: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;args: &lt;/span&gt;&lt;span class="sx"&gt;%w[no-sandbox headless disable-gpu window-size=1400,1400]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Selenium&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Driver&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;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;browser:              :chrome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;desired_capabilities: &lt;/span&gt;&lt;span class="n"&gt;capabilities&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;driven_by&lt;/span&gt; &lt;span class="ss"&gt;:headless_chrome&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 corresponding example tests that use this configuration can be found &lt;a href="https://github.com/dstull/docker-rails/tree/master/test/system"&gt;here.&lt;/a&gt;  An explanation of why I chose to use Rails system tests over rspec can be found &lt;a href="https://dev.to/dstull/my-journey-from-rspec-feature-tests-to-rails-system-tests"&gt;here.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Xvfb install and Docker entrypoint setup for test execution
&lt;/h2&gt;

&lt;p&gt;In the setup at work we needed &lt;a href="https://www.x.org/archive/X11R7.6/doc/man/man1/Xvfb.1.xhtml"&gt;xvfb&lt;/a&gt; installed for the tests to function properly.  However, we were also using an older chrome version(v59).  On this example setup I wasn't able to prove out that xvfb was required.  This was either due to the newer version of chrome (v62), or a less complex testing setup.&lt;/p&gt;

&lt;p&gt;Setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the xvfb-run rpm as seen above in the test docker image.&lt;/li&gt;
&lt;li&gt;Modify the entrypoint directive in docker-compose.yml to include xvfb-run and rails test execution steps.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/dstull/docker-rails/blob/master/docker-compose.yml"&gt;docker-compose.yml&lt;/a&gt; rails-test-run definition
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;rails-test-run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rails-test&lt;/span&gt;
    &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--login&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;xvfb-run&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;rails&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;test:system&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RAILS_ENV=test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In the above I am telling docker via the command &lt;code&gt;docker-compose run --rm rails-test-run&lt;/code&gt; to interactively login (bash --login), run xvfb, execute rails system tests, then exit.  The RAILS_ENV variable is set to test so that Rails doesn't by default first look at the development db setup.&lt;/p&gt;

&lt;p&gt;Here is an example output of a test run utilizing a &lt;a href="https://github.com/dstull/docker-rails/blob/master/sdlc/test"&gt;test script&lt;/a&gt;:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-rails&lt;span class="nv"&gt;$ &lt;/span&gt;sdlc/test
Starting rails-test, running and exiting

Run options: &lt;span class="nt"&gt;--seed&lt;/span&gt; 22514

&lt;span class="c"&gt;# Running:&lt;/span&gt;

Puma starting &lt;span class="k"&gt;in &lt;/span&gt;single mode...
&lt;span class="k"&gt;*&lt;/span&gt; Version 3.10.0 &lt;span class="o"&gt;(&lt;/span&gt;ruby 2.4.2-p198&lt;span class="o"&gt;)&lt;/span&gt;, codename: Russell&lt;span class="s1"&gt;'s Teapot
* Min threads: 0, max threads: 1
* Environment: test
* Listening on tcp://0.0.0.0:35683
Use Ctrl-C to stop
...

Finished in 8.587443s, 0.3493 runs/s, 0.4658 assertions/s.
3 runs, 4 assertions, 0 failures, 0 errors, 0 skips
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



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

&lt;p&gt;I found this setup to be the one that worked in our eventual goal of hooking into a CI system.&lt;/p&gt;

&lt;p&gt;The setup, as shown here, is far from complete for a Rails application destined for production.  Here are some of the things that I believe are out of scope and were left out of this article and the &lt;a href="https://github.com/dstull/docker-rails"&gt;example application&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Nginx/Unicorn install and setup&lt;/li&gt;
&lt;li&gt;This container is running as root, in our production setup, we run as a non root user.&lt;/li&gt;
&lt;li&gt;Setting container to read-only and adding VOLUME directives in the Dockerfile for certain areas Rails needs for writing. Changing of permissions/ownership to those files and directories at build time.&lt;/li&gt;
&lt;li&gt;Mysql containers for test/development.&lt;/li&gt;
&lt;li&gt;Environment variable breakout.&lt;/li&gt;
&lt;li&gt;Handling of compiled assets and js runtime env.
...many more&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>docker</category>
      <category>rails</category>
      <category>headlesschrome</category>
      <category>testing</category>
    </item>
    <item>
      <title>My sins against Webpacker/Yarn and compiled Rails assets</title>
      <dc:creator>Doug Stull</dc:creator>
      <pubDate>Fri, 14 Jul 2017 14:31:31 +0000</pubDate>
      <link>https://forem.com/dstull/my-sins-against-webpackeryarn-and-compiled-rails-assets</link>
      <guid>https://forem.com/dstull/my-sins-against-webpackeryarn-and-compiled-rails-assets</guid>
      <description>&lt;h2&gt;
  
  
  What did I do...
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Used &lt;a href="https://github.com/rails/webpacker"&gt;webpacker gem&lt;/a&gt; in Rails 5.x, and then checked all the webpacker/js packages into git that were installed by yarn.&lt;/li&gt;
&lt;li&gt;Compiled Rails assets, while still in dev, and checked them into git.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why did I do it?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;For Webpacker/Yarn, even though it has a yarn.lock file and such, I didn't see a reason not to. Although you could make 100 reasons for this being 'wrong'.  Plus, outside of our macs, gaining access to the outside world, installing yarn, etc, was close to impossible(well not impossible, just probably not worth it if we had another way).  So if we were to develop anywhere outside of our macs, we'd have issues...of course, we wouldn't be able to download new packages anyway at that point if not on our macs...hmmm&lt;/li&gt;
&lt;li&gt;For the Rails assets, since &lt;a href="https://github.com/rails/rails/commit/b1c08d8d9b921fdcf3813b5c20a0c3fab96eccca"&gt;yarn install&lt;/a&gt; and &lt;a href="https://github.com/rails/webpacker/commit/d2a6537e6ab0896a9bf14b6eddc5c0961db2c802"&gt;webpacker:compile&lt;/a&gt; is now 'baked in' to the rake assets:precompile command, we were stuck.  It is a monumental task to get yarn/node installed past our dev environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to make this work with precompiled assets in git and still develop like normal with uncompiled assets.
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The webpacker install and actually using it can be found on the webpacker github &lt;a href="https://github.com/rails/webpacker"&gt;page&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Force development and test to still use uncompiled assets as normal.
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Inside the config/environments/development.rb and test.rb add config.asssets.prefix directive to override the defaults:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="c1"&gt;# this allows us to precompile for non dev and commit, yet still use uncompiled for dev&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;assets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'/dev-assets'&lt;/span&gt; &lt;span class="c1"&gt;# can be anything really&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;How this works: since we will compile to the usual assets path, development and test will not find compiled assets and will default back to the uncompiled ones.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  If assets change, now you need an extra step before checking into git:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Note: Do this &lt;strong&gt;After&lt;/strong&gt; running tests...this part is important as I've seen the manifest.json get overwritten and pointing back to the webpacker local server on port 8080.&lt;/li&gt;
&lt;li&gt;In your terminal:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RAILS_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;production&lt;/span&gt; &lt;span class="n"&gt;rake&lt;/span&gt; &lt;span class="n"&gt;assets&lt;/span&gt;&lt;span class="ss"&gt;:precompile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Submit files into git as normal.  Since production env is setup by default when webpacker is installed, that is the optimal env to use for this, even if you have more than one type of 'production' environment.  Of course there are some that would say you should just have 3 environments in rails and use ENV vars to control everything else...depends on your setup/perspective, but I digress...&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion/Thoughts:
&lt;/h2&gt;

&lt;p&gt;This seems to be working so far, although there are some that would say I am breaking some cardinal rules of yarn/assets/etc, and some that probably say 'yeah I did that too'.  All I know is we needed a solution for the closed environment our apps live in, and this seemed to be a viable solution.  I didn't come up with this solution on my own, a bunch of googling, reading rails docs, and putting it all together here...  I hope this spurs some fruitful conversation and perhaps a more elegant solution than this one for the closed from the outside world/hard to get anything installed outside development/using your own infrastructure and not AWS/Heroku world.&lt;/p&gt;

&lt;p&gt;One other thing to mention...I believe this will help ease any transition we have into docker as well.  I've seen some strategies for compiling and maintaining assets for docker, and they all seem messy.  I believe this one will keep the docker image immutable and provide us with less friction between environments.&lt;/p&gt;

</description>
      <category>yarn</category>
      <category>rails</category>
      <category>assets</category>
    </item>
    <item>
      <title>My journey from Rspec feature tests to Rails system tests</title>
      <dc:creator>Doug Stull</dc:creator>
      <pubDate>Thu, 13 Jul 2017 00:30:41 +0000</pubDate>
      <link>https://forem.com/dstull/my-journey-from-rspec-feature-tests-to-rails-system-tests</link>
      <guid>https://forem.com/dstull/my-journey-from-rspec-feature-tests-to-rails-system-tests</guid>
      <description>&lt;h1&gt;
  
  
  Goals:
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Achieve reliable headless testing with system tests that utilize javascript&lt;/li&gt;
&lt;li&gt;Learn the new Rails system test setup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Quite often I find myself spending hours debugging(and sometimes finally deleting) a flaky feature test.  Finally I decided to stop doing that, and after watching the Rails Conf &lt;a href="https://www.youtube.com/watch?v=sSn4B8orX70"&gt;talk&lt;/a&gt; about system testing, decided it was time to act.&lt;/p&gt;

&lt;p&gt;Since my first goal is to always keep it simple, I didn't jump right away into the Rails system tests.  Due to the rest of my testing suite being written in Rspec, I first tried to go headless with that setup.&lt;/p&gt;

&lt;h1&gt;
  
  
  Rspec failure(s)
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Trying &lt;a href="https://robots.thoughtbot.com/headless-feature-specs-with-chrome"&gt;headless chrome with capybara&lt;/a&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  spec/rails_helper.rb
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register_driver&lt;/span&gt; &lt;span class="ss"&gt;:headless_chrome&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;app&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;capabilities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Selenium&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WebDriver&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Remote&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Capabilities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;chromeOptions: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;args: &lt;/span&gt;&lt;span class="sx"&gt;%w(headless disable-gpu)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Selenium&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;browser: :chrome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;desired_capabilities: &lt;/span&gt;&lt;span class="n"&gt;capabilities&lt;/span&gt;
&lt;span class="k"&gt;end&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;javascript_driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:headless_chrome&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Result:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Selenium::WebDriver::Error::unknownError...basically it gave me the 'is not clickable at point' error.  I tried working around these by doing simple fixes to capybara finders, digging more for answers, and in the end(about 30 minutes), couldn't get it resolved.  So I moved on to the next attempt... &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Trying &lt;a href="https://github.com/thoughtbot/capybara-webkit"&gt;capybara webkit&lt;/a&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Result:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Well this didn't go very far as it needed a &lt;a href="https://github.com/thoughtbot/capybara-webkit#qt-dependency-and-installation-issues"&gt;Qt&lt;/a&gt; dependency, and when I tried to brew install that, the compiler barfed due to, what seems to be, Xcode upgrade dependency.  Due to not having admin privileges on my mac, I took the easy way out and abandoned webkit for now. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Trying &lt;a href="https://github.com/teampoltergeist/poltergeist"&gt;poltergeist&lt;/a&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  spec/rails_helper.rb
&lt;/h3&gt;



&lt;div class="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;'capybara/poltergeist'&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;javascript_driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:poltergeist&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Modifying some tests for known 'click_on' &lt;a href="https://github.com/teampoltergeist/poltergeist/issues/530"&gt;issues&lt;/a&gt;:
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Before:
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;click_on&lt;/span&gt; &lt;span class="s1"&gt;'Create Engine'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  After:
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"input[type=submit][value='Create Engine']"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'click'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Result:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Success! ...or so I thought.  All features passed.  However, when ran with entire test suite, they randomly failed.  So I tried fixing them with different swap-outs for 'have_content' checks and such.  Those then passed, ran again, they failed and the others(that I didn't fix) passed.  At this point I was thoroughly frustrated, because either I run my test suite for features separately from other tests, or I need to fix this...&lt;/li&gt;
&lt;li&gt;Note: I &lt;em&gt;assume&lt;/em&gt; this has to due with the database cleanup and different AR threads and such that I've read about.  I did have my database cleaner setup to truncate on js tests, but when ran in headless mode with the rest of my test suite, it was flaky.  Also, before going down this headless path, they worked fine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Being fed up with flaky tests and 3 failed attempts at remedying this in Rspec, I decided to now move on and try the Rails System Tests...&lt;/p&gt;

&lt;h1&gt;
  
  
  Rails System Tests:
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Setup:
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Prerequisites:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Rails 5.1&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/teampoltergeist/poltergeist"&gt;Poltergeist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/freerange/mocha"&gt;Mocha&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/puma/puma"&gt;Puma&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Some other gems I like: FactoryGirl/Capybara(of course), standard test setup&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  test/test_helper.rb
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Simple setup really, only added a few helpers and such:&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expand_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'../../config/environment'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rails/test_help'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'mocha/test_unit'&lt;/span&gt; &lt;span class="c1"&gt;# for stubs&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'minitest/spec'&lt;/span&gt; &lt;span class="c1"&gt;# for let/it/etc&lt;/span&gt;

&lt;span class="c1"&gt;# not sure about this, but rails kinda does it here...feels sloppy, but I hate&lt;/span&gt;
&lt;span class="c1"&gt;# typing require in all my test files&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/support/**/*.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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TestCase&lt;/span&gt;
  &lt;span class="c1"&gt;# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.&lt;/span&gt;
  &lt;span class="n"&gt;fixtures&lt;/span&gt; &lt;span class="ss"&gt;:all&lt;/span&gt;

  &lt;span class="c1"&gt;# Add more helper methods to be used by all tests here...&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;MiniTest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Spec&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DSL&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;WaitForAjax&lt;/span&gt; &lt;span class="c1"&gt;# hack for remote js calls that waits for ajax to be done&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  test/application_system_test_case.rb
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;This file comes with Rails 5.1, but I switched it to use poltergeist:&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="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="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'capybara/poltergeist'&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="n"&gt;driven_by&lt;/span&gt; &lt;span class="ss"&gt;:poltergeist&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  test/system/app/my_map_test.rb
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;This is just one of the test files, but good enough to show the setup&lt;/li&gt;
&lt;li&gt;I like to abstract away a lot of the standard Capybara DOM traversal steps, so those will be in the next file.&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="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;'application_system_test_case'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyMapTest&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StandardType&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;MyMapTest&lt;/span&gt;
    &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:subject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;MyMapForm&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="s1"&gt;'standard_type'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s1"&gt;'page has different types'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit_page&lt;/span&gt;

      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;has_content?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'first type'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;has_content?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'second type'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;has_content?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'third type'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s1"&gt;'page has address link'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit_page&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;has_link?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Address Import'&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Note: this was originally written in Rspec, but the conversion was simple...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;s/describe/class, s/it/test, s/expect..to/assert, s/have_content/has_content?..etc&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  test/support/forms/my_map_form.rb
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;This is my file to hold all the repeatable/dirty details about traversing the DOM&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="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;MyMapForm&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DSL&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;FactoryGirl&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Syntax&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Methods&lt;/span&gt;
  &lt;span class="kp"&gt;include&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;url_helpers&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# using factory here of course and converting to find the factory&lt;/span&gt;
    &lt;span class="vi"&gt;@item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# one more...&lt;/span&gt;
    &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;app&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;visit_page&lt;/span&gt;
    &lt;span class="c1"&gt;# don't care about authenticating here&lt;/span&gt;
    &lt;span class="c1"&gt;# in rspec, this uses allow_any_instance_of...&lt;/span&gt;
    &lt;span class="no"&gt;ApplicationController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stubs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:require_long&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;ApplicationController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stubs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:can_edit?&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="n"&gt;app_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;test: &lt;/span&gt;&lt;span class="s1"&gt;'My Map'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt; &lt;span class="c1"&gt;# in non headless, click_on 'My Map' works&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

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

  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:app&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusions:
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Pros:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It seems to work so far...and let's be honest, that is the most important thing.&lt;/li&gt;
&lt;li&gt;Default functionality of a screenshot in my terminal on failure.  Basic, but nice.&lt;/li&gt;
&lt;li&gt;Seems to solve the multiple db connection issue, and therefore reducing the flakiness of these gui driven system tests&lt;/li&gt;
&lt;li&gt;Runs faster than non headless implementation&lt;/li&gt;
&lt;li&gt;I suppose it makes running tests in CI env easier, as we can skip some of the browser setup?&lt;/li&gt;
&lt;li&gt;Gives the team some exposure to Rails test framework (this can also be seen as a con below)&lt;/li&gt;
&lt;li&gt;By using a different test suite for system tests, it gives a clear line of delineation in configs and troubleshooting, etc.&lt;/li&gt;
&lt;li&gt;Is part of Rails core, so I have full confidence in the future support and improvements&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cons:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Adds technical debt to the team since we do everything else in Rspec&lt;/li&gt;
&lt;li&gt;Documentation/examples for Rspec is miles ahead from what I've been able to find for Rails System tests (hence some of the reason for this writeup)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts:
&lt;/h2&gt;

&lt;p&gt;While this journey into Rails System tests feels good today, it is quite possible I will look back in a month or so and wonder...what was I thinking using this and multiple test suites...  But then again, don't we always look back and have those thoughts about our development decisions?&lt;/p&gt;

&lt;p&gt;Of note, I also see that &lt;a href="https://github.com/rspec/rspec-rails"&gt;rspec-rails&lt;/a&gt;, is planning on supporting this soon as well, I think, as seen by this &lt;a href="https://github.com/rspec/rspec-rails/pull/1813"&gt;issue&lt;/a&gt;. If that comes to fruition, it will be worth a look, and definitely worth consolidating back into one test suite...we'll see.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>rspec</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
