<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dr Nic Williams</title>
    <description>The latest articles on DEV Community by Dr Nic Williams (@drnic).</description>
    <link>https://dev.to/drnic</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%2F440668%2F7178c97c-ea0b-40c6-99a8-6c3db175dba6.jpeg</url>
      <title>DEV Community: Dr Nic Williams</title>
      <link>https://dev.to/drnic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/drnic"/>
    <language>en</language>
    <item>
      <title>Broadcasting custom Turbo actions like set_title, morph, and more</title>
      <dc:creator>Dr Nic Williams</dc:creator>
      <pubDate>Wed, 28 Dec 2022 22:33:42 +0000</pubDate>
      <link>https://dev.to/drnic/broadcasting-custom-turbo-actions-like-settitle-morph-and-more-3e22</link>
      <guid>https://dev.to/drnic/broadcasting-custom-turbo-actions-like-settitle-morph-and-more-3e22</guid>
      <description>&lt;p&gt;One of the great new features of Hotwire Turbo 7.2 was custom actions. Originally, Turbo allowed us to send stream actions to the browser to add, remove, or replace some HTML. Now, we can tell the browser to do anything: console log, set title, play sounds, and use the morphdom library for powerful changes.&lt;/p&gt;

&lt;p&gt;There are two ways for a Rails app to emit Stream Actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;return one or more of them from a controller action with a &lt;code&gt;turbo_stream&lt;/code&gt; format, and an &lt;code&gt;turbo_stream.erb&lt;/code&gt; action template file.&lt;/li&gt;
&lt;li&gt;broadcast them to subscribers of a Turbo Stream&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this post I will recap how to send custom actions via &lt;code&gt;turbo_stream&lt;/code&gt; action responses, and then cover the new thing I had to figure out: how to broadcast custom actions to all subscribers of a Turbo Stream.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are the built-in actions?
&lt;/h2&gt;

&lt;p&gt;Hotwire Turbo provides a small set of Stream Actions &lt;a href="https://www.rubydoc.info/gems/turbo-rails/0.5.2/Turbo/Streams/TagBuilder" rel="noopener noreferrer"&gt;out of the box&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;append&lt;/li&gt;
&lt;li&gt;prepend&lt;/li&gt;
&lt;li&gt;remove&lt;/li&gt;
&lt;li&gt;replace&lt;/li&gt;
&lt;li&gt;update&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What makes a Turbo Stream Action is determined by the JavaScript side of the Turbo library. When an &lt;code&gt;append&lt;/code&gt; or &lt;code&gt;remove&lt;/code&gt; action is sent to the browser -- by controller action response, or broadcast stream -- the client-side JavaScript looks at the Action and determines how to handle it.&lt;/p&gt;

&lt;p&gt;In all 5 built-in actions above, the client-side JavaScript mutates the browser DOM to add, change, or remove elements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sending built-in actions to the browser
&lt;/h2&gt;

&lt;p&gt;As mentioned above, we can send Stream Actions to the browser from controller action responses, or via broadcasts.&lt;/p&gt;

&lt;p&gt;For controller action responses,&lt;/p&gt;

&lt;p&gt;1) your action needs to specify the &lt;code&gt;turbo_stream&lt;/code&gt; response format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
  &lt;span class="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="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="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;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;2) you need a corresponding &lt;code&gt;create.turbo_stream.erb&lt;/code&gt; view template file. Within it, you are yielded a &lt;code&gt;turbo_stream&lt;/code&gt; object upon which you construct Stream Actions that will be send back to the browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt; &lt;span class="ss"&gt;:new_button&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For broadcasting actions,&lt;/p&gt;

&lt;p&gt;1) you setup client-side subscriptions called Streams, by using the &lt;code&gt;turbo_stream_from&lt;/code&gt; helper. Below, the resulting page will subscribe to any actions associated with a specific &lt;code&gt;Book&lt;/code&gt; instance.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;2) now broadcast actions from anywhere on the server-side and they will be sent to the required browsers. For example, we can broadcast replacement HTML if a &lt;code&gt;Book&lt;/code&gt; instance changes from within the &lt;code&gt;Book&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Book&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;after_update_commit&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;broadcast_replace_later_to&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;target: &lt;/span&gt;&lt;span class="n"&gt;dom_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&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;"books/book_summary"&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;book: &lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;broadcast_replace_later_to&lt;/code&gt; helper will construct a &lt;code&gt;replace&lt;/code&gt; Stream Action, including the newly rendered partial &lt;code&gt;app/views/books/_book_summary.html.erb&lt;/code&gt;, and ask the background job system to send out the request to 0+ subscribers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are custom actions?
&lt;/h2&gt;

&lt;p&gt;So if an action is implemented in client-side JavaScript, why can't we do arbitrary things? Log something to the browser console? Dispatch events to the DOM? Activate client-side JavaScript?&lt;/p&gt;

