Moving from CarrierWave to ActiveStorage in a Rails app

NJ Pearman
EPFL Extension School
9 min readJun 20, 2018

We use Ruby on Rails to teach web application development at the EPFL Extension School. It’s a great framework that is solid, tried-and-trusted and presents an easy-to-follow structure when taking your first steps learning about web development.

But it is also a constantly evolving piece of software, with improvements and new features being released every year. One of the recent additions to Rails in version 5.2 is the ActiveStorage feature, which adds easy file upload management to the core Rails framework for the first time.

File uploads in Rails, old vs new

In the first iteration of our web application development program, we include a subject to learn how to add file uploads in a Rails application. We use Rails 5.1 in the program however, and the file upload subject uses one of the widely-used third party solutions for adding file uploads, called CarrierWave. This is a solid third party gem that allows us to implement file uploads in a Rails app and is in use in many real-world Ruby on Rails apps — including our learner platform!

But with the introduction of ActiveStorage, these third party gems become less attractive than this new solution bundled inside Rails. In fact, Paperclip, one of the other popular third-party file upload gems, has already announced that it is deprecated in favour of ActiveStorage. The developers of Paperclip have recognised that ActiveStorage is a better choice for developers and stopped further work on their own gem.

So let’s look specifically at what it takes to move from CarrierWave in Rails 5.1 to ActiveStorage in Rails 5.2.

The application

In the web application development program, we work through building a web application called My Bucket List. This is an app that allows users to create and track experiences that they’ve always wanted to accomplish. Users can see what other people have created and track those too. One of the features in the app allows a user to upload their own avatar, and we build that feature in order to learn about file uploads and attachments using CarrierWave.

A user can upload an avatar with this form, using CarrierWave

So first, let’s have a quick review of what’s involved with adding a simple attachment using CarrierWave.

CarrierWave configuration

In order to include CarrierWave in a project, we need to:

  • Add the CarrierWave gem to the Gemfile.
  • Include the CarrierWave adapter for ActiveRecord within a config initializer, config/initializers/carrierwave.rb.

and then to add an image attachment to a model, we need to:

  • Generate an Uploader using rails generate uploader UploaderName.
  • Add an attribute on the model to store the attached filename for each record, with associated database migration.
  • Edit values in the uploader, such as upload storage location and default image.
  • Use the mount_uploader macro in the model to include the particular uploader.
  • Add a file form field where an image can be uploaded.
  • Add the model attribute to the list of strong parameters in the controller that handles the form containing the file field.

Let’s consider one of these points further: in CarrierWave, it’s necessary to add specific attributes to any model that needs to have an attached file. For example, a User model that needs an avatar image attached to it will need an attribute called :avatar that is a String. This requires a database migration to add the underlying database column, plus creating and mounting an Uploader class to that attribute in the model.

This isn’t particularly laborious but, as we’ll see, ActiveStorage requires much less code on a case-by-case basis.

One more point to highlight is that in my app — the My Bucket List app — the CarrierWave avatar uploader is set up with the default simple file storage. In order words, uploaded images are stored somewhere inside the public/ folder in the application. That precise path is defined within the Uploader class for the avatar, and is set to "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}". This is a path relative to the public/ folder, so that uploaded images are directly accessible via the web server. This allows direct access to the attachment files, which we can contrast with ActiveStorage later on.

Upgrading Rails

Before looking at ActiveStorage, it’s necessary to upgrade to Rails 5.2. This is a task that can be quite involved, depending on the complexity of your application. The application that I’m using here, which we build in our WAD program, isn’t particular difficult to update but it still needs some care and attention.

Here’s what I did to update the My Bucket List application from Rails 5.1 to Rails 5.2.

First, I created a new branch in my git repository to isolate the upgrade — it is important to be able to go back to the previous working version of an app if anything goes awry in the upgrade process!

On the new branch, I changed the version of Rails in the Gemfile from '~> 5.1.2' to '~> 5.2.0', and then ran bin/bundle update rails — this command downloads the new version of rails and all of the related gems in the application. For my application, there were no gem conflicts but if you are using third party gems, you might need to resolve conflicts between different gem versions at this point!

After installing the new Rails gem and it’s dependencies, it’s necessary to update the application itself. I did this using bin/bundle exec rails app:update. This command programmatically updates all of the necessary files in the application to work with Rails 5.2.

But… this command will overwrite changes that you have made to your application, such as config/routes.rb. Luckily, the command asks whether to overwrite each file, and it’s possible to diff any of those files during this process to check what would be overwritten. The prompt for each overwrite is [Ynaqdh], which means:

  • Y for “Yes, overwrite my file”, and is the default option if you just press Enter.
  • n for “no, do not overwrite my file”
  • a for “Yes, overwrite this file and overwrite every other file too!”. (My general rule is never use a).
  • q for “Quit this process”
  • d for “Show me the differences between my file and the overwrite”
  • h for “Show me a list of what Ynaqdh means”

In my application, I accepted each overwrite with Y except config/routes.rb and config/locales/en.yml. Those two files were files that I had changed and so I wanted to keep my version, so chose n.

This upgrade process might be more involved if you have a more complex application, particularly if you have lots of custom configuration in any of the environment configuration files i.e. config/environments/production.rb, config/environments/test.rb, and config/environments/development.rb. The upgrade adds important settings to these files but you’ll also want to keep your own custom configuration — this is where having the original version of the application in a separate git branch helps. It’s then possible to the code in this old branch to check that all of your application code and configuration is still in place after the upgrade.

After running bin/bundle exec rails app:update, it was necessary for me to add the bootsnap gem to the Gemfile. This is a new gem used in Rails 5.2 but it is not added as part of the app:upgrade command. So I just added gem 'bootsnap' to the Gemfile and then ran bin/bundle install.

After this, my Bucket List application started up as before using rails server.

Replacing CarrierWave with ActiveStorage

So, with the application now running on Rails 5.2, I could start replacing CarrierWave with ActiveStorage.

With ActiveStorage, it’s unnecessary to add model-specific attributes to store the attachment filenames that are necessary with CarrierWave. ActiveStorage works with the same two associated tables for all attachments.

In order to set up these two tables, I ran bin/bundle exec rails active_storage:install followed by rails db:migrate. The two tables are active_storage_blobs and active_storage_attachments; the first is for the details of the attached file and the second is for the polymorphic join table that links an attached file to a model record.

And here is the important point: once we have created those two tables, we don’t need to create any other migrations in order to include attachments in our models! This makes ActiveStorage easy to work with from a database perspective.

Next, I looked at replacing the implementation of the :avatar attachment with ActiveStorage instead of CarrierWave. In my app, each User has an :avatar. Because I was using CarrierWave previously, I had an :avatar attribute mounted to a CarrierWave Uploader in the User model, declared like this:

class User < ApplicationRecord
# ... various codez
mount_uploader :avatar, AvatarUploader # ... other codez
end

The CarrierWave :avatar attachment can be replaced with an ActiveSupport attachment by using has_one_attached :avatar, like this:

class User < ApplicationRecord
# ... various codez
# commented old uploader for reference
# mount_uploader :avatar, AvatarUploader
has_one_attached :avatar
# ... other codez
end

That is a change to one line of code. At this point, what else needs to be done? For example, how is the upload storage location set? Remember that in my app, the CarrierWave avatar uploader is set up with the default simple file storage, and the location for this is explicitly set in the avatar_uploader.rb file. Simple file storage is the default in ActiveStorage too. The location is set within the config/storage.yml file, with a default value for local uploads.

The default config for ActiveStorage means that I don’t need to write any more code for my uploads to work for the modified :avatar attribute in my application. I’ve changed the upload mechanism entirely from CarrierWave to ActiveStorage and I don’t need to do anything more: I don’t need to make any changes to the Controller that manages the upload, because :avatar is already declared as a strong parameter. And I don’t need to change the file form field in the view that allows users to select an image to upload, because this is backed by the :avatar “attribute”.

But there is one more change that I need to make to migrate from CarrierWave to ActiveStorage, and that is how to use the attached image in views. With CarierWave, I used the avatar_url method to render the full image path. With ActiveStorage, the file URL is not rendered from the attachment attribute itself, but rendered by passing the attribute to helper methods. So the full attachment URL can be rendered using url_for(user.avatar), or directly with image_tag.

It’s also necessary to explicitly check whether the attachment is present, so a complete usage in a view would look something like this:

<% if current_user.avatar.attached? %>
<%= image_tag current_user.avatar, class: 'avatar' %>
<% else %>
<%= image_tag 'default-avatar', class: 'avatar' %>
<% end %>

Once I updated the references to the user.avatar_url in my views with snippets like that above, I had moved completely from CarrierWave to ActiveStorage for all new uploads.

The old CarrierWave code needs to be tidied up, and there is a question over what to do with any existing uploads that were done with CarrierWave. But see below about that…

More complex scenarios

Almost all production scenarios will be more complex than the one I outlined above. It’ll be necessary to consider things such as uploads being stored on CDNs or remote servers like AWS S3, and image resizing for thumbnails, etc. ActiveStorage allows us to do a lot of those things out of the box too and seems very well engineered for developers’ needs. As you might expect, there is good documentation in the Rails guide.

With regards to migrating existing uploads from CarrierWave to ActiveStorage, this would certainly be possible with a relatively small rake script. I will aim to cover that in a future post.

This was a simple scenario, but overall I was really pleased with how easy it was to make use of ActiveStorage and replace CarrierWave with it. I also like the thought that has gone into the various parts of ActiveStorage, as everything seems to be in files and folders that nicely match the structure of the rest of Rails. I definitely think it’s worth getting into Rails 5.2 in order to use ActiveStorage for file uploads in you applications!

Learn more!

Interested in learning more about Ruby, and Ruby on Rails? I teach Web Application Development at the EPFL Extension School, a certified online learning platform teaching digital skills in data science, web application development, and more. EPFL is one of the world’s leading universities and is ranked the “Number 1 Young University” by the Times Higher Education Ranking.

--

--