With introduction of ActiveStorage in Rails 5.2 it has become easier to attach files to ActiveRecord models in Rails. So much so that Paperclip has decided to cease support for their gem and encouraging users to migrate to ActiveStorage for more streamlined solution.

While ActiveStorgae provides straightforward ways to attach files to models using has_one_attached and has_many_attached, the official guides, stackoverflow and online tutorials don’t have an example of a simple uploader to only upload files to a service or local storage in case of development. An example could be a WYSIWYG editor file uploader.

A simple file uploader can be written using ActiveStorage::Blob and url_for. Here we will see an example to upload multiple images using a simple uploader with ActiveStorage.

First we might define route to our controller like:

  # config/routes.rb

  post 'uploader/image', to: 'uploader#image'

In our controller:

  
  # app/controllers/uploader_controller.rb

  class UploaderController < ApplicationController
    def index; end

    def image
      images_uploader = ImagesUploader.new(files)
      if images_uploader.upload
        flash[:success] = 'Image successfully uploaded.'
      else
        flash[:warning] = 'Image could not be uploaded. Try uploading .png or .jpg'
      end
      redirect_back(fallback_location: root_path)
    end

    private

    def files
      @files ||= params[:files]
    end
  end

We try to avoid putting too much logic in controller. So, we put the ImageUploader in a namespaced directory inside the controllers dir.

  # app/controllers/uploader_controller/images_uploader.rb

  require 'file_upload_service'

  class UploaderController
    # Class to upload images.
    class ImagesUploader
      attr_reader :files

      def initialize(files)
        @files = files
      end

      def upload
        return false if invalid_file_types
        files.each do |file|
          FileUploadService.upload(file)
        end
      end

      private

      def invalid_file_types
        (files.map(&:content_type) & %w[image/png image/jpeg]).empty?
      end
    end
  end

We place the file upload logic sperate in lib dir. So, that now it is easier if we want to create another uploader for .pdf file type or testing.

  
  # /lib
  # Used to upload file using ActiveStorage
  class FileUploadService
    def self.upload(file)
      ActiveStorage::Blob.create_after_upload!(
        io: file,
        filename: file.original_filename,
        content_type: file.content_type
      )
    end
  end

Create a simple form with file upload fields taking multiple files at once:

  
  # app/views/uploader/index.html.erb

  <div class="row">
    <div class="col-lg-6 mx-auto m-5">
      <h3>Upload Images</h3>
      <%= form_tag uploader_image_path, method: 'post', multipart: true do %>
        <%= file_field_tag :files, name: 'files[]', multiple: true, class: 'btn btn-primary', required: true %>
        <%= submit_tag 'Upload', class: 'btn btn-primary' %>
      <% end %>
      <small><em>only .png and .jpg</em></small>
    </div>
  </div>

And just like that we have a file uploader we can use to upload files straight to our preferred service. Write to me at [email protected] with questions or leave a comment. Cheers!