Upload Photo with Active Storage

Build Blog with Ruby on Rails (Part 4)
Luan Nguyen
Luan Nguyen
Sep 05, 2020 · 4 min read

In this part, I'm going to share with you how to upload a cover photo for Post using Active Storage component in Ruby on Rails application.

What'll we learn:

  • Introduction to Active Storage
  • Setup Active Storage
  • Configurate Amazon S3 service to store files
  • Using Active Storage to upload photos
  • Show the cover photo of Post in Homepage
  • Solution to N + 1 queries
  • Conclusion

Source: unsplash.com/@tateisimikito


This is part 4 in Project: Build Blog with Ruby on Rails


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.

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, which uses to store files.
  • active_storage_blobs
  • active_storage_attachments 
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 in Active Storage
We using Amazon S3 to store photos, so have to 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 to config/environments/development.rb:
# Store uploaded files on the local file system
config.active_storage.service = :local

In the production environment, we use the Amazon S3 service by adding 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 Photo for Post

Each post has one cover_photo attached to it.
We use has_one_attached macro to sets up a one-to-one relationship between Post and Photo
class Post < ApplicationRecord
  has_one_attached :cover_photo
end

The has_one_attached macro sets up a one-to-one mapping between records and files.

In Form:
We use file_field helper method to upload photo.
# app/views/posts/_form.html.erb
<div class="form-group">
  <%= form.label :cover_photo %>
  <%= form.file_field :cover_photo, class: 'form-control' %>
</div>

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

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

Show the cover Photo after uploading:
# app/views/posts/show.html.erb
<p>
  <strong>Cover Photo:</strong>
  <%= image_tag @post.cover_photo %>
</p>

We will receive a small bug when view the Post which has not added the cover photo yet.
The errors: cover photo attachment of post is nil
Can't resolve image into URL: to_model delegated to attachment, 
but attachment is nil

To avoid this error, we should check attachment before showing the cover photo:
<% if @post.cover_photo.attached? %>
  <%= image_tag @post.cover_photo, width: '100%' %>
<% end %>

Call cover_photo.attached?  to determine whether a post has a cover photo.


Transforming Images

Transforming images is a process that supports to create variant photos from an uploaded photo.
To enable this functionality, we add the image_processing gem to your Gemfile:
gem 'image_processing', '~> 1.2'

Run bundle to install:
bundle install

Don’t forget to restart the server!

Now, we can create a new variation of the photo, call variant  method on the Blob.
You can pass any transformation to the method supported by the processor (default is MiniMagick).

We'll create a variation of Cover Photo by resize original photo to "300x200" :
post.cover_photo.variant(resize: "300x200")


Show the Variation of cover Photo in Homepage:

# app/views/home/index.html.erb
<% if post.cover_photo.attached? %>
  <%= image_tag post.cover_photo.variant(resize: "300x200")%>
<% end %>

Eager loading Active Storage to avoids N+1 queries: Include the attached blobs in the query
Post.with_attached_cover_photo

View the result:
Homepage


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.
____________________

Conclusion

In this article, I guided you step by step use Active Storage component to upload photos in Rails application.
In part 5, I'm going to share with you about Action Text, which helps us add a rich-text-editor for writing Post in Blog application.

References: