Migrating paperclip attachments

In web development, sometimes you have to perform data migrations that involve migrating file attachments on existing models onto new models. I recently had to do this on a client project and I’ll go over some code snippets demonstrating the steps that need to be taken.

Just as a preface, this is a Ruby on Rails application that is using the paperclip gem to handle file attachments. All files are uploaded to AWS S3.

Let’s say that we have a User model that has an attachment column called avatar. In the beginning of the application’s life, the application only allows the user to have one avatar. This is relatively simple to set up in Ruby on Rails. Here’s an example User ActiveRecord model set up with paperclip.

Pretty simple and nothing special. Now, let’s say that we get a new requirement that will allow users to have multiple avatars. How do we handle this? In this case, I’d say we have two options

  1. Add as many extra paperclip attachment columns as necessary to the users table and attach all future avatars to the user model directly
  2. Create a new table specifically for storing image attachments and set up a has_many relationship between the User model and the Picture model.

Option 1 is simpler but is the more naive approach. Also, it doesn’t scale. What if in the future, we have other models, let’s say a Book model for example, that needs to have multiple image attachments as well? The more flexible and scalable option is options number 2 where we set up a separate table.

Let’s set this up shall we?

First, create a new picture model by running this command in your terminal

And, in your newly created migration file, set up a polymorphic association like this

And your Picture model should look like this

Now, we want to set up the User to have a has_many polymorphic association with the Picture model.

Now, we want to run a migration to migrate over all pre-existing user’s avatar attachments over to the new pictures table. Create a new migration file by running something like this in your terminal.

And in your newly created migration file, set it up like

The script in the migration above will grab all users that have avatars attached, loop through them to create new picture record with the same avatar attachment and associate that instance of the picture with the appropriate user, and then delete the original avatar in the user record to free up hard drive space. The last part is important if you are using something like AWS S3 and you don’t want random files that aren’t associated with any application records lying around.

And finally, we need to run one more migration to remove the avatar attachment column in the users table. Create another migration with a command like this

And in the migration file, simply remove the attachment from the users table

And that’s it! Don’t forget to change your views appropriately so that your app doesn’t break for your users.

Also, before I end this post, why a polymorphic association rather than a normal association? Because if we make the pictures table polymorphic, we can reuse that table to store all types of image attachments and have one place where we can hold all logic for handling image attachments. That should result in a cleaner, DRY’er, and more maintainable codebase in the long run.