How to use Active Storage to Upload Files to S3 on Rails

Using Active Storage to upload files to Amazon S3 service in Rails application
Luan Nguyen
Apr 07, 2020 · 4 min read

In this article, I'm going to share with you how to upload files to Amazon S3 service in Rails application throughout Active Storage.

What'll we do:

  • Introduction about Active Storage.
  • Create a new Rails application and CRUD for Post.
  • Setup Active Storage + Amazon S3 Service
  • Using Active Storage to: 
    • Upload Images for Post
    • Upload a File (PDF or Video) for Post
  • Conclusion + Example code on Github

Introduce Active Storage

Active Storage is a great feature in Rails 5.2. It’s designed to upload files to a cloud storage service like Amazon S3, Google Cloud Storage, or Microsoft Azure Storage and attaching those files to Active Record objects.
Active Storage comes with a local disk-based service for development and testing.

Using Active Storage, Rails application can transform image uploads with ImageMagick, generate image representations of non-image uploads like PDFs and videos, and extract metadata from arbitrary files.

Create a new Rails application

Make sure using Rails version >= 5.2
rails new active-storage-in-rails5.2 --version=5.2.3

After creating the Rails application, switch to its folder
cd active-storage-in-rails5.2

Install Gems for our application:
bundle install

Starting up the Web Server:
rails server

CRUD Post by scaffold 
A scaffold in Rails is a full set of model, database migration for that model, controller to manipulate it, views to view and manipulate the data, and a test suite for each of the above.
rails generate scaffold Post title:string content:text

and do not forget running migration to create the Post table:
rails db:migrate


Setup Active Storage

Run command:
rails active_storage:install

It’ll create a migration file like this: db/migrate/YYYYYYYYY_create_active_storage_tables.active_storage.rb
class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
  def change
    create_table :active_storage_blobs do |t|
      t.string   :key,        null: false
      t.string   :filename,   null: false
      t.string   :content_type
      t.text     :metadata
      t.bigint   :byte_size,  null: false
      t.string   :checksum,   null: false
      t.datetime :created_at, null: false

      t.index [ :key ], unique: true
    end

    create_table :active_storage_attachments do |t|
      t.string     :name,     null: false
      t.references :record,   null: false, polymorphic: true, index: false
      t.references :blob,     null: false

      t.datetime :created_at, null: false

      t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
      t.foreign_key :active_storage_blobs, column: :blob_id
    end
  end
end

The migration will create 2 tables are: 
  • active_storage_blobs
  • active_storage_attachments 
which Active Storage uses to store files. And then, execute the migration:
rails db:migrate
== 20200404135729 CreateActiveStorageTables: migrating =======
-- create_table(:active_storage_blobs)
   -> 0.0055s
-- create_table(:active_storage_attachments)
   -> 0.0030s
== 20200404135729 CreateActiveStorageTables: migrated (0.0089s)

Config Services where store files:
Active Storage declares services in config/storage.yml. Open this file, you can see the default config like below:
test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>
local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

This configuration that means our application declare two services named test and local. Both of these services use Disk service.

Config Amazon S3 Service
We add a new service with named amazon to config/storage.yml :
amazon:
 service: S3
 access_key_id: your_access_key_id
 secret_access_key: your_secret_access_key
 region: your_region
 bucket: your_own_bucket

Add the aws-sdk-s3 gem to your Gemfile:
gem "aws-sdk-s3", require: false

Each environment often uses a different service.
In the development environment, we can use the Disk service by adding the config code to config/environments/development.rb:
# Store uploaded files on the local file system
config.active_storage.service = :local

To use the Amazon S3 service in production, you can add the following code to config/environments/production.rb:
# Store uploaded files on Amazon S3
config.active_storage.service = :amazon

Don’t forget to restart the server when updating the configuration in each environment.
____________________

Using Active Storage to upload Images for Post

With each post to have many images, you define Post model like this:
class Post < ApplicationRecord
  has_many_attached :images
end

The has_many_attached macro sets up a one-to-many relationship between records and files. Each record can have many files attached to it.

In Form upload images:
<%= form.file_field :images, multiple: true %>

In Post Controller
Update post_params to create Post with images
class PostsController < ApplicationController
  [...]

  private
  def post_params
    params.require(:post).permit(:title, :content, images: [])
  end
end

Show images of Post after uploading:
<% @post.images.each do |image| %>
  <%= image_tag image %>
<% end %>

Transforming Images
We can create a variation of the image, call variant on the Blob.
You can pass any transformation to the method supported by the processor (default is MiniMagick).
Add the image_processing gem to your Gemfile
gem 'image_processing', '~> 1.2'

Using a variant of an image in View:
<% @post.images.each do |image| %>
  <%= image_tag image.variant(resize: "300x300")%>
<% end %>

You can change the transformation by each variant, Active Storage will transform the original image into the specified format (transformation).
Type of transformation you can view more in image_processing gem.
____________________

Upload a File (PDF or Video) for Post

Each post has an attached file, you define Post model like this:
class Post < ApplicationRecord
  has_one_attached :file
end

The has_one_attached macro sets up a one-to-one mapping between records and files. Each record can have one file attached to it.

Using in Form upload a file like this:
<%= form.file_field :file %>

Previewing File
Non-image files can be presented as images. Active Storage supports previewing videos and PDF documents.
  • A video can be previewed by extracting its first frame 
  • A PDF document can be previewed by the first page.
Because of extracting previews requires third-party applications, FFmpeg for video and muPDF for PDFs and these libraries are not provided by Rails. So we must install them to preview files.
Install mutool and ffmpeg on MacOSX:
# muPDF
brew install mupdf-tools
# FFmpeg
brew install ffmpeg

Using preview file of Post:
<%= image_tag @post.file.preview(resize: "300x300") %>

Download file
We can add a link to download the file by using helper rails_blob_{path|url} like this:
<%=link_to 'Download', rails_blob_path(@post.file, disposition: "attachment")%>

You can check content_type of File:
@post.file.blob.image?
@post.file.blob.video?


Conclusion

In this article, I guide you step by step use Active Storage component to upload images and files in Rails application.

Example code on Github:
https://github.com/thanhluanuit/active-storage-in-rails5.2

References: