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
Since 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 Gemfile
:
gem "paperclip", "~> 4.3"
and then 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 Authors
table:
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 up
and 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_name
for our attachment. - It adds a
content_type
for our attachments, which will be the mime type of our images. - It adds the
file_size
of our attachments. - It creates a
updated_at
column, which is particularly useful since we can order and sort our attachments/images bydatetime
format.
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 profile_media
. The 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:
1
|
|
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 AttachmentContentTypeValidator
, AttachmentPresenceValidator
, and 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
paperclip
was 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 0
or 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 before_save
hook.
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!
tl;dr?
- The
paperclip
gem really needs only two things to function properly: ahas_attached_file :attachment
in the model and anattachment_file_name
column in the database. Theattachment_content_type
is only required if you’re going to use content type validation. - If you want to see some
paperclip
in action, check out this RailsCast that covers all the basics. - Want to dive into the
paperclip
source code? Go for it! Perhaps you can start by checking out their well-documented class methods.