They say that a picture is worth a thousand words. How they came up with a such a nice, conveniently specific number number I’ll never know. But what I do know is that everything on the web is just data floating around in cyberspace. And when I say everything, I really do mean everything – including pictures!
I’ve worked on a few different projects that have required building out an interface to allow a user or an admin to upload images. The first time that I had to do this, I knew that there were a few different Rails gems out there to help make this magic happen. But I didn’t really understand what was going on when I implemented these gems the first time around. To be quite honest, the first time I had to implement file uploading, I just followed the setup steps rather blindly. Now that I’ve had to solve the same problem multiple times, however, I feel a bit more comfortable with the process.
There are a few different gems out there for handing file attachment in Rails, but my favorite one to use so far has been paperclip. Created by the super cool developers over at ThoughtBot, the paperclip gem is fairly simple and straightforward to use. The reason that I like this gem in particular is that it fits seamlessly into the Rails framework. Files and attachments are treated just like an attribute on an ActiveRecord object, which makes the setup process both easy and intuitive. However, that doesn’t mean that it’s not intimidating at first! Luckily, we’re going to walk through using the paperclip gem together.
How To Start Clipping
paperclip is a gem, the first thing we’ll have to do before we really get started on clipping anything is add it to our
gem "paperclip", "~> 4.3"
bundle install, because it’s what all the cool kids do. This is also a good time to make sure that we have
ImageMagick, which is one of
paperclip’s dependencies. (We can always run
brew install imagemagick to install it if we don’t have it already.)
Now it’s time to get clipping! And take a look at our schema, obvs. For our Bookstore app, we want each of our authors to have a headshot image uploaded and associated with their work. This is going to be super important from a user experience point of view, and it will be something that will be displayed on the show page of any given author.
There are two ways to go about actually adding an attachment to our model; one of them is is a bit easier because it generates a migration for you. But we’ll go ahead and write our own migration to start.
We already have an
Author model and migration. What we need to do is add an
attachment column that’ll handle everything from file uploading to associating a file with a specific
Author object. So, we can just write a migration (
rails g migration AddProfileMediaToAuthors) that will add an
attachment column to our
1 2 3 4 5 6 7 8 9
We’ll call our attachment columns
profile_media to preemptively namespace the different types of attachments that we might have on a single
Author’s page. And just in case you needed a little refresher on the difference between the
down methods, head over here.
Okay, now we’ll run
rake db:migrate, and take a look at our
schema.rb file. Let’s see what happened:
1 2 3 4 5 6 7 8 9 10
Interesting! So our
add_attachment method actually did a lot of things for us, didn’t it? That’s because it’s actually a helper method does a lot of important things that
paperclip relies on:
- It adds a
file_namefor our attachment.
- It adds a
content_typefor our attachments, which will be the mime type of our images.
- It adds the
file_sizeof our attachments.
- It creates a
updated_atcolumn, which is particularly useful since we can order and sort our attachments/images by
Now we need to hook up our database migration with the corresponding model!
Objects With Attached Files
Inside of our
Author class, we need want to add one very important line to our model:
has_attached_file. The important thing about this method is that it needs to correspond to whatever we named our attachment in our migrations from earlier. In our case, we called our attachments
profile_media, so that’s exactly what we’ll use inside of our model as well:
1 2 3 4 5 6 7 8 9
This line sets up a few defaults for us and gives us the option of having a few different sizes for our
default_url can be helpful if we ever want to give our attachment url a default and avoid an instance of
nil. The sizes that we specify here are what we’ll use inside of our views:
But the most important part of making all of this work is, of course, permitting our media to be seen! (I’m looking at you, strong params!). All we need to do is add our attachment name (
profile_media) to our permitted parameters, which is already being used by our controller actions:
1 2 3 4 5 6 7 8 9 10
Pretty simple, right? But these are just the bare bones of
paperclip. We can spice things up a bit, too!
Validating And Customizing Our Clippings
Once we have the gem up and working, it’s super easy to add some bells and whistles and write it to fit our application’s specific standards. The developers at ThoughtBot actually have several different validators that we can implement, including
AttachmentSizeValidator. Personally, however, I prefer the old school helper methods, which function in exactly the same way.
Let’s add a
validates_attachment_content_type to our
Author class, and validate that the content being uploaded is actually an
image. We can do that with a nice regular expression:
1 2 3 4 5 6 7 8 9 10 11
We can also validate the size and presence of our attachment as well:
1 2 3
And what about deleting attachments? Well, because
paperclip is designed to work so well with ActiveRecord, any attachment we create is treated just like an attribute. As the documentation explains,
The intent behind
paperclipwas to keep setup as easy as possible and to treat files as much like other attributes as possible. This means they aren’t saved to their final locations on disk, nor are they deleted if set to nil, until ActiveRecord::Base#save is called. It manages validations based on size and presence, if required.
Since the only way to delete an attachment is by setting the attribute to
nil, there are a few different ways to actually go about deleting attachments. One implementation that I like to use is writing a custom method that checks whether an
Author object has it’s
profile_media attribute equal to
nil before saving it:
1 2 3 4 5 6 7 8 9 10 11
In this structure, I also create an attribute on my
Author object called
remove_profile_media, which will either be
1, based on whether a box on a form has been checked or not. If the button is checked,
remove_profile_media will be set to
1, and I’ll call the
delete_profile_media method in my
If deleting data is something that scares you (or if you’re a fan of the
acts_as_paranoid gem) there’s also another option. You can just preserve your files along with your “soft deleted” models:
1 2 3 4 5
This extra line prevents any data in our
profile_media columns from being completely erased when the model is soft deleted. The good news here is that when the object is restored later on, our images will be too! And that is just as good of a reason as any to celebrate!
paperclipgem really needs only two things to function properly: a
has_attached_file :attachmentin the model and an
attachment_file_namecolumn in the database. The
attachment_content_typeis only required if you’re going to use content type validation.
- If you want to see some
paperclipin action, check out this RailsCast that covers all the basics.
- Want to dive into the
paperclipsource code? Go for it! Perhaps you can start by checking out their well-documented class methods.