ActiveStorage in Rails 5.2, step by step.
24th April 2018Almost 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.
View Comments