4 Comments

Uploading Files in Rails Using Paperclip and Active Admin

I recently came across a situation where I needed to be able to upload a file to a Rails server with Active Admin. I did a quick search on Google and found this post by Job, a fellow Atom.

Our use cases were a little bit different, though. He was storing the file contents directly in the database, whereas I needed to be able to uplaod a firmware image file to the server’s filesystem, parse the file name, and perform some validations on the file. I decided to use the Paperclip gem to manage the file processing and storage. Using Job’s advice on Active Admin file uploads, I expanded his example to incorporate Paperclip.

What is Paperclip?

Paperclip, as its name suggests, is a file attachment library for ActiveRecord. It is designed to treat files much like other attributes, and it provides a whole slew of built-in extensions (e.g. validations, pre/post processing callback hooks, security enhancements, etc.). The setup is very simple, and getting the basics up and running takes only a few minutes. For the sake of this example, we will set up the ActiveRecord model, create a migration, and get rolling with the Active Admin file upload.

Install the Paperclip Gem

Installing the gem is easy. Just add

gem 'paperclip'

to your Gemfile and run

bundle install

Create a Migration

If you have an existing model where you want to add Paperclip support, then you simply need to create a migration. In this case, I already had a table and a corresponding model for my firmware image, so I just needed to add a migration to add the columns that Paperclip requires. The easiest way to do this is with the Rails migration generator:

rails generate paperclip firmware image

This will automatically create a migration file that looks like this:


class AddImageColumnsToFirmware < ActiveRecord::Migration
  def up
    add_attachment :firmware, :image
  end

  def down
    remove_attachment :firmware, :image
  end
end

The add_attachment helper will automatically create the following columns in your table:

  • image_file_name
  • image_content_type
  • image_file_size
  • image_updated_at

Technically, only the *_file_name column is required for Paperclip to operate, so you could throw away the others if you don’t need them.

Paperclip Your Models

The true beauty of Paperclip is how well it integrates with ActiveModel. To add Paperclip support to your model, start by adding the following line to your model class:


class Firmware < ActiveRecord::Base
  has_attached_file :image
  # more stuff to come
end

With that one line, Paperclip is now integrated with your Rails model, and it will automatically handle your file “attachments” just like any other Rails attribute! There are, of course, plenty of other options that you can add to the has_attached_file attribute (e.g. specifying a file path, style type, etc.), but I won’t go into that right now. For our purposes, the defaults should be just fine.

Validations!

Paperclip also makes it really easy to perform powerful validations on the file. If any validation fails, the file will not be saved to disk and the associon will be rolled back, just like any other ActiveRecord validation. There are built-in helpers to validate attachment presence, size, content type, and file name.

In our case, we really just need to validate the file name to ensure that it had the proper format and also that it was a unique file name. The following validation did the trick:


validates_attachment_file_name :image, :matches => [/_\d+_\d+_\d+\.bin$/]
validates_uniqueness_of :image_file_name # this is a standard ActiveRecord validator

Additional Processing

I also wanted to be able to grab the firmware version out of the file name. The best way to do this is with a before_post_process callback in the model, like this:


before_post_process :parse_file_name

def parse_file_name
  version_match = /_(?\d+)_(?\d+)_(?\d+)\.bin$/.match(image_file_name)
  if version_match.present? and version_match[:major] and version_match[:minor] and version_match[:patch]
    self.major_version = version_match[:major]
    self.minor_version = version_match[:minor]
    self.patch_version = version_match[:patch]
  end
end
  

Before the file is saved, but after the filename is validated (so we can be sure it has the proper formatting), we extract the major, minor, and patch numbers from the filename and save them in our database.

Configure Active Admin for File Upload

Now that Paperclip is all set up and wired into our Rails model, we need to actually set up the file upload piece in Active Admin. I won’t go into much detail, since I relied on Job’s post as a reference. Basically, all we need to do is:

  1. Define the Index page contents

    Enumerate which columns will be displayed. If any special decoration is required, such as customized column title, sort properties, or row formatting, they can be specified easily. For example, I wanted a link to download the firmware image, so I added a link_to in the “Image” column. Note that the file path is stored in the image.url attribute.

  2. Specify which parameters may be changed

    Use the permit_params method to whitelist any attributes.

  3. Create the upload form

    Use the f.input :image, as: :file syntax to automatically create a file upload field in Active Admin.

The code snippet below is the Active Admin page, which allows the user to create, view, edit, and delete a firmware image.


ActiveAdmin.register Firmware do
  permit_params :image
  
  index do
    selectable_column
    id_column
    column 'Image', sortable: :image_file_name do |firmware| link_to firmware.image_file_name, firmware.image.url end
    column :image_file_size, sortable: :image_file_size do |firmware| "#{firmware.image_file_size / 1024} KB" end
    column "Version" do |firmware| "#{firmware.major_version}.#{firmware.minor_version}.#{firmware.patch_version}" end
    column :created_at
    actions
  end

  form do |f|
    f.inputs "Upload" do
      f.input :image, required: true, as: :file
    end
    f.actions
  end
end

And that’s it! We now have a fully functional file-upload implementation using Active Admin backed by Paperclip. It’s really quite simple, and it only took a few minutes to get it set up and running.