Many applications let users upload files. This lesson covers storing uploads as model attributes with CarrierWave, generating image versions, and testing uploads.
Important
Work on this lesson in
advisormode.
Learning goals
- You can explain how an uploaded file travels from the browser to the server (a multipart form post) and where a Rails app can store it.
- You can treat an uploaded file like any other model attribute — assign, validate, persist — using an uploader library (CarrierWave in our stack).
- You can generate image versions such as resized or cropped variants, and explain which library does the processing.
- You can control whether a browser displays a file inline or downloads it (e.g. with a
Content-Dispositionheader). - You can keep an upload across a form redisplay after a validation error.
- You can test uploads: an end-to-end test for the happy path, unit tests for versions and validations.
- You can explain how ActiveStorage, Rails' built-in alternative, differs from CarrierWave and why we default to CarrierWave.
Resources
Read what's new to you, skim what's familiar, skip what you already master. Stop when you can meet the learning goals.
Your agent can also generate an overview, a tutorial or an explanation for anything here, tailored to what you already know. Just ask.
- 📄 CarrierWave README Show archive.org snapshot — uploaders, versions, validations, and keeping uploads across form redisplays Show archive.org snapshot
- 📄 Common mistakes when storing file uploads with Rails — our card
- 📄 CarrierWave: processing images with libvips — our card on the image library behind the versions
- 📄 CarrierWave: how to attach files in tests and built-in RSpec matchers — our cards
- 📄 Capybara: testing file downloads — our card, for the end-to-end test of the download link
- 📄
MDN:
multipart/form-dataShow archive.org snapshot and the form'senctypeShow archive.org snapshot — how a file travels from the browser to the server - 📄
MDN:
Content-DispositionShow archive.org snapshot — inline display vs. forced download - 📄 Rails Guide: Active Storage Show archive.org snapshot — the built-in alternative; see "Alternatives" below
Exercises
In MovieDB, allow movie authors to upload a movie poster using the carrierwave gem:
- The poster should be uploaded in the form where we can also fill in title, year, etc.
- On the movie show view, render a poster version that is 400 pixel wide, with a height that respects the aspect ratio of the original image
- On the movie index view, render a poster version that is 100 pixel wide and cropped to 100 pixel height, regardless of the original aspect ratio. The image should be cropped to the square aspect ratio, not distorted.
- On the movie show view, offer a link to download the original poster image file.
- The download link should always download the image and never display it inline within the browser window. You can do so with either the
[download]HTML attribute, or by sending aContent-Dispositionheader when delivering the image. - When editing a movie, offer controls to delete or replace the poster image file.
- There should be a validation allowing only uploads of
.jpg,.jpeg,.pngand.webpfiles. - You do not need to treat movie posters as confidential, so it's not important if an unauthorized user can see a poster.
- When saving a poster and there is a validation error on another field (e.g. missing title), the file selection should be preserved when the form is displayed again. Use the CarrierWave cache Show archive.org snapshot for this.
Make sure to add tests for adding, changing and deleting a poster image:
- An integration test for the happy path
- User uploads image, sees an image review, downloads image.
- Capybara needs the browser to show an interactive HTML page to be happy. Capybara methods will fail when the browser shows a "Save as" dialog or when it displays an inline download within the browser window. See Testing File Downloads with Selenium for alternatives.
- Unit tests for details
- Rendering of image versions and validation of file extension can be tested with RSpec
- These can be model specs for either the
Movieor yourPosterUploader - To save a poster, just assign any
IOobject to theMovie#poster=setter and save the movie. - In a real HTTP request scenario, the assigned
IOobject also carries information about image's original filename and its MIME type. You can build a similar IO object like this:movie.poster = Rack::Test::UploadedFile.new('spec/fixtures/poster.jpg', 'image/jpeg')
Alternatives
Rails ships its own upload library, ActiveStorage Show archive.org snapshot . It stores metadata in two extra tables and serves files through Rails controllers (redirecting or proxying to a storage service), with variants generated on demand. CarrierWave mounts an uploader on a model attribute, stores files in the file system by default, and can deliver downloads without hitting a Rails controller. You will meet ActiveStorage in tutorials and in client projects; our default is CarrierWave — see Our Rails stack.