Words and Code

One writer’s journey from words to code.

Clipping Images for Rails: Using Paperclip

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
class AddProfileMediaToAuthors < ActiveRecord::Migration
  def self.up
    add_attachment :authors, :profile_media
  end

  def self.down
    remove_attachment :authors, :profile_media
  end
end

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
create_table "authors", force: :cascade do |t|
  t.string   "firstname"
  t.string   "lastname"
  t.datetime "created_at",         null: false
  t.datetime "updated_at",         null: false
  t.string   "profile_media_file_name"
  t.string   "profile_media_content_type"
  t.integer  "profile_media_file_size"
  t.datetime "profile_media_updated_at"
end

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:

  1. It adds a file_name for our attachment.
  2. It adds a content_type for our attachments, which will be the mime type of our images.
  3. It adds the file_size of our attachments.
  4. It creates a updated_at column, which is particularly useful since we can order and sort our attachments/images by datetime 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
class Author < ActiveRecord::Base
  has_attached_file :profile_media,
      styles: {
          large: "500x500",
          medium: "300x300",
          thumb: "100x100"
      },
      default_url: "/images/:style/missing_profile_media.png"
end

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
= link_to image_tag(author.profile_media.url(:thumb)), author.profile.url

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
class AuthorsController < ApplicationController
  def create
      author = Author.create(author_params)
  end

  private
    def author_params
      params.require(:author).permit(:firstname, :lastname, :profile_media)
    end
end

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
class Author < ActiveRecord::Base
  has_attached_file :profile_media,
      styles: {
          large: "500x500",
          medium: "300x300",
          thumb: "100x100"
      },
          default_url: "/images/:style/missing_profile_media.png"

      validates_attachment_content_type :profile_media, content_type: /\Aimage\/.*\Z/
end

We can also validate the size and presence of our attachment as well:

1
2
3
validates :profile_image, attachment_presence: :true

validates_attachment_size :profile_media, size: { in: 0..100.kilobytes }

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
class Author < ActiveRecord::Base
  before_save :delete_profile_media,
      if: -> { remove_profile_media == '1' && !profile_media_updated_at_changed? }
  
  attr_accessor :remove_profile_media

  private
      def delete_profile_media
          self.profile_media = nil
      end
end

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
class Author < ActiveRecord::Base
  has_attached_file :profile_media, {
      preserve_files: "true",
  }
end

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: a has_attached_file :attachment in the model and an attachment_file_name column in the database. The attachment_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.