File uploading feature is very common demand of any application, there are several options available in rails to incorporate this feature in an application such as Paperclip, Carrierwave, Refile etc. These are few gems that can be used to implement File Upload.

Rails 5.2 comes up with the inbuilt feature called Active Storage for the same purpose i.e File uploading which is much simpler than existing options.

Let’s see what makes it simple and reliable.

What actually Active Storage is?

Active Storage provides file uploading (supports all file types) facility to cloud storage like Amazon S3, Google Cloud and Microsoft Azure Storage. It provides disk-based service for development and testing environment. With the help of Active Storage, an application can transform image uploads with ImageMagic, generate image representations of non-image uploads like PDFs and videos, and extract meta-data from arbitrary files.

Let’s start our setup for Active Storage for uploading files to Amazon S3. Here we will assume that we have our existing rails 5.2 application in which we wish to implement a file upload feature. Here we go!

Active Storage Setup

Active Storage uses two tables in the application’s database named active_storage_blobs and active_storage_attachments. So first we need to generate these two tables in our application’s database with the following commands:

Generate a migration that creates these table and runs the migration

rails active_storage:install
rails db:migrate

Now we will declare Active Storage services in config/storage.yml. For each service our application uses, we will provide a name and the requisite configuration. Following example declares three services named local, test, and Amazon.

local:
service: Disk
root: <%= Rails.root.join("storage") %>
test:
service: Disk
root: <%= Rails.root.join("tmp/storage") %>
amazon:
service: S3
access_key_id: "52634587467"
secret_access_key: "hqwjkshwdhewjk"
(Bellow we will be seeing how to save aws secrets using Rails 5.2 encrypted credentials, as of now you can keep this simple)

You can add any of the supported services by Active Storage (Amazon S3, Google Cloud Storage, or Microsoft Azure Storage) here but as this article is about Amazon S3 we will see Amazon S3 setup further.

Each environment will use a different service, so we need to tell to Active Storage that which service is to be used. So depending upon application’s environment (Development, Test, Production) we need to add the following line in our environment files. Suppose here we are setting up Active Storage for Production environment so we will add the following to config/environments/production.rb

# Store files on Amazon S3.
config.active_storage.service = :amazon

Now add gem "aws-sdk-s3", require: false to you Gemfile and run bundle installs.

That’s it! Here our Active Storage setup is completed.
Now we will see how to upload files with active storage.

Attaching files to records

Active Storage provides two macros has_one_attached and has_many_attached for defining a relationship between files and models

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.

For example, suppose our application has an Employee model. We want to have each employee to have one photo, then our Employee model will look like this –

class Employee < ApplicationRecord
has_one_attached :photoe
end

We can create Employee with a photo like:

class EmployeesController < ApplicationController
def create
employee = Employee.create!(employee_params)
redirect_to root_path
end
private
def employee_params
params.require(:employee).permit(:first_name, :last_name, :photo)
end
end

Can attach the photo to an existing employee using:

employee.photo.attach(params[:photo])

Can check whether an employee has the photo or not with:

employee.photo.attached?

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

For example, suppose our application has a Product model. We want to have each product to have multiple images, then our Product model will look like this –

class Message < ApplicationRecord
has_many_attached :images
end

We can create Product with images like –

class ProductsController < ApplicationController
def create
products = Product.create!(product_params)
redirect_to product
end
private
def product_params
params.require(:product).permit(:title, :description, images: [])
end
end

Can attach images to existing products using:

@product.images.attach(params[:images])

Can check whether a product has the image or not with:

@product.images.attached?

Attaching File/IO Objects

There is often a situation that we need to attach some files to records which are not arriving from HTTP request, sometimes we need to attach files which are stored on our system, this also can be achieved using Active Storage. To do that, provide a Hash containing at least an open IO object and a filename –

@product.image.attach(
io: File.open('/path/to/file'),
filename: 'file.pdf'
)

If possible try to provide a content type as well. Active Storage attempts to determine a file’s content type from its data. It falls back to the content type you provide if it can’t do that –

@product.image.attach(
io: File.open('/path/to/file'),
filename: 'file.pdf',
content_type: 'application/pdf'
)

We can bypass the content type inference from the data by passing in identify: false along with the content_type.

@product.image.attach(
io: File.open('/path/to/file'),
filename: 'file.pdf',
content_type: 'application/pdf'
identify: false
)

If we don’t provide a content type and Active Storage can’t determine the file’s content type automatically, it defaults to application/octet-stream.

Removing Attached Files

We can remove attached files with purge:

# Synchronously destroy the avatar and actual resource files.
@employee.photo.purge
# Destroy the associated models and actual resource files async, via Active Job.
@employee.photo.purge_later

Linking to Files

Generating URL for files.

url_for(@employee.photo)

To create a download link, use the rails_blob_{path|url} helper. Using this helper allows you to set the disposition.

rails_blob_path(@employee.photo, disposition: "attachment")

If we need to create a link from outside of controller/view context (Background jobs, Cronjobs, etc.), you can access the rails_blob_path like this:

Rails.application.routes.url_helpers.rails_blob_path(@employee.photo, only_path: true)

Downloading Files: We can download file with:

pic = @employee.photo.download

We might want to download a blob to a file on disk so an external program (e.g. a virus scanner or media transcoder) can operate on it. Use ActiveStorage::Blob#open to download a blob to a temp file on disk

product.images.open do |file|
system '/path/to/virus/scanner', file.path
# ...
end

That’s it! Here we have seen all basics related to Active Storage. Isn’t it pretty simple?

How to store credentials using a master key in Rails 5.2?

Rails 5.2 have replaced both secrets provided by Rails 5.1, with encrypted credentials. We cannot use plain text credentials. There’s only credentials.yml.enc.

To use encrypted credentials, we need a key. Without this encryption key, we won’t be able to decrypt our credentials. We can commit the encrypted file to our repository but we should avoid committing the encryption key.

When we fire command rails new, one file gets generated config/master.key. The encryption key for our application gets generated and get stored in this file.

The encrypted credentials are saved on config/credentials.yml.enc. Don’t edit the file directly. To add credentials, run

bin/rails credentials:edit

If you do not have any editor set, use the following:

EDITOR=nano bin/rails credentials:edit
(I prefer to use nano, you can set it your favorite one)

credentials yml

credentials.yml.enc will get opened enter your AWS secrets here

After saving the file, the encrypted version will be saved to config/credentials.yml.enc.

Reading Credentials -For using the credentials in the production environment, add the following to config/environments/production.rb

config.require_master_key = true

Similarly we can add this for Test and Development environments.
Now we can access the credentials with Rails.application.credentials. For example, if we have

amazon:
service: S3
access_key_id: "52634587467"
secret_access_key: "hqwjkshwdhewjk"

We can access access_key_id, secret_access_key with –

Rails.application.credentials.dig(:aws, :access_key_id)
Rails.application.credentials.dig(:aws, :secret_access_key)

That’s it, you are now all set to rock!

Click here for more details


At BoTree Technologies, we build enterprise applications with our RoR team of 25+ engineers.

We also specialize in RPA, AI, Python, Django, JavaScript and ReactJS.

Consulting is free – let us help you grow!