&lt;p&gt;Thanks to Turbo 7.2 we now can dispatch arbitrary "custom" actions, and provide our own JavaScript to handle them.&lt;/p&gt;

&lt;p&gt;Marco Roth's article &lt;a href="https://dev.to/marcoroth/turbo-72-a-guide-to-custom-turbo-stream-actions-4h0e"&gt;Turbo 7.2: A guide to Custom Turbo Stream Actions&lt;/a&gt; is the go-to guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moar custom actions
&lt;/h2&gt;

&lt;p&gt;Marco also wrote a huge library of Stream Actions you might want to use called &lt;a href="https://github.com/marcoroth/turbo_power" rel="noopener noreferrer"&gt;Turbo Power&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's a screenshot for dramatic effect:&lt;/p&gt;

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

&lt;p&gt;In your controller &lt;code&gt;turbo_stream.erb&lt;/code&gt; response you can set the page title, and log a message to the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"New Page Title goes here"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;console_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"We're hiring if you can see this!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Broadcasting custom actions
&lt;/h2&gt;

&lt;p&gt;But what if you want to broadcast a &lt;code&gt;set_title&lt;/code&gt; custom action to all subscribers of a stream, not just one user?&lt;/p&gt;

&lt;p&gt;The good news is that a Stream Action that is sent to the browser via controller actions or via broadcasting is the same message. What changes is how we build the Stream Action and broadcast it.&lt;/p&gt;

&lt;p&gt;Whilst there are nice helpers like &lt;code&gt;broadcast_replace_later_to&lt;/code&gt; for built-in actions, I could not find an equivalently concise way to broadcast arbitrary custom actions.&lt;/p&gt;

&lt;p&gt;As of writing, I found I had to use some low-level methods to broadcast custom actions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Book&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="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Turbo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Streams&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ActionHelper&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Turbo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Streams&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;StreamName&lt;/span&gt;

  &lt;span class="n"&gt;after_update_commit&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream_action_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:set_title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s2"&gt;"Book: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="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="n"&gt;stream_name_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Send multiple Stream Actions to the same subscribers by concatenating them together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream_action_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:set_title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s2"&gt;"Book: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream_action_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:console_log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="s2"&gt;"Book: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="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="n"&gt;stream_name_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Excellent, now we can broadcast to all stream subscriber any arbitrary custom Stream Action; and thanks to Marco's &lt;code&gt;turbo-power&lt;/code&gt; library we can now do just about anything to the browser without needing to write some bespoke JavaScript to handle it. Lovely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Epilogue
&lt;/h2&gt;

&lt;p&gt;After posting, I chatted with Marco and after a few iterations he suggested the following syntax idea that I like a lot:&lt;/p&gt;

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

</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>Friendly IDs for Ruby on Rails</title>
      <dc:creator>Dr Nic Williams</dc:creator>
      <pubDate>Thu, 20 Oct 2022 22:08:15 +0000</pubDate>
      <link>https://dev.to/drnic/friendly-ids-for-ruby-on-rails-1c8p</link>
      <guid>https://dev.to/drnic/friendly-ids-for-ruby-on-rails-1c8p</guid>
      <description>&lt;p&gt;Do you have URLs like &lt;code&gt;/books/1&lt;/code&gt; or &lt;code&gt;/secret_things/10&lt;/code&gt; and wish that they were friendlier? Also, wish the IDs were sequential and guessable?&lt;/p&gt;

&lt;p&gt;I like Stripe's URLs, such as &lt;code&gt;https://dashboard.stripe.com/test/products/prod_MG5m4q7sKvGto8&lt;/code&gt;. If I see the ID value &lt;code&gt;prod_MG5m4q7sKvGto8&lt;/code&gt; anywhere I know it belongs to a Stripe Product. &lt;code&gt;cus_MG5sTiccdSlpjw&lt;/code&gt;? A Stripe Customer ID. Very friendly.&lt;/p&gt;

&lt;p&gt;And the IDs are not sequential. Friendly and random. I want this.&lt;/p&gt;

&lt;p&gt;I'm going to show you the project I use for friendly IDs, and how I add them to my Rails scaffolding generators so they are automatically enabled for every model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails generate scaffold Book title description:text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And my URLs automatically pop out looking like &lt;code&gt;/books/book_qYlVPJvDprRabHa0wy1xz3n9&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A quick tweak of the generated class and they might become even nicer, &lt;code&gt;/books/bk_qYlVPJvDprRabHa0wy1xz3n9&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Yeah, you want this too.&lt;/p&gt;

&lt;h2&gt;
  
  
  prefixed_ids gem
&lt;/h2&gt;

&lt;p&gt;The magic is provided by Chris Oliver's &lt;a href="https://github.com/excid3/prefixed_ids"&gt;prefixed_ids&lt;/a&gt; rubygem.&lt;/p&gt;

&lt;p&gt;The only requirement is that your models' &lt;code&gt;id&lt;/code&gt; column is &lt;code&gt;bigint&lt;/code&gt; or some other integer. I was not successful using the gem with &lt;code&gt;uuid&lt;/code&gt; columns. But you won't need UUID IDs because you'll have publicly friendly, random IDs, and internally sequential IDs.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Trying it out
&lt;/h2&gt;

&lt;p&gt;Before we go and edit your scaffold generators, let's try it out on an existing model.&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;has_prefix_id :thing&lt;/code&gt; to one of your models, and go and view it in your app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Book&lt;/span&gt;
  &lt;span class="n"&gt;has_prefix_id&lt;/span&gt; &lt;span class="ss"&gt;:bk&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your boring IDs now look gloriously friendly, &lt;code&gt;/books/bk_x912t423&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The gem hijacks the &lt;code&gt;find&lt;/code&gt; and &lt;code&gt;to_param&lt;/code&gt; methods. Everything just works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding it to your model generator
&lt;/h2&gt;

&lt;p&gt;You definitely want friendly IDs for all your future models. That is, when you run either:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails g model Book title
rails g scaffold Book title
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You want the resulting &lt;code&gt;app/models/book.rb&lt;/code&gt; to include the &lt;code&gt;has_prefix_id :bk&lt;/code&gt; link from above.&lt;/p&gt;

&lt;p&gt;First, copy the current Rails &lt;a href="https://github.com/rails/rails/blob/main/activerecord/lib/rails/generators/active_record/model/templates/model.rb.tt"&gt;&lt;code&gt;model.rb.tt&lt;/code&gt; template&lt;/a&gt; into your app.&lt;/p&gt;

&lt;p&gt;If you're using Jumpstart Pro, you can skip this step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p lib/templates/active_record/model
curl https://raw.githubusercontent.com/rails/rails/main/activerecord/lib/rails/generators/active_record/model/templates/model.rb.tt \
  -o lib/templates/active_record/model/model.rb.tt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file will be used to generate all your future model class files.&lt;/p&gt;

&lt;p&gt;In the newly created &lt;code&gt;model.rb.tt&lt;/code&gt; file, add the 3rd line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;% module_namespacing do -%&amp;gt;
class &amp;lt;%= class_name %&amp;gt; &amp;lt; &amp;lt;%= parent_class_name.classify %&amp;gt;
  has_prefix_id :&amp;lt;%= singular_name %&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you were to generate a &lt;code&gt;Book&lt;/code&gt; model, it would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Book&lt;/span&gt;
  &lt;span class="n"&gt;has_prefix_id&lt;/span&gt; &lt;span class="ss"&gt;:book&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can edit &lt;code&gt;:book&lt;/code&gt; to &lt;code&gt;:bk&lt;/code&gt;. Pick an abbreviation that resonates with your URL-appreciating customers.&lt;/p&gt;

&lt;p&gt;Show them you care.&lt;/p&gt;

</description>
      <category>rails</category>
    </item>
    <item>
      <title>./bin/commits-since-last-production-deploy</title>
      <dc:creator>Dr Nic Williams</dc:creator>
      <pubDate>Wed, 23 Mar 2022 21:10:53 +0000</pubDate>
      <link>https://dev.to/drnic/bincommits-since-last-production-deploy-4ka8</link>
      <guid>https://dev.to/drnic/bincommits-since-last-production-deploy-4ka8</guid>
      <description>&lt;p&gt;When you work in a team, with multiple people taking on responsibility for moving work into production, it can mean that you are never 100% confident whether the current &lt;code&gt;main&lt;/code&gt;/&lt;code&gt;master&lt;/code&gt; branch has been fully deployed or not. What's running on prod?&lt;/p&gt;

&lt;p&gt;Or rather, what's &lt;strong&gt;NOT&lt;/strong&gt; running on prod yet?&lt;/p&gt;

&lt;p&gt;Can I deploy my new commits merged into &lt;code&gt;main&lt;/code&gt; through to production because they are all mine, and mine alone?&lt;/p&gt;

&lt;p&gt;Or are there some gnarly new contributions that have been foisted on to the shared work branch, and you thoroughly want to review them before you take responsibility for sending them off into production, and potentially causing problems for customers, support team, and then the dev team in a few minutes or hours?&lt;/p&gt;

