<?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: Craig @ localhost</title>
    <description>The latest articles on DEV Community by Craig @ localhost (@jalisocsp).</description>
    <link>https://dev.to/jalisocsp</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%2F402500%2F9eef6f64-a40f-4422-9b06-985e1db5625c.jpg</url>
      <title>DEV Community: Craig @ localhost</title>
      <link>https://dev.to/jalisocsp</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jalisocsp"/>
    <language>en</language>
    <item>
      <title>Profile Images With Active Storage</title>
      <dc:creator>Craig @ localhost</dc:creator>
      <pubDate>Fri, 05 Jun 2020 09:58:54 +0000</pubDate>
      <link>https://dev.to/jalisocsp/profile-images-with-active-storage-1n46</link>
      <guid>https://dev.to/jalisocsp/profile-images-with-active-storage-1n46</guid>
      <description>&lt;p&gt;I was surprised how simple it was to implement Active Storage just following &lt;a href="https://edgeguides.rubyonrails.org/active_storage_overview.html"&gt;the guide&lt;/a&gt; from Rails Guides. This is how I added profile images to &lt;a href="https://speakerslive.tech"&gt;Speakers Live&lt;/a&gt; in 6 easy steps!&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Run the installation task and migrations
&lt;/h3&gt;

&lt;p&gt;This will give us the correct schema we need for Rails to store our uploads.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bin/rails active_storage:install
bin/rails db:migrate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Add &lt;code&gt;image&lt;/code&gt; to our controller's whitelisted params
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def user_params
  params.fetch(:user, {}).permit(:email, :image, [...])
end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Update the model
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;has_one_attached :image
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Add &lt;code&gt;form.file_field :image&lt;/code&gt; to our User form
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="w-full md:w-1/2 px-3 mb-6 md:mb-0"&amp;gt;
  &amp;lt;%= f.label :image, class: "form-label" %&amp;gt;
  &amp;lt;%= f.file_field :image, class: "form-field" %&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Update the views to use the dynamic images
&lt;/h3&gt;

&lt;p&gt;If a user doesn't have an image, then a broken image will be displayed if we just use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;%= image_tag user.image,
  loading: "lazy", alt: user.name, class: "rounded-full" %&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You'll need to separately handle a default profile image using the &lt;code&gt;attached?&lt;/code&gt; option. On user, we use a method to decide which image to show (this presentational logic is a perfect candidate for some sort of presenter)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def displayed_image
  if image.attached?
    image
  else
    "path/to/default/image.png"
  end
end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Doing it this way allows us to keep using &lt;code&gt;image_tag&lt;/code&gt; without having to make a shared partial and worrying about different styles in different places:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;%= image_tag user.displayed_image,
  loading: "lazy", alt: user.name, class: "rounded-full" %&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Custom Validations
&lt;/h3&gt;

&lt;p&gt;The Rails Guide doesn't show how to do this clearly, but we need to add custom validations to our image uploads.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.a. Only allow our desired file types
&lt;/h3&gt;

&lt;p&gt;We're only dealing with images, but Active Storage allows uploads of all file times - anything you can find on your computer; PDF, EXE, MP4.&lt;/p&gt;

&lt;p&gt;We can prevent other types of uploads by checking the &lt;code&gt;image.content_type&lt;/code&gt; matches a type from an allowed list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;validate :acceptable_image_type?

def acceptable_image_type?
  return unless image.attached?
  return if image.content_type.in? ["image/png", "image/jpeg"]
  errors.add :image, "must be a PNG or JPG"
end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  6.b. Limit uploaded file sizes
&lt;/h3&gt;

&lt;p&gt;Lastly, we don't want them to upload HUGE images. Twitter stores your profile image at less than 30KB. Incredibly small and efficient on page loads.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;validate :acceptable_image_size?

def acceptable_image_size?
  return unless image.attached?
  return unless image.byte_size &amp;gt; 1.megabyte
  errors.add :image, "is over 1MB"
end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  That's it!
&lt;/h2&gt;

&lt;p&gt;Now you have image uploads in a Rails application.&lt;/p&gt;

&lt;p&gt;Some more improvements could be made though:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In our custom validations, we're checking if the file is attached multiple times (once in each validation).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This implementation is using the default local storage, on your server. The guide shows examples of setting up externally with AWS and Google Cloud. Remember: if you're storing images locally, you will need to add the storage folder to your deploy symlink otherwise they will stay with the release and be lost on your next deploy.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  set :linked_dirs, %w{storage}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Image pre-processing - for our application, we know we never want profile images bigger than 150x150. Rather than storing the full image, we could process those images when they are upload to be our desired size. This will improve load times, storage space and allow us to handle initially larger images.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Originally posted: &lt;a href="https://craigpetterson.co.uk/article/2020/06/01/profile-images-with-active-storage.html"&gt;https://craigpetterson.co.uk/article/2020/06/01/profile-images-with-active-storage.html&lt;/a&gt;&lt;/p&gt;

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