Uploading Attachments in Rails Using Active Storage

Active Storage is a feature in Rails(applicable in versions >= 5.2) which facilitates uploading files to a cloud storage service like Amazon S3, Google Cloud Storage, or Microsoft Azure Storage and attaching those files to Active Record objects. It comes with a local disk-based service for development and testing and supports mirroring files to subordinate services for backups and migrations.

Active storage provides an easy and streight forward way to upload images without using additional gems like paperclip, carrierwave, shrine (which were in most use before rails 5.2).

How to Setup Active Storage:

1. Create a new rails app

Here I am creating a new rails application but please read the post carefully and you can apply the main feature in your existing application as well.

                
                  rails new active-storage-demo
                
              
                  
                  cd active-storage-demo
                
              
                  
                  bundle install
                
              

2. Create a model into which we will use ActiveStorage for uploading image

Here at this stage I am creating a model with name Post, but I am just taking this for an example, you can choose any name for your resource. You just need to do few things in your model class.

Run following command into your terminal:

                
                  rails g scaffold post title:string content:text
                
              

3. Adding Active storage to application

Since Active Storage also require active_storage_blobs and active_storage_attachments tables to store attachments so we also need to create these tables as well. You need to run following command for getting migration for both tables:

                
                  rails active_storage:install
                
              

4. Run Migration

Now Run following commands to get all tables into your schema:

                
                  rails db:migrate
                
              

5. Add attachments attributes into model

Now add following line into your post model:

                
                  has_one_attached :cover_image
                
              

6. Permitting cover_image as params

Open your app/controllers/posts_controller.rb and replace following method

                
                  def post_params
                    params.require(:post).permit(:title, :content)
                  end
                
              

with following:

                
                  def post_params
                    params.require(:post).permit(:title, :content, :cover_image)
                  end
                
              

7. Add cover_image field to form

Now since active storage attachment attributes will not be added to the table of model class, we need to add this into form manually. Add following snippet into app/views/posts/_form.html.erb, before submit button

                
                  
                    <div class="field">
                      <%= form.label :cover_image %><br>
                      <%= form.file_field :cover_image %>
                    </div>
                  
                
              

that's it. So far we have done everything to upload image and store this inot database.

8. Display active storage attachment to view

open your app/views/posts/show.html.erb and following line to top of the file:

                
                  <%= image_tag @post.cover_image %>
                
              

If you want to make a check that whether cover_image exists or not than you can use following snippet instead of above line:

                
                  
                    <% if @post.cover_image.attached? %>
                      <%= image_tag @post.cover_image %>
                    <% else %>
                      <p>No Cover Image Uploaded</p>
                    <% end %>
                  
                
              

And you are done.

Visit http://localhost:3000/posts/new and create a new post by uploading a new cover image for this post. Save it and display this post. You will see cover_image with post details.

These steps will work fine for development and test environment. But what if you want to upload image in production environment? You can not use local file system there to keep your images. You must use one the cloud providers mentioned in the beginning of the blog. Here I am going to show you that how we can store our images Amazon S3 cloud storage. You need to do following things for this:

1. Set amazon as active storage bucket

Open your config/environments/production.rb and replace following line:

                
                  config.active_storage.service = :local
                
              

with

                
                  config.active_storage.service = :amazon
                
              

2. Configure storage.yml

Open your config/storage.yml and uncomment following lines::

                
                  amazon:
                    service: S3
                    access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
                    secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
                    region: <%= Rails.application.credentials.dig(:aws, :aws_region) %>
                    bucket: <%= Rails.application.credentials.dig(:aws, :aws_bucket) %>
                
              

However you must edit your credentials before uncommenting these lines and set your AWS credentials there. Run following command to set the AWS secrets.

                
                  rails credentials:edit
                
              

If you are not familiar with rails credentials and you want to use any gems(dotenv or figaro) for managing your env variables you can simply do following configuration.

                
                  amazon:
                    service: S3
                    access_key_id: <%= ENV['AWS_ACCESS_KEY'] %>
                    secret_access_key: <%= ENV['AWS_SECRET_KEY'] %>
                    region: <%= ENV['AWS_REGION'] %>
                    bucket: <%= ENV['AWS_BUCKET'] %>
                
              

Just make sure to define these ENV vars in your production environment.

3. Add aws-sdk gem in your Gemfile:

Add following line to bottom of your Gemfile and Run bundle install:

                
                  gem "aws-sdk-s3", require: false
                
              

now run

                
                  bundle install
                
              

That's it.