&lt;p&gt;I wrote a script to that I have started adding to every Heroku-hosted project &lt;code&gt;./bin/commits-since-last-production-deploy&lt;/code&gt; that is shown below and &lt;a href="https://gist.github.com/drnic/b0be3d3bb27abb5635955bf93bed1b6a"&gt;hosted on a Gist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To compare the current branch against a specific Heroku application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./bin/commits-since-last-production-deploy &lt;span class="nt"&gt;-a&lt;/span&gt; myapp-stg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any additional args are forwarded on to the &lt;code&gt;git log&lt;/code&gt; command, such as &lt;code&gt;--oneline&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./bin/commits-since-last-production-deploy &lt;span class="nt"&gt;-a&lt;/span&gt; myapp-stg &lt;span class="nt"&gt;--oneline&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have a default &lt;code&gt;heroku&lt;/code&gt; remote so you can &lt;code&gt;git push heroku HEAD:main&lt;/code&gt; like a wild thing, then you can drop the &lt;code&gt;-a&lt;/code&gt; flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./bin/commits-since-last-production-deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script is below, and is &lt;a href="https://gist.github.com/drnic/b0be3d3bb27abb5635955bf93bed1b6a"&gt;hosted on a Gist&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;help&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Usage: ./bin/commits-since-last-production-deploy [-a myapp ] [ -h ]"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;2
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; heroku &amp;amp;&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Install 'heroku' CLI"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; jq &amp;amp;&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Install 'jq' CLI"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

while &lt;/span&gt;&lt;span class="nb"&gt;getopts&lt;/span&gt; &lt;span class="s1"&gt;'a:h'&lt;/span&gt; opt&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="nt"&gt;-a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;appname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;shift &lt;/span&gt;2
    &lt;span class="p"&gt;;;&lt;/span&gt;
  &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nt"&gt;--help&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;help
    exit &lt;/span&gt;2
    &lt;span class="p"&gt;;;&lt;/span&gt;
  &lt;span class="k"&gt;esac&lt;/span&gt;
&lt;span class="k"&gt;done

