<?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: Kyle Keesling</title>
    <description>The latest articles on DEV Community by Kyle Keesling (@kylekeesling).</description>
    <link>https://dev.to/kylekeesling</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%2F138311%2F1e09743f-911f-4064-9e39-3fa26fa2a8bb.png</url>
      <title>DEV Community: Kyle Keesling</title>
      <link>https://dev.to/kylekeesling</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kylekeesling"/>
    <language>en</language>
    <item>
      <title>Migrating to jsbundling-rails</title>
      <dc:creator>Kyle Keesling</dc:creator>
      <pubDate>Sun, 23 Jan 2022 16:00:00 +0000</pubDate>
      <link>https://dev.to/kylekeesling/migrating-to-jsbundling-rails-56jg</link>
      <guid>https://dev.to/kylekeesling/migrating-to-jsbundling-rails-56jg</guid>
      <description>&lt;p&gt;Now that &lt;a href="https://github.com/rails/webpacker/commit/16bba5d6ca862b950a002f19c10d12e4bf51a87b#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5"&gt;Webpacker is riding off into the sunset&lt;/a&gt;, many of us find ourselves switching over to using one of the solutions offered by &lt;a href="https://github.com/rails/jsbundling-rails"&gt;jsbuilding-rails&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With the move comes a much simplier javascript story, which is a welcome change for many of us, but making the move can quickly lead to a few gotchas. Below I’ll outline the few that I’ve run into and how you can avoid them so you can ensure a smooth transition to using esbuild.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha #1 - Importing CSS in your Javascript
&lt;/h2&gt;

&lt;p&gt;This has been one of the most common problems I’ve noticed folks having. Say you’re using a javascript package that also includes CSS assets, esbuild does allow you to import those, but will name the output file the same name as the root javascript file in &lt;code&gt;app/javascript&lt;/code&gt; which in most cases will be &lt;code&gt;application.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The result of that being two files output to &lt;code&gt;app/assets/build&lt;/code&gt; , &lt;code&gt;application.js&lt;/code&gt; and &lt;code&gt;application.css&lt;/code&gt; - and that’s where the hang-up occurs. If you are also using &lt;a href="https://github.com/rails/cssbundling-rails"&gt;cssbundling-rails&lt;/a&gt; it will by default name it’s output the same thing - &lt;code&gt;application.css&lt;/code&gt;, causing a naming collision that will inevitably cause you to bang your head against the wall if you aren’t sure what’s going on.&lt;/p&gt;

&lt;p&gt;There are two solutions here, one is renaming the output of your &lt;code&gt;cssbundling-rails&lt;/code&gt; file, or renaming your &lt;code&gt;app/javascript/application.js&lt;/code&gt; to something else, which in my case is what I did, making it &lt;code&gt;application-esbuild.js&lt;/code&gt;. Now, esbuild will process your javascript and CSS and place two files named &lt;code&gt;application-esbuild.js&lt;/code&gt; and &lt;code&gt;application-esbuild.css&lt;/code&gt; into &lt;code&gt;app/assets/build&lt;/code&gt;. Then all you need to do is make sure you include those files in the head of your &lt;code&gt;application.html.erb&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;stylesheet_link_tag&lt;/span&gt; &lt;span class="s2"&gt;"application"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;media: :all&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;turbo_track: &lt;/span&gt;&lt;span class="s2"&gt;"reload"&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;stylesheet_link_tag&lt;/span&gt; &lt;span class="s2"&gt;"application-esbuild"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;media: :all&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;turbo_track: &lt;/span&gt;&lt;span class="s2"&gt;"reload"&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;javascript_include_tag&lt;/span&gt; &lt;span class="s2"&gt;"application-esbuild"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;turbo_track: &lt;/span&gt;&lt;span class="s2"&gt;"reload"&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;
  
  
  Gotcha #2 - Having Two Javascript Directories
&lt;/h2&gt;

&lt;p&gt;If you’re not starting fresh in Rails 7 there’s good chance you’ve got files in &lt;code&gt;app/assets/javascript&lt;/code&gt;. If so you’ll also want to make sure you’ve got sprockets setup to serve those assets. This can be done in &lt;code&gt;app/assets/config/manifest.js&lt;/code&gt;, just make sure you’ve got the following line in the config:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c1"&gt;//= link_tree ../images&lt;/span&gt;
&lt;span class="c1"&gt;//= link_tree ../fonts&lt;/span&gt;
&lt;span class="c1"&gt;//= link_tree ../builds&lt;/span&gt;

