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.
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:
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:
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)