&lt;/span&gt;&lt;span class="nv"&gt;last_deploy_result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;heroku releases &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;appname&lt;/span&gt;:+-a&lt;span class="p"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$appname&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;".[0].description"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$last_deploy_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ ^&lt;span class="o"&gt;(&lt;/span&gt;Deploy &lt;span class="o"&gt;(&lt;/span&gt;.&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;sha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASH_REMATCH&lt;/span&gt;&lt;span class="p"&gt;[2]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  git log &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$sha&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;..HEAD &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Last deploy to production did not succeed"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"--&amp;gt; &lt;/span&gt;&lt;span class="nv"&gt;$last_deploy_result&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fundamentally, this runs &lt;code&gt;heroku releases&lt;/code&gt; on your app, gets the Git SHA-1 that was deployed, then compares it against the list of commits on the current branch.&lt;/p&gt;

&lt;p&gt;If you see no output, then there are no new commits that are not yet on production. You are free. Production is all yours. BWAHAHA.&lt;/p&gt;

</description>
      <category>heroku</category>
    </item>
    <item>
      <title>What is my current Tailwind CSS breakpoint?</title>
      <dc:creator>Dr Nic Williams</dc:creator>
      <pubDate>Tue, 01 Mar 2022 22:56:27 +0000</pubDate>
      <link>https://dev.to/drnic/what-is-my-current-tailwind-css-breakpoint-4p2m</link>
      <guid>https://dev.to/drnic/what-is-my-current-tailwind-css-breakpoint-4p2m</guid>
      <description>&lt;p&gt;When you're designing your site and fiddling around with which Tailwind CSS classes should apply starting at each breakpoint, mobile (the default), &lt;code&gt;sm:&lt;/code&gt;, &lt;code&gt;md:&lt;/code&gt;, and beyond, it can be very handy for your browser to tell you definitively what breakpoint is currently active.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5rw6eyxheueh3if96gnr.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%2F5rw6eyxheueh3if96gnr.png" alt="breakpoint-lg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi34mwt8taws6qgj04ed6.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%2Fi34mwt8taws6qgj04ed6.png" alt="breakpoint-mobile"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a partial/fragment/component with the following HTML, which uses the &lt;code&gt;sticky z-50 top-2 left-2&lt;/code&gt; classes to ensure your little badge always appears in the top left corner.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sticky z-50 top-2 left-2 max-w-xs text-gray-900 bg-gray-50"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"p-2 text-xs"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Breakpoint:
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sm:hidden"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;mobile&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hidden sm:inline md:hidden"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;sm&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hidden md:inline lg:hidden"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;md&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hidden lg:inline xl:hidden"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;lg&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hidden xl:inline 2xl:hidden"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;xl&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hidden 2xl:inline 3xl:hidden"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;2xl&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hidden 3xl:inline 4xl:hidden"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;3xl&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hidden 4xl:inline"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;4xl+&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&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;Next, in your most outer layout, inject this partial/component when the current page's parameters include &lt;code&gt;dev=1&lt;/code&gt; or similar, near the top of the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;

&lt;p&gt;For example, in Ruby on Rails you might put the following inside your &lt;code&gt;application.html.erb&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;render&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;shared&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;sticky_panel&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="na"&gt;:dev&lt;/span&gt;&lt;span class="err"&gt;].&lt;/span&gt;&lt;span class="na"&gt;presence&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;Want to try it out? It's currently live to the public at &lt;a href="https://app.rcrdshp.com/?dev=1" rel="noopener noreferrer"&gt;https://app.rcrdshp.com/?dev=1&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>css</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>How to display Admin-only links with CSS</title>
      <dc:creator>Dr Nic Williams</dc:creator>
      <pubDate>Mon, 21 Feb 2022 22:39:58 +0000</pubDate>
      <link>https://dev.to/drnic/how-to-display-admin-only-links-with-css-2d95</link>
      <guid>https://dev.to/drnic/how-to-display-admin-only-links-with-css-2d95</guid>
      <description>&lt;p&gt;It is very convenient for staff/admins to sprinkle "admin-only" links around the application. Since you're always a staff/admin user, how do you distinguish staff-only links from normal links? How do you make sure you're not exposing "dead" admin-only links to customers?&lt;/p&gt;

&lt;p&gt;I like to annotate my admin-only links with a little SVG icon to indicate that this link is for staff/admin only:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NpFKJ2Ul--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qo1dte0otkxndsusyzzo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NpFKJ2Ul--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qo1dte0otkxndsusyzzo.png" alt="Annotated link with a tiny SVG icon" width="710" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a normal &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; link with a class &lt;code&gt;admin-only&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/admin/deals/1234"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"admin-only"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Internal docs&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is implemented with a tiny piece of CSS to use the magic &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::after"&gt;::after&lt;/a&gt; pseudo-element, and the &lt;code&gt;content&lt;/code&gt; property.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="nc"&gt;.admin-only&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="nc"&gt;.admin-only&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-2px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url('data:image/svg+xml; utf8, &amp;lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"&amp;gt;&amp;lt;path d="M4 8V6a6 6 0 1 1 12 0v2h1a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2v-8c0-1.1.9-2 2-2h1zm5 6.73V17h2v-2.27a2 2 0 1 0-2 0zM7 6v2h6V6a3 3 0 0 0-6 0z"/&amp;gt;&amp;lt;/svg&amp;gt;')&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#444&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;content&lt;/code&gt; value could be plain text, such as &lt;code&gt;(admin)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;top&lt;/code&gt;/&lt;code&gt;right&lt;/code&gt; help to place the SVG icon to the right of the &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; text on the screen. Maybe you'll need different values for different sized icons or fonts.&lt;/p&gt;

&lt;p&gt;I love that I can inject any SVG into the &lt;code&gt;content&lt;/code&gt; property, and with the &lt;code&gt;::after&lt;/code&gt; pseudo property I can place these SVG icons after any text. Seems a perfect use case to indicate which links are admin-only.&lt;/p&gt;

</description>
      <category>css</category>
    </item>
    <item>
      <title>What is the ordering of Ruby on Rails controller filters?</title>
      <dc:creator>Dr Nic Williams</dc:creator>
      <pubDate>Tue, 10 Aug 2021 01:00:30 +0000</pubDate>
      <link>https://dev.to/drnic/what-is-the-ordering-of-ruby-on-rails-controller-filters-544j</link>
      <guid>https://dev.to/drnic/what-is-the-ordering-of-ruby-on-rails-controller-filters-544j</guid>
      <description>&lt;p&gt;Some of my application's controllers are a blend of modules from other people's Rubygems, such as ShopifyApp library. They inject before and after action filters around my actions. Sometimes they abort the request and redirect somewhere else. I wanted to know what filters were being invoked and in what order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;AuthenticatedController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="n"&gt;__callbacks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:process_action&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_variable_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:"@key"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output was like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:around&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;82000&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:before&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:set_shop_host&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:before&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:redirect_to_splash_page&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:before&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:set_locale&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:after&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:set_test_cookie&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:before&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:verify_authenticity_token&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:after&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:verify_same_origin_request&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:after&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:set_esdk_headers&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:before&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:login_again_if_different_user_or_shop&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:around&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:activate_shopify_session&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:before&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:set_shop_origin&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;



</description>
      <category>rails</category>
      <category>shopifyapp</category>
    </item>
    <item>
      <title>How to isolate your Rails blobs in subfolders</title>
      <dc:creator>Dr Nic Williams</dc:creator>
      <pubDate>Fri, 11 Jun 2021 04:20:04 +0000</pubDate>
      <link>https://dev.to/drnic/how-to-isolate-your-rails-blobs-in-subfolders-1n0c</link>
      <guid>https://dev.to/drnic/how-to-isolate-your-rails-blobs-in-subfolders-1n0c</guid>
      <description>&lt;p&gt;Ruby on Rails has a very nice abstraction for storing user-provided assets and images to any cloud (AWS S3, Google, Azure, local files etc) called &lt;a href="https://guides.rubyonrails.org/active_storage_overview.html"&gt;Active Storage&lt;/a&gt;. If you are spinning up new deployments of your application with each branch/pull request, you will want the uploaded assets to go nicely into their own subfolders. Much easier to clean them up later. Unfortunately this is not possible out of the box.&lt;/p&gt;

&lt;p&gt;Fortunately, with Ruby, we can fix anything.&lt;/p&gt;

&lt;p&gt;Firstly, we need a solution that does not alienate all existing blobs. We only want to put new blobs into subfolders. Existing blobs can stay exactly where they are.&lt;/p&gt;

&lt;p&gt;I've deployed two demonstration apps that show the internal &lt;code&gt;key&lt;/code&gt; to demonstrate that the images are stored in different subfolders:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://subfolder-demo-1111.herokuapp.com/"&gt;https://subfolder-demo-1111.herokuapp.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://subfolder-demo-2222.herokuapp.com/"&gt;https://subfolder-demo-2222.herokuapp.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The new files in each app are now being stored in subfolders, whilst legacy files are kept in their original file in the root of the bucket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ aws s3 ls s3://mybucket/
                           PRE subfolder-demo-1111/
                           PRE subfolder-demo-2222/
2021-06-11 12:08:16    1579488 g7643rle6b6bgn38v16734062wqw
2021-06-11 13:50:33    5034683 ki79m2n0p5ib3hg8l6bf1z7yrclt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the sample solution below, I'm going to prefix all uploaded blobs with the name of the deployment. On Heroku this is available at runtime from the environment variable &lt;code&gt;$HEROKU_APP_NAME&lt;/code&gt; after you turn on &lt;a href="https://devcenter.heroku.com/articles/dyno-metadata"&gt;dyno metadata&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Each user-provided file is stored in your blobstore and registered in your database with an &lt;code&gt;ActiveStorage::Blob&lt;/code&gt;. The &lt;code&gt;ActiveStorage::Blob#key&lt;/code&gt; value guarantees that two files uploaded with the same name will be stored with unique file names.&lt;/p&gt;

&lt;p&gt;If we change &lt;code&gt;#key&lt;/code&gt; to have different prefix for each deployment then our blobs will be stored in isolated subfolders.&lt;/p&gt;

&lt;p&gt;To feel good about myself as a professional, let's &lt;a href="https://github.com/mocra/subfolder-rails-activestorage-blobs/blob/develop/spec/lib/activestorage_blob_spec.rb"&gt;write a test first&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/lib/activestorage_blob_spec.rb&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"rails_helper"&lt;/span&gt;

&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;ActiveStorage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Blob&lt;/span&gt; &lt;span class="k"&gt;do&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;:blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;ActiveStorage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_and_upload!&lt;/span&gt; &lt;span class="ss"&gt;io: &lt;/span&gt;&lt;span class="no"&gt;StringIO&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="s2"&gt;"This is a test file"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;filename: &lt;/span&gt;&lt;span class="s2"&gt;"test.txt"&lt;/span&gt; &lt;span class="p"&gt;}&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;:key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;key&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"keys have no special prefix by default"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:[]&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"HEROKU_APP_NAME"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# default key looks like "nk2gxeujmuoldqr6o8ng6gov9g5c"&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;%r{&lt;/span&gt;&lt;span class="se"&gt;\w&lt;/span&gt;&lt;span class="sr"&gt;{28}}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"keys have no special prefix by default"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:[]&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"HEROKU_APP_NAME"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"rcrdcp-pr-123"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;twice&lt;/span&gt;
    &lt;span class="c1"&gt;# expect like "rcrdcp-pr-123/nk2gxeujmuoldqr6o8ng6gov9g5c"&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;%r{rcrdcp-pr-123/&lt;/span&gt;&lt;span class="se"&gt;\w&lt;/span&gt;&lt;span class="sr"&gt;{28}}&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;If the application is not running on Heroku, then keep using the default &lt;code&gt;#key&lt;/code&gt; — a 28-character long random string.&lt;/p&gt;

