Rails Active Storage is a game-changer when it comes to handling file uploads in your web apps. I remember the first time I used it - it felt like magic! Gone were the days of wrestling with complex file management code. Active Storage makes it a breeze to upload files directly to cloud services like AWS S3.
Let’s dive into how to set it up and use it in your Rails app. First, you’ll need to add the necessary gems to your Gemfile:
gem 'aws-sdk-s3', require: false
After running bundle install
, you’ll need to configure Active Storage. Run the following command to generate the necessary migration:
rails active_storage:install
This creates a migration file that sets up the tables Active Storage needs. Run rails db:migrate
to apply the changes to your database.
Next, you’ll need to configure your storage service. In config/storage.yml
, add your AWS S3 credentials:
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: us-east-1
bucket: your_bucket_name
Remember to replace your_bucket_name
with your actual S3 bucket name. It’s crucial to keep your AWS credentials secure, so I always use Rails’ encrypted credentials feature.
Now, tell your Rails app to use Amazon S3 for Active Storage. In config/environments/production.rb
, add:
config.active_storage.service = :amazon
With the setup out of the way, let’s look at how to use Active Storage in your models. Say you have a Blog model and you want to allow users to attach a cover image to their blog posts. In your app/models/blog.rb
file, add:
class Blog < ApplicationRecord
has_one_attached :cover_image
end
This has_one_attached
macro sets up a one-to-one relationship between the Blog model and a file attachment. If you want to allow multiple file attachments, you can use has_many_attached
instead.
Now, in your controller, you can handle file uploads like this:
class BlogsController < ApplicationController
def create
@blog = Blog.new(blog_params)
if @blog.save
redirect_to @blog, notice: 'Blog was successfully created.'
else
render :new
end
end
private
def blog_params
params.require(:blog).permit(:title, :content, :cover_image)
end
end
In your view, you can add a file input field to your form:
<%= form_with(model: @blog, local: true) do |form| %>
<%= form.label :title %>
<%= form.text_field :title %>
<%= form.label :content %>
<%= form.text_area :content %>
<%= form.label :cover_image %>
<%= form.file_field :cover_image %>
<%= form.submit %>
<% end %>
To display the uploaded image, you can use the image_tag
helper in your view:
<% if @blog.cover_image.attached? %>
<%= image_tag @blog.cover_image %>
<% end %>
One of the cool things about Active Storage is that it handles image variants out of the box. This means you can easily create different sizes or versions of your uploaded images. For example, you might want to display a thumbnail version of the cover image in a list of blog posts:
class Blog < ApplicationRecord
has_one_attached :cover_image
def cover_image_thumbnail
cover_image.variant(resize: '100x100').processed
end
end
Then in your view:
<%= image_tag blog.cover_image_thumbnail %>
This will display a 100x100 pixel version of the cover image. Active Storage uses the image_processing gem to handle these transformations, so make sure you have it in your Gemfile.
Now, let’s talk about direct uploads. By default, when a user uploads a file, it first goes to your Rails server, which then uploads it to S3. This can be slow for large files. Direct uploads allow the file to go straight from the user’s browser to S3, which is much faster.
To enable direct uploads, you need to include the Active Storage JavaScript module in your application. In your app/javascript/packs/application.js
, add:
import * as ActiveStorage from "@rails/activestorage"
ActiveStorage.start()
Then, in your form, add the direct_upload: true
option to your file field:
<%= form.file_field :cover_image, direct_upload: true %>
This will automatically handle the direct upload process for you. Pretty neat, right?
But what if you want more control over the upload process? Maybe you want to show a progress bar or allow users to cancel uploads. Active Storage’s got you covered there too. You can use the DirectUpload
JavaScript API for fine-grained control:
import { DirectUpload } from "@rails/activestorage"
const input = document.querySelector('input[type=file]')
input.addEventListener('change', (event) => {
const file = event.target.files[0]
const upload = new DirectUpload(file, '/rails/active_storage/direct_uploads')
upload.create((error, blob) => {
if (error) {
// Handle the error
} else {
// Add the blob id to a hidden field
const hiddenField = document.createElement('input')
hiddenField.type = 'hidden'
hiddenField.name = 'blog[cover_image]'
hiddenField.value = blob.signed_id
document.querySelector('form').appendChild(hiddenField)
}
})
})
This gives you complete control over the upload process. You can add event listeners for progress, error handling, and more.
One thing to keep in mind when using Active Storage is that it doesn’t validate file types or sizes by default. You’ll need to add your own validations. Here’s an example of how you might do that:
class Blog < ApplicationRecord
has_one_attached :cover_image
validate :acceptable_image
private
def acceptable_image
return unless cover_image.attached?
unless cover_image.blob.byte_size <= 1.megabyte
errors.add(:cover_image, "is too big")
end
acceptable_types = ["image/jpeg", "image/png"]
unless acceptable_types.include?(cover_image.content_type)
errors.add(:cover_image, "must be a JPEG or PNG")
end
end
end
This validation ensures that the uploaded file is no larger than 1MB and is either a JPEG or PNG image.
Another cool feature of Active Storage is its ability to handle metadata. When you upload an image, Active Storage automatically extracts metadata like image dimensions. You can access this metadata in your code:
blog.cover_image.blob.metadata
This returns a hash of metadata that might look something like this:
{
"identified"=>true,
"width"=>800,
"height"=>600,
"analyzed"=>true
}
You can use this metadata to do things like validate image dimensions or display image information to users.
Active Storage also makes it easy to purge (delete) attachments. If a user wants to remove their blog post’s cover image, you can do:
blog.cover_image.purge
This will remove the file from your storage service and delete the associated Active Storage records.
One thing I’ve found really useful is the ability to test file uploads in your Rails tests. Active Storage provides some handy helpers for this. In your test file, you can do something like:
test "can attach a cover image to a blog post" do
blog = Blog.create(title: "My awesome blog post", content: "Lorem ipsum...")
blog.cover_image.attach(io: File.open('/path/to/image.jpg'), filename: 'image.jpg', content_type: 'image/jpeg')
assert blog.cover_image.attached?
end
This simulates attaching a file without actually needing to upload anything to S3 during your tests.
As your app grows, you might find that you’re using a lot of storage space for old or unused files. Active Storage includes a handy rake task to clean up unattached blobs:
rails active_storage:purge_unattached
This will delete any blobs in your storage that aren’t attached to a record. It’s a good idea to run this periodically to keep your storage tidy.
One last tip: if you’re dealing with sensitive files, you might want to use private ACLs for your S3 bucket. You can configure this in your storage.yml
:
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: us-east-1
bucket: your_bucket_name
private: true
With this setup, your files won’t be publicly accessible. Instead, Rails will generate signed URLs that provide temporary access to the files.
In conclusion, Active Storage is a powerful tool that simplifies file handling in Rails applications. It integrates seamlessly with cloud storage services, provides easy-to-use APIs for file attachments, and includes features like image variants and direct uploads. Whether you’re building a simple blog or a complex application with extensive file management needs, Active Storage has got you covered. Happy coding!