We learn as we go, we write as we learn.

About michelada.io


By joining forces with your team, we can help with your Ruby on Rails and Javascript work. Or, if it works better for you, we can even become your team! From e-Commerce to Fintech, for years we have helped turning our clients’ great ideas into successful business.

Go to our website for more info.

Tags


ActiveStorage in Rails 5.2, step by step.

24th April 2018

Almost all of your web apps these days require that users upload a file at some point. It can be a profile picture, a photo of something they are trying to sell or a document of some sort.

For the past few years we've all used carrierwave or paperclip to handle uploads in Rails, but now that version 5.2 has been released we have another alternative in ActiveStorage.

What is Active Storage?

From its README:

Active Storage makes it simple to upload and reference files in cloud services like Amazon S3, Google Cloud Storage, or Microsoft Azure Storage, and attach those files to Active Records. Supports having one main service and mirrors in other services for redundancy. It also provides a disk service for testing or local deployments, but the focus is on cloud storage.

ActiveStorage is an engine that will help you handle file uploads in your Rails apps in a very straightforward and easy way.

Set it up

Let's create a new rails app for our example. I'm now assuming you already installed Rails 5.2 on your system.

$ rails new catalog

Before you can start using active storage, you need a few database tables. Run the following command to install the migrations that will create them:

$ bin/rails active_storage:install

Now, let's create a product model with a title and then run all migrations.

$ bin/rails generate scaffold products title:string

$ bin/rails db:migrate

Attach a file to a model

Now, let's say that all of our Product objects have one picture. We use the has_one_attached method to define this relationship:

class Product < ApplicationRecord
  has_one_attached :picture

end

We now need a field on the Product form where the user will be able to specify the file we're trying to upload. Edit app/views/products/_form.html.erb find where the field definitions are and add the code for the picture field:

  <div class="field">
    <%= form.label :title %>
    <%= form.text_field :title %>
  </div>

  <!-- ADD THIS -->
  <div class="field">
    <%= form.label :picture %>
    <%= form.file_field :picture %>
  </div>
  <!-- / ADD THIS -->

  <div class="actions">
    <%= form.submit %>
  </div>

Before we forget, let's add picture as a permitted parameter on the ProductsController.

class ProductsController < ApplicationController
  # ...
  
  # Never trust parameters from the scary internet, only allow the white list through.
  def product_params
    params.require(:product).permit(:title, :picture)
  end
  
end

Now if you start your server, navigate to /products, add a new Product, type a title and select a picture, you'll notice on the development.log file an INSERT statement to the active_storage_blobs table.

Look for something that reads very similar to this:

ActiveStorage::Attachment Create (0.3ms)  INSERT INTO "active_storage_attachments" ("name", "record_type", "record_id", "blob_id", "created_at") VALUES (?, ?, ?, ?, ?)  [["name", "picture"], ["record_type", "Product"], ["record_id", 4], ["blob_id", 2], ["created_at", "2018-04-25 04:07:32.090892"]]

Display the attached file

You can use the url_for helper to return the url of an attached file. In our case let's display the picture that we just uploaded. Edit app/views/products/index.html.erb and add the following line:

  <tbody>
    <% @products.each do |product| %>
      <tr>
        <td><%= product.title %></td>
        <!-- ADD THIS LINE -->
        <td><%= image_tag url_for(product.picture) %></td>
        <!-- / ADD THIS LINE -->
        <td><%= link_to 'Show', product %></td>
        <td><%= link_to 'Edit', edit_product_path(product) %></td>
        <td><%= link_to 'Destroy', product, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>

Your scaffold should now display the file that you uploaded when you created a product.

Different storage strategies.

When you are developing an app, you want to keep uploaded files in your disk, but when you deploy it it's very likely that you will use a third party service to store them, like Amazon S3, Azure Storage Service or Google Cloud Storage.

The way ActiveStorage handles this is very simple.

You declare the different storage strategies in config/storage.yml. For example, you can declare a disk strategy for development and an amazon strategy that can be used for production:

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

amazon:
  service: S3
  access_key_id: AMAZON KEY
  secret_access_key: AMAZON SECRET

Then, in each of the config/environments/*.rb files you can specify which storage to use.

For development, in config/environments/development.rb:

config.active_storage.service = :local

For production, in config/environments/production.rb:

config.active_storage.service = :amazon

Note that you may need to include additional gems to your Gemfile depending on your strategies. To use Amazon S3 you need to include aws-sdk-s3:

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

Many, many more features

ActiveStorage comes with plenty more features but this post would be too long if I wrote about them. I do want to mention Image Transformation that allows you to create resized versions of your images, and Direct Uploads that allows you to skip the controller and upload the files directly from the client to the cloud. It even includes the JavaScript libraries to make it easy.

Go ahead and read the official guides for more information. They are very well written and there's plenty more to learn about ActiveStorage from it.

David Padilla
AUTHOR

David Padilla

View Comments