&lt;p&gt;If the app is running on Heroku, then put the app name at the front of the string, e.g. &lt;code&gt;myapp-pr-123/nk2gxeujmuoldqr6o8ng6gov9g5c&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Currently, the &lt;code&gt;#key&lt;/code&gt; value is created indirectly by &lt;code&gt;has_secure_token :key&lt;/code&gt; &lt;a href="https://github.com/rails/rails/blob/b869a4e3a6e06c8d741ebf48faecfed6afb550a0/activerecord/lib/active_record/secure_token.rb#L32"&gt;before the creation&lt;/a&gt; of an &lt;code&gt;ActiveStorage::Blob&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mocra/subfolder-rails-activestorage-blobs/blob/develop/config/initializers/active_storage.rb"&gt;Our solution will be to re-create&lt;/a&gt; this &lt;code&gt;key&lt;/code&gt; value with an additional &lt;code&gt;before_create&lt;/code&gt; call.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_prepare&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;ActiveStorage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class_eval&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;before_create&lt;/span&gt; &lt;span class="ss"&gt;:generate_key_with_prefix&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_key_with_prefix&lt;/span&gt;
      &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;prefix&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;join&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_unique_secure_token&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_unique_secure_token&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prefix&lt;/span&gt;
      &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"HEROKU_APP_NAME"&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;You can use any environment variable that differentiates your deployments.&lt;/p&gt;

&lt;p&gt;On Heroku, to access the &lt;code&gt;$HEROKU_APP_NAME&lt;/code&gt; variable you need to turn on dyno metadata and deploy the app again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;heroku labs:enable runtime-dyno-metadata
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thanks to &lt;a href="https://github.com/arianf"&gt;Arian Faurtosh&lt;/a&gt; for the starting point for this solution &lt;a href="https://github.com/rails/rails/issues/32790#issuecomment-844095704"&gt;https://github.com/rails/rails/issues/32790#issuecomment-844095704&lt;/a&gt;. I found I needed to take a different route due to the behaviour of &lt;code&gt;has_secure_token&lt;/code&gt; which might a recent change.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>activestorage</category>
      <category>heroku</category>
    </item>
    <item>
      <title>Automatically sorting your Tailwind CSS class names</title>
      <dc:creator>Dr Nic Williams</dc:creator>
      <pubDate>Tue, 08 Jun 2021 21:28:38 +0000</pubDate>
      <link>https://dev.to/drnic/automatically-sorting-your-tailwind-css-class-names-4gej</link>
      <guid>https://dev.to/drnic/automatically-sorting-your-tailwind-css-class-names-4gej</guid>
      <description>&lt;p&gt;When Steve Jobs returned to Apple he opted to make one less decision everyday: he always wore the same black top. He was now liberated to make one additional, more important, decision during his day. I've been making tiny decisions every day and I want to be set free: I do not want to decide the order of my Tailwind CSS classes.&lt;/p&gt;

&lt;p&gt;I want tools to do it. Tools that everyone on the team today and in the future will keep using consistently.&lt;/p&gt;

&lt;p&gt;Never again will I need to decide the ordering of CSS classes in my HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-grow-0 justify-between items-center px-2 -mt-7 text-xs text-white uppercase"&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;"flex items-center space-x-2"&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;"flex justify-center items-center w-5 h-5 text-gray-900 rounded-full bg-brand-primary-surface"&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;Well known Tailwind CSS utility classes are sorted first, and bespoke classes come last. What is the ordering of utility classes? Why is &lt;code&gt;flex&lt;/code&gt; before &lt;code&gt;w-5&lt;/code&gt; which is before &lt;code&gt;text-gray-900&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Who cares. You don't either. Let the tools below do their job and everyone's CSS class names will look the same, and look fantastic.&lt;/p&gt;

&lt;p&gt;There are three tools that help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/ryanhhhh/headwind" rel="noopener noreferrer"&gt;Headwind&lt;/a&gt; - a VS Code extension that sorts your CSS classes on save&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/avencera/rustywind" rel="noopener noreferrer"&gt;Rustywind&lt;/a&gt; - a CLI that can update all your files' CSS classes at once&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/sds/overcommit" rel="noopener noreferrer"&gt;Overcommit&lt;/a&gt; - run &lt;code&gt;rustywind --write&lt;/code&gt; during &lt;code&gt;git commit&lt;/code&gt; to update your files before you send them off to git&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Update: &lt;a href="https://dev.to/fractaledmind"&gt;@fractaledmind&lt;/a&gt; fixed an issue where &lt;code&gt;rustywind&lt;/code&gt; is installed via &lt;code&gt;package.json&lt;/code&gt; -- we must wrap it in an &lt;code&gt;npm run rustywind-fix&lt;/code&gt; script.&lt;/p&gt;

&lt;h2&gt;
  
  
  Headwind
&lt;/h2&gt;

&lt;p&gt;I use VS Code and once of its exemplar qualities is the magnificent library of community extensions. VS Code provides the ability for one or more installed extensions to run when you save a file. This is brilliant for automatically formatting your file before it hits the disk. Let's use this hook to format any series of CSS class names in our file.&lt;/p&gt;

&lt;p&gt;Enter, &lt;a href="https://github.com/ryanhhhh/headwind" rel="noopener noreferrer"&gt;Headwind&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=heybourn.headwind" rel="noopener noreferrer"&gt;Install it into VS Code&lt;/a&gt;, go to any of your HTML files, re-save the file and BOOM!! all the series of CSS class names are reordered consistently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rustywind
&lt;/h2&gt;

&lt;p&gt;This is great for you. You installed the VS Code extension. Unfortunately, statically speaking, someone you work with uses Vim. You know who they are because they would have told you.&lt;/p&gt;

&lt;p&gt;So let's take steps towards ensuring everyone who works on your projects is always formatting their CSS class name lists using the same tool.&lt;/p&gt;

&lt;p&gt;Enter, &lt;a href="https://github.com/avencera/rustywind" rel="noopener noreferrer"&gt;Rustywind&lt;/a&gt;. It is a CLI &lt;code&gt;rustywind&lt;/code&gt; that is installed via npm but written in Rust. I didn't know you could do that either.&lt;/p&gt;

&lt;p&gt;From what I read, Rustywind was inspired by Headwind, but now looks to be used by Headwind itself. Very nice.&lt;/p&gt;

&lt;p&gt;You can fix a single file with &lt;code&gt;rustywind&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rustywind path/to/my/template.html --write
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or fix all the files:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;To ensure everyone installs &lt;code&gt;rustywind&lt;/code&gt;, add it to your project's &lt;code&gt;package.json&lt;/code&gt; with &lt;code&gt;npm&lt;/code&gt; or &lt;code&gt;yarn&lt;/code&gt; to taste:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;yarn &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; rustywind

&lt;span class="c"&gt;# or&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;npm i &lt;span class="nt"&gt;--save-dev&lt;/span&gt; rustywind
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everyone will have &lt;code&gt;rustywind&lt;/code&gt; installed into the application when they next run &lt;code&gt;npm i&lt;/code&gt; or &lt;code&gt;yarn&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, create a local npm script in your &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rustywind-fix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rustywind --write ."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any developers of your repository can now access the local &lt;code&gt;rustywind&lt;/code&gt; via:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run rustywind-fix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Overcommit
&lt;/h2&gt;

&lt;p&gt;Finally, let's help everyone run &lt;code&gt;rustywind&lt;/code&gt; all the time. Or, at least, run it before they commit their files to the Git repository.&lt;/p&gt;

&lt;p&gt;If you're familiar with &lt;code&gt;git&lt;/code&gt; hooks, you'll know you could create or edit &lt;code&gt;.git/hooks/pre-commit&lt;/code&gt; and run the &lt;code&gt;rustywind . --write&lt;/code&gt; command. Alas, these git hooks are local to you. Your hooks aren't assured to be the same as everyone else. You're formatting with &lt;code&gt;rustywind&lt;/code&gt; but no one else is.&lt;/p&gt;

&lt;p&gt;Fortunately, you can share git hooks, and make them pretty. How pretty?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7y1cc97rn20rix7wahmc.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%2F7y1cc97rn20rix7wahmc.png" alt="image"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Yes, you want that now, I know.&lt;/p&gt;

&lt;p&gt;Enter, &lt;a href="https://github.com/sds/overcommit" rel="noopener noreferrer"&gt;Overcommit&lt;/a&gt;: a tool to manage and configure Git hooks.&lt;/p&gt;

&lt;p&gt;Create a configuration file for your project that runs Rustywind called &lt;code&gt;.overcommit.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;PreCommit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Rustywind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;run'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rustywind-fix'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that you've added this &lt;code&gt;.overcommit.yml&lt;/code&gt; file to the root of your git repo, we now need everyone to do something for the greater good. They need to install Overcommit and activate it for their local git clone.&lt;/p&gt;

&lt;p&gt;Overcommit is a CLI that is distributed as a RubyGem.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;If your project is a Ruby project with a &lt;code&gt;Gemfile&lt;/code&gt;, then add the gem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ss"&gt;:development&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# A fully configurable and extendable Git hook manager&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"overcommit"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And run &lt;code&gt;bundle install&lt;/code&gt;. I like this best.&lt;/p&gt;

&lt;p&gt;The second manual step for everyone is to activate the Git hooks. Everyone needs to run:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Thanks everyone. You're making the world a better place. Next, net zero carbon emissions.&lt;/p&gt;

&lt;p&gt;Every developer will now see Rustywind during their next commit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git commit -a -m "Doing the things"
Running pre-commit hooks
Run Rustywind.............................................[Rustywind] OK

✓ All pre-commit hooks passed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're a Ruby developer and also want everyone to run the Ruby formatter and linter StandardRB, then extend your &lt;code&gt;.overcommit.yml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;PreCommit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Rustywind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;run'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rustywind-fix'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;StandardRB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bundle'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;exec'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;standardrb'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# Invoke within Bundler context&lt;/span&gt;
    &lt;span class="na"&gt;flags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--fix'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you commit the two commands &lt;code&gt;rustywind&lt;/code&gt; and &lt;code&gt;standardrb --fix&lt;/code&gt; will be run in parallel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git commit -a -m "Doing the things"
Running pre-commit hooks
Run Rustywind.............................................[Rustywind] OK
Run StandardRB...........................................[StandardRB] OK

✓ All pre-commit hooks passed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Freedom by abdication
&lt;/h2&gt;

&lt;p&gt;"But I don't agree with the order that Rustywind sorts the classes!"&lt;/p&gt;

&lt;p&gt;Whether its a syntax formatter for your programming language whitespace or a tiny tool that has a preference for CSS class orders, just pick one and let it care more than you do. It will consistently do its thing over and over.&lt;/p&gt;

&lt;p&gt;You really don't care that much about the order of the classes to not use a free, instant, shareable, pretty, and consistent tool do you?&lt;/p&gt;

</description>
      <category>tailwindcss</category>
      <category>linting</category>
      <category>vscode</category>
      <category>githooks</category>
    </item>
  </channel>
</rss>
