Skip to Content
Course content

210: Active Storage for File Uploads

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I remember the first time I tried to handle file uploads in a Rails app before Active Storage existed. I did what seemed logical: I added a profile_picture_url string column to my users table, wrote some custom code to move files from a temp folder to a /public/uploads directory, and spent an entire weekend fighting with permissions and broken links. It was a nightmare.

The String Column Trap

Let's say we're building a simple member directory. I'll start the way I used to, just to show you why it's a bad idea. I might try to generate a migration like this:

rails generate migration AddAvatarToUsers avatar:string

The logic here is that I'll just store the filename (like "me.jpg") in the database and look it up in a folder. But wait—what happens when I move the app to a different server? Or what if I want to move my images to Amazon S3 or Google Cloud Storage? I'd have to rewrite my entire file-handling logic. It's brittle. This is exactly why Rails gave us Active Storage.

Letting Rails Handle the Heavy Lifting

Instead of managing columns ourselves, Active Storage uses a polymorphic approach. It creates its own tables to track files, meaning our users table stays clean. First, I need to actually install the framework:

rails active_storage:install
rails db:migrate

If you look at your schema.rb now, you'll see active_storage_blobs and active_storage_attachments. I don't need to touch these; Rails uses them as a "join table" between my model and the actual file data.

Now, I'll go into my User model and tell Rails that a user can have one avatar:

class User < ApplicationRecord
  has_one_attached :avatar
end

Notice I didn't run a migration to add an avatar column to the users table. That's the part that usually trips people up. The "attachment" exists in those separate Active Storage tables, not in the user record itself.

The Parameter Wall

I've got the model set up, so now I'll add a file field to my form:

<%= form_with model: @user do |f| %>
  <%= f.label :avatar %>
  <%= f.file_field :avatar %>
  <%= f.submit %>
<% end %>

I'll try to upload a photo, hit submit, and... nothing. The user saves, but the avatar is still empty. I check my server logs and see this classic warning: Unpermitted parameter: :avatar.

Right. I forgot that since avatar is now a parameter being passed to the controller, I have to explicitly allow it in my Strong Parameters. I'll head over to the users_controller.rb and update the permit list:

def user_params
  params.require(:user).permit(:name, :email, :avatar)
end

Now when I refresh and upload, the file actually sticks. Rails handles the upload to the local disk (by default) and creates the necessary entries in the blobs table automatically.

Actually Seeing the Image

Now for the final hurdle: displaying it. I can't just do @user.avatar because that returns an Attached::One object, not a URL. If I try to put that in an image tag, it'll just crash or show nothing.

I'll try using the url_for helper, which tells Rails to generate a temporary, signed URL for the file:

<% if @user.avatar.attached? %>
  <%= image_tag @user.avatar %>
<% else %>
  <%= image_tag "default-avatar.png" %>
<% end %>

Wait, I noticed the image is huge—like, 4000 pixels wide. My layout is completely broken. I can't just rely on CSS for this; I need to actually resize the image. This is where I'd usually reach for a gem like CarrierWave, but Rails has built-in variants now.

I'll change my image tag to use a variant:

<%= image_tag @user.avatar.variant(resize_to_limit: [100, 100]) %>

The first time I load this page, it might be a bit slow because Rails is processing the image. But after that, it caches the resized version. Now we have a clean, managed upload system without a single custom column in our users table.




📋 Practical Task

Implementing a Project Portfolio Header

You are building a portfolio app where each Project needs a header image. Currently, the Project model only has a title and description.

  • Install Active Storage in the application.
  • Update the Project model to allow a single attached file named header_image.
  • Update the ProjectsController to permit the header_image parameter.
  • Modify the project "New" and "Edit" forms to include a file upload field for the header image.
  • In the project "Show" view, display the uploaded image, but ensure it is resized to a maximum of 800x400 pixels using a variant. Provide a fallback placeholder image if no header is attached.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.