Level Up Your Rails Forms with Active Storage

Author's Note: This post is related to the Rails Fitness application that I started earlier this summer. I wrote a multi-post series about building this app and you can read the prelude to Part 1 here .

While reviewing the code, I decided I wanted a user to be able to upload images to go with a routine and/ or its exercises. To achieve this goal, I decided to configure image uploads and attachments through Active Storage.

active_storage.png

What Is Active Storage?

Active Storage is a component of Rails that lets a user upload and attach one or more files to an Active Record object.

More specifically, Active Storage lets you:

  • Transform image uploads via ImageMagick
  • Render non-image files such as PDF and video

Setup

The only real requirement for setting up Active Storage properly is for the version of Rails to be 5.2 or higher. Other than that, the setup process is pretty simple.

1. Set up the migrations

Run bundle exec rails active_storage:install, then bundle exec rails db:migrate and take a look at the following code added to db/scheme.rb:

  create_table "active_storage_attachments", force: :cascade do |t|
    t.string "name", null: false
    t.string "record_type", null: false
    t.integer "record_id", null: false
    t.integer "blob_id", null: false
    t.datetime "created_at", null: false
    t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
    t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
  end

  create_table "active_storage_blobs", force: :cascade do |t|
    t.string "key", null: false
    t.string "filename", null: false
    t.string "content_type"
    t.text "metadata"
    t.bigint "byte_size", null: false
    t.string "checksum", null: false
    t.datetime "created_at", null: false
    t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
  end

Two new tables are created afterward. The first, active_storage_attachments, according the Rails Guides article on Active Storage, is a "polymorphic join table that stores your model's class name". It is the connection between the model at hand and the other table. activestorage_blob contains all of the key metadata pertaining to the uploaded file. The "blob" stores information such as filename, content_type, and metadata.

2. Attach the file(s) to a model

Now that we've migrated the necessary tables related to Active Storage, we need to set up the mappings between a given record and the file(s) that might be attached to it. There are two distinct ways to set up attachments in models

With has_one_attached, we are establishing a one-to-one connection between an Active Record object and a single file (represented as an instance of ActiveStorage::Attached::One)

class Exercise < ApplicationRecord
  has_many :routine_exercises, dependent: :destroy
  has_many :routines, through: :routine_exercises
  has_one_attached :image

  def exercises_attributes=(exercise)
    if exercise[:name] != "" && exercise[:exercise_type] != "" && exercise[:description] != ""
      new_exercise = self.exercises.build
      new_exercise.name = exercise[:name]
      new_exercise.exercise_type = exercise[:exercise_type]
      new_exercise.description = exercise[:description]
      new_exercise.image.attach(exercise[:image]) # ^^^
    end
  end
end

^^^If there are custom attribute setters (like in the snippet above), then the file(s) needs to be attached to new instance being built.

With has_many_attached, we are setting up a many-to-many connection between the Active Record object and multiple files (represented altogether as an instance of ActiveStorage::Attached::Many)

3. Incorporate Active Storage in controllers

If you're creating and updating records with strong params, then the Active Storage instance variable must be added in the list of permitted params attributes.

class ExercisesController extends ApplicationController 
  . . .

  private 
  # in this example, `image` is the AS variable we're adding to the strong params
  def exercise_params  
    params.require(:exercise).permit(:id, :name, :exercise_type, :description, :image, 
   :routine_exercises_attributes => [:id, :routine_id, :sets, :reps])
  end
end

If you're not using strong params, then the process of passing the Active Storage variable in the controller is taken care of. (Make sure it's attached, though! --> image.attached?)

4. Add a file_field in the new/edit views

You'll then want to incorporate uploading images via Active Storage in the actual forms. Whether the form is inside the actual views or separated into a partial, you'll want to add code similar to this:

# app/views/exercises/_form.erb
# inside the form

form.file_field(:image)

See the code for an actual example.

When the server is started, the rendered form will look something like this:

Screen Shot 2020-08-18 at 3.37.16 PM.png

Do you notice the new button for uploading an image? We've successfully set up Active Storage!

Usage

After setting up, actually using Active Storage is a breeze. If we were to fill out a routine/ edit form like normal (except we're now uploading images) and submit, the new routine looks like this:

Screen Shot 2020-08-18 at 3.58.47 PM.png

Cost/ Benefit Analysis

The Pros: Active Storage is the official Rails tool used for attaching files to Active Record models.

With the help of the special active_storage_blobs and active_storage_attachments tables, we can attach a file to a model without adding a separate column to the model's table itself.

The Cons: As of right now, I haven't really found any cons for Active Storage!

Conclusion

If you're looking fora quick way to upload images to go with your Active Record models in your Rails projects, this is the solution for you. Active Storage is easy to set up and implement. It's a tool I'll definitely keep close to me.

Happy Coding! Brandon

Resources

Active Storage Overview (Rails Guides)

Pros and Cons of Active Storage (StackOverflow thread)

Rails API

No Comments Yet