&lt;span class="c1"&gt;// this is the key addition, referring to the file app/assets/javascript/application.js&lt;/span&gt;
&lt;span class="c1"&gt;//= link application.js&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Of course you could also use &lt;code&gt;link_tree&lt;/code&gt; or any of the other methods that sprockets provides in order to properly include the necessary javascript files in &lt;code&gt;app/assets/javascript/&lt;/code&gt;, but I think explicitly including just one file helps keep things clean and easy to understand.&lt;/p&gt;

&lt;p&gt;Also note that doing this also assumes that you’ve renamed your esbuild output file to &lt;code&gt;application-esbuild.js&lt;/code&gt;as mentioned above, otherwise you’d run into similar naming collision issues that Gotcha #1 covers.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>esbuild</category>
      <category>webpacker</category>
    </item>
    <item>
      <title>Setting Up Proper Amazon S3 Permissions for ActiveStorage</title>
      <dc:creator>Kyle Keesling</dc:creator>
      <pubDate>Tue, 28 Jan 2020 17:43:00 +0000</pubDate>
      <link>https://dev.to/kylekeesling/setting-up-proper-amazon-s3-permissions-for-activestorage-3gff</link>
      <guid>https://dev.to/kylekeesling/setting-up-proper-amazon-s3-permissions-for-activestorage-3gff</guid>
      <description>&lt;p&gt;If you’ve found yourself marveling at how cryptic and impenetrable understanding AWS services, then you are definitely not alone. For much too long have I relied on doing a quick web search and blindly copying and pasting settings and policies, so when I found myself doing it again this week for &lt;a href="https://passtesting.com/tools/service-providers"&gt;one of my applications&lt;/a&gt; I decided it was time to slow down and actually understand what it was that I was doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;I’m creating a demo environment that needs to be completely separate from our production data, so part of that entails creating its own S3 bucket for use with &lt;a href="https://edgeguides.rubyonrails.org/active_storage_overview.html"&gt;ActiveStorage&lt;/a&gt;. Having not really done this since ActiveStorage was first released I begin DuckDuckGoing (is this even a thing?) for a how-to.&lt;/p&gt;

&lt;p&gt;Some perfectly fine write-ups and tutorials popped up on the first page, but I noticed that all of them recommended setting up an &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users.html"&gt;IAM User&lt;/a&gt; with &lt;code&gt;AmazonS3FullAccess&lt;/code&gt; permissions. As soon as I looked at the description for this policy it threw up a &lt;em&gt;big&lt;/em&gt; red flag:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;AmazonS3FullAccess: Provides full access to all buckets via the AWS Management Console&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Full access to all buckets? That’s gonna be a big nope from me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;At an absolute minimum the permissions need to be locked down to a single bucket, but ideally we’d only give the app the ability to perform solely the actions that are required for the framework to function properly. Luckily there’s &lt;a href="https://edgeguides.rubyonrails.org/active_storage_overview.html#amazon-s3-service"&gt;a callout right in the Rails guide&lt;/a&gt; that states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The core features of Active Storage require the following permissions: &lt;code&gt;s3:ListBucket&lt;/code&gt;, &lt;code&gt;s3:PutObject&lt;/code&gt;, &lt;code&gt;s3:GetObject&lt;/code&gt;, and &lt;code&gt;s3:DeleteObject&lt;/code&gt;. If you have additional upload options configured such as setting ACLs then additional permissions may be required.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Assuming you already have a bucket created, we just need to &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html"&gt;create an IAM User&lt;/a&gt; so we can generate the &lt;code&gt;access_key_id&lt;/code&gt; and &lt;code&gt;secret_access_key&lt;/code&gt; necessary to configure the &lt;code&gt;config/storage.yml&lt;/code&gt; Amazon service.&lt;/p&gt;

&lt;p&gt;Once you have your IAM user created you can use the policy template I’ve built down below as a guide to either create and attach the policy to a group you add your IAM user to, or just attach the policy directly to the user, which is what I did.&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VisualEditor0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&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="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"s3:DeleteObject"&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;"Resource"&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="s2"&gt;"arn:aws:s3:::BUCKET_NAME_GOES_HERE/*"&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="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;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VisualEditor1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:ListBucket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::BUCKET_NAME_GOES_HERE"&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final Notes
&lt;/h2&gt;

&lt;p&gt;To their credit, one or two articles said something to the extent of “you may want to do this differently in production”, but let’s be honest, most people don’t read the entire article and/or will not remember to do this, so I find it to be a much better idea to set things up right from the start.&lt;/p&gt;

&lt;p&gt;Hopefully this helps you in your ActiveStorage-related endeavors, and if you have any suggestions or comments on how you handle S3 permissions, I’d love to hear them!&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>activestorage</category>
      <category>s3</category>
    </item>
    <item>
      <title>Piping Your Honeybadger Events to Basecamp with Zapier</title>
      <dc:creator>Kyle Keesling</dc:creator>
      <pubDate>Wed, 03 Jul 2019 13:30:00 +0000</pubDate>
      <link>https://dev.to/kylekeesling/piping-your-honeybadger-events-to-basecamp-with-zapier-20d1</link>
      <guid>https://dev.to/kylekeesling/piping-your-honeybadger-events-to-basecamp-with-zapier-20d1</guid>
      <description>&lt;p&gt;My team has been working on consolidating and improving our communication tools and practices, and as part of that we’ve decided to ween ourselves off of &lt;a href="http://slack.com"&gt;Slack&lt;/a&gt; and totally leverage &lt;a href="http://basecamp.com"&gt;Basecamp&lt;/a&gt; for all internal and customer communications.&lt;/p&gt;

&lt;p&gt;One of the major things that the team appriciated in Slack was the #dev channel I had created that piped all of our deploys, pull requests, and errors into one central location, allowing everyone to know what’s going on without having to tap someone on the shoulder.&lt;/p&gt;

&lt;p&gt;Basecamp provides an easy way, using a &lt;a href="https://github.com/basecamp/bc3-api/blob/master/sections/chatbots.md"&gt;chatbot&lt;/a&gt;, to feed Github events into a Campfire chat, but getting our application errors in wasn’t quite as straightforward.&lt;/p&gt;

&lt;p&gt;We use &lt;a href="https://www.honeybadger.io/"&gt;Honeybadger&lt;/a&gt; to track our errors, and while they &lt;a href="https://docs.honeybadger.io/guides/integrations.html"&gt;offer many integrations&lt;/a&gt;, Basecamp isn’t one of them — enter &lt;a href="https://zapier.com"&gt;Zapier&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How’d I do it?
&lt;/h2&gt;

&lt;p&gt;Zapier made it a very simple process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;create a &lt;a href="https://zapier.com/apps/webhook/integrations"&gt;catch webhook&lt;/a&gt; to catch &lt;a href="https://docs.honeybadger.io/guides/services.html#1-select-the-webhook-integration"&gt;incoming webhooks from Honeybadger&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;create a POST webhook action that massages the JSON payload from Honeybadger, into &lt;a href="https://github.com/basecamp/bc3-api/blob/master/sections/chatbots.md#create-a-line"&gt;the format Basecamp expects for a chatbot&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Result in Basecamp
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Jj_3d2l_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://kylekeesling.dev/images/2019/07/honeybadger-chatbot1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Jj_3d2l_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://kylekeesling.dev/images/2019/07/honeybadger-chatbot1.png" alt="Honeybadger Chatbot Example" width="880" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using the &lt;code&gt;details&lt;/code&gt; and &lt;code&gt;summary&lt;/code&gt; elements gives you the ability in Basecamp to create a collapsable post. In the sample above notice the little black right arrow, if you click it you can even get a little sample of the stacktrace. Here’s the value of the &lt;code&gt;contents&lt;/code&gt; key I used for the chatbot payload:&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;details&amp;gt;
      &amp;lt;summary&amp;gt;
        &amp;lt;strong&amp;gt;💥({xxx __fault__ id}){xxx__message}&amp;lt;/strong&amp;gt;&amp;lt;br&amp;gt;
        &amp;lt;a href="{xxx __fault__ url}"&amp;gt;View in Honeybadger&amp;lt;/a&amp;gt;
      &amp;lt;/summary&amp;gt;
      &amp;lt;br&amp;gt;&amp;lt;hr&amp;gt;&amp;lt;br&amp;gt;
      &amp;lt;pre&amp;gt;{xxx __notice__ application_trace}&amp;lt;/pre&amp;gt;
    &amp;lt;/details&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here’s a preview of what the expanded preview looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eNeXdefS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://kylekeesling.dev/images/2019/07/honeybadger-chatbot2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eNeXdefS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://kylekeesling.dev/images/2019/07/honeybadger-chatbot2.png" alt="Honeybadger Chatbot Example w/ Stacktrace" width="880" height="723"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;We are still in the process of fulling moving over to Basecamp, but so far I’ve not missed Slack nearly as much as I thought I would.&lt;/p&gt;

&lt;p&gt;In all honestly the things we’ve missed the most from Slack are the GIPHY integration, and the quick access to screen collaboration tools, so if you have any suggestions on how your team addresses those areas, &lt;a href="https://twitter.com/kylekeesling"&gt;I’d love to hear about it&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>honeybadger</category>
      <category>rails</category>
      <category>zapier</category>
      <category>notifications</category>
    </item>
    <item>
      <title>An Easier Way to Accommodate Deletion of ActiveStorage Attachments</title>
      <dc:creator>Kyle Keesling</dc:creator>
      <pubDate>Mon, 03 Dec 2018 22:45:00 +0000</pubDate>
      <link>https://dev.to/kylekeesling/an-easier-way-to-accommodate-deletion-of-activestorage-attachments-2ndn</link>
      <guid>https://dev.to/kylekeesling/an-easier-way-to-accommodate-deletion-of-activestorage-attachments-2ndn</guid>
      <description>&lt;h2&gt;
  
  
  A Little Background
&lt;/h2&gt;

&lt;p&gt;As I continue to phase out &lt;a href="https://github.com/thoughtbot/paperclip"&gt;Paperclip&lt;/a&gt; in favor of &lt;a href="https://edgeguides.rubyonrails.org/active_storage_overview.html"&gt;ActiveStorage&lt;/a&gt;, I’ve wanted to keep the methods I used to manage these assets as succinct and reusable as possible.&lt;/p&gt;

&lt;p&gt;ActiveStorage gives us a dead simple way to save and update assets, but the ability to delete assets independently of the parent record, particularly if you’re using &lt;code&gt;has_many_attached&lt;/code&gt;, has been left to each individual app to figure out.&lt;/p&gt;

&lt;p&gt;I have stumbled upon a couple of different takes on how to potentially do this, but I’ve not been satisfied with their approaches. Many do not take into account authorization and permissions, which if you’re not careful, allows any user to delete any attachment they choose just by randomly hitting URLs in your application. With that in mind I set out to try and roll my own.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Approach
&lt;/h2&gt;

&lt;p&gt;Since all attachments are saved as the same object/models types, &lt;a href="https://api.rubyonrails.org/classes/ActiveStorage/Attachment.html"&gt;Attachment&lt;/a&gt; and &lt;a href="https://api.rubyonrails.org/classes/ActiveStorage/Blob.html"&gt;Blob&lt;/a&gt;, we can use a common controller to interact and modify with them, regardless of the parent record that it belongs to.&lt;/p&gt;

&lt;p&gt;In my case I chose to stick closely to the naming conventions already given to us, so I created a new controller named &lt;code&gt;ActiveStorage::AttachmentsController&lt;/code&gt;, and since for now I’m only worried about deleting attachments, we only need one method, &lt;code&gt;delete&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I use &lt;a href="https://github.com/plataformatec/devise"&gt;Devise&lt;/a&gt; for authentication, so we need to make sure that the user is logged in before we let them do anything, which we do with &lt;code&gt;:authenticate_user!&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I also want to make sure that the user has the permissions to modify these attachments, so I check their permissions to see if they can modify the parent record using the &lt;code&gt;authorize_attachment_parent!&lt;/code&gt; method. I use &lt;a href="https://github.com/CanCanCommunity/cancancan"&gt;CanCanCan&lt;/a&gt; in this case, but you can always swap out your auth call to fit whatever method you use. This piece is _ &lt;strong&gt;critical&lt;/strong&gt; _ to ensure that your users aren’t deleting anything they shouldn’t.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;purge_later&lt;/code&gt; call will take care of the actual file deletion in your background queue, and will delete the corresponding &lt;code&gt;Blob&lt;/code&gt; record.&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;# app/controllers/active_storage/attachments_controller.rb&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ActiveStorage::AttachmentsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
      &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:authenticate_user!&lt;/span&gt;
      &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:set_attachment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:authorize_attachment_parent!&lt;/span&gt;

      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;destroy&lt;/span&gt;
        &lt;span class="vi"&gt;@attachment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;purge_later&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;set_attachment&lt;/span&gt;
          &lt;span class="vi"&gt;@attachment&lt;/span&gt; &lt;span class="o"&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;Attachment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
          &lt;span class="vi"&gt;@record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@attachment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&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;authorize_attachment_parent!&lt;/span&gt;
          &lt;span class="n"&gt;authorize!&lt;/span&gt; &lt;span class="ss"&gt;:manage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@record&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;Also be sure to wire this new controller up in your routes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/routes.rb&lt;/span&gt;
    &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="ss"&gt;:active_storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;module: :active_storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;as: :active_storage&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="ss"&gt;:attachments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:destroy&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;It’s also important to update the user interface to remove any reference to the attachment, and in this case I used Rails UJS. The javascript necessary to remove the element from my UI is simple and straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/views/active_storage/attachments/destroy.js.erb&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;%= dom_id @attachment %&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only assumptions made here are that you had the representation of your attachment wrapped in a div with ID of &lt;code&gt;attachment_#{id}&lt;/code&gt;, which you can easily render using Rails handy &lt;code&gt;dom_id&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;You can now use the following markup next all of your attachments to allow for easy deletion:&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;link_to&lt;/span&gt; &lt;span class="s2"&gt;"Delete"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;active_storage_attachment_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attachment&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
             &lt;span class="ss"&gt;method: :delete&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;remote: :true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;confirm: &lt;/span&gt;&lt;span class="s2"&gt;"Are you sure you wanna this?"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;This approach is relatively simple, and relies heavily on the tools and features that Rails provides to us.&lt;/p&gt;

&lt;p&gt;In my case I’m also leveraging a common partial to render thumbnails, metadata, and links for attachments, making it dead simple to add attachments to any model I choose to in the future.&lt;/p&gt;

&lt;p&gt;As always things evolve over time, and while I’d like ot think I’m perfect I’m sure there are ways to improve this code, so if you have any suggestions or improvements &lt;a href="https://twitter.com/kylekeesling"&gt;I’d love to hear them&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>activestorage</category>
    </item>
    <item>
      <title>Migrating Your Assets from Paperclip to ActiveStorage</title>
      <dc:creator>Kyle Keesling</dc:creator>
      <pubDate>Thu, 26 Apr 2018 08:00:00 +0000</pubDate>
      <link>https://dev.to/kylekeesling/migrating-your-assets-from-paperclip-to-activestorage-3n5d</link>
      <guid>https://dev.to/kylekeesling/migrating-your-assets-from-paperclip-to-activestorage-3n5d</guid>
      <description>&lt;p&gt;With the release of Rails 5.2 there is now a native, built in way to handle asset uploads and management called &lt;a href="http://edgeguides.rubyonrails.org/active_storage_overview.html"&gt;ActiveStorage&lt;/a&gt;, making the need to use gems like &lt;a href="https://github.com/thoughtbot/paperclip/"&gt;Paperclip&lt;/a&gt;, &lt;a href="https://github.com/carrierwaveuploader/carrierwave"&gt;Carrierwave&lt;/a&gt;, or &lt;a href="https://github.com/fog/fog"&gt;Fog&lt;/a&gt;, with Paperclip going so far as writing a &lt;a href="https://github.com/thoughtbot/paperclip/pull/2568"&gt;migration guide&lt;/a&gt;; implying that it may not be long for this world.&lt;/p&gt;

&lt;p&gt;To be honest I didn’t even know that Paperclip had written a &lt;a href="https://github.com/thoughtbot/paperclip/blob/master/MIGRATING.md"&gt;migration guide&lt;/a&gt; until I started writing this post, and while it’s fairly comprehensive, it doesn’t do what in my mind is one of the most important tasks - migrating the files on the remote host.&lt;/p&gt;

&lt;p&gt;With that in mind I’ve used the following rake tasks with great success to move assets for many of my models.&lt;/p&gt;

&lt;p&gt;There are two flavors here, migrating assets while changing their name, or just plain old moving.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving an Asset While Changing Its Name
&lt;/h2&gt;

&lt;p&gt;In my case I had a &lt;code&gt;User&lt;/code&gt; class with a Paperclip attachment called &lt;code&gt;headshot&lt;/code&gt;. I never really liked that we used the term headshot, so this was the perfect opportunity to change it to &lt;code&gt;avatar&lt;/code&gt;.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This scenario is surprisingly easier due and the fact that we don’t have to worry about a naming collision (&lt;code&gt;headshot&lt;/code&gt; to &lt;code&gt;avatar&lt;/code&gt;), which means we can still use paperclip’s built in URL helpers in our rake task.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Just remember to leave your Paperclip declarations on your model until after you run your rake task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving an Asset As-is
&lt;/h2&gt;

&lt;p&gt;In reality, you’ll likely want to keep the same name, which requires us to get a little more crafty. In my case I have an &lt;code&gt;Organization&lt;/code&gt; class with a &lt;code&gt;logo&lt;/code&gt;. We wanted to keep using that term, but in order to do so, we have to remove the Paperclip declarations from the model, otherwise would couldn’t refer to all the new ActiveStorage goodness.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;To get around this we just need to utilize the actual asset URL in our rake task.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This rake task is pretty straightforward, but what got a little sticky was dealing with filename extensions, mainly the fact that sometimes uploaded files don’t have them, so that’s what lines 5 &amp;amp; 6 are about.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Few Considerations
&lt;/h2&gt;

&lt;p&gt;There are a few tradeoffs that you’ll have to consider if you use this method though.&lt;/p&gt;

&lt;p&gt;The first advantage that you get is that these tasks can be ran from any server, including your dev environment, even before you deploy any actual code.&lt;/p&gt;

&lt;p&gt;In my case I ran the rake tasks on my laptop, and as soon as the updated code was deployed to the server, the assets were already there. The only thing left to do is to remove the old assets when you’re ready, but they are still there in the event you need to rollback your deploy.&lt;/p&gt;

&lt;p&gt;The biggest downside to this method is simply the fact that you are duplicating all of your assets temporarily. In my app I was able to move thousands of image files in a matter of hours, but if you have millions of records, or very large assets, this method might not be your best option.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>activestorage</category>
      <category>paperclip</category>
    </item>
    <item>
      <title>Bootstrap 3 Responsive Button Groups</title>
      <dc:creator>Kyle Keesling</dc:creator>
      <pubDate>Wed, 12 Aug 2015 17:28:01 +0000</pubDate>
      <link>https://dev.to/kylekeesling/bootstrap-3-responsive-button-groups-2nme</link>
      <guid>https://dev.to/kylekeesling/bootstrap-3-responsive-button-groups-2nme</guid>
      <description>&lt;p&gt;Ever been disappointed/pissed off with how Bootstrap handles (read ignores) button formatting on smaller screens? Check this out:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Makes this: &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GjXGePz5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://kylekeesling.dev/images/2015/08/bs-btn-before.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GjXGePz5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://kylekeesling.dev/images/2015/08/bs-btn-before.jpg" alt="" title="Before :("&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look like this: &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mQE_NBIG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://kylekeesling.dev/images/2015/08/bs-btn-after.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mQE_NBIG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://kylekeesling.dev/images/2015/08/bs-btn-after.jpg" alt="" title="After!"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://kylekeesling.dev/posts/2015/08/bootstrap-responsive-btn-group"&gt;Permalink ∞&lt;/a&gt;&lt;/p&gt;

</description>
      <category>css</category>
      <category>bootstrap</category>
      <category>bootstrap3</category>
    </item>
  </channel>
</rss>
