Scott's Recipes Logo

Working with Rails 7 and ActiveStorage

Every developer has one of those platform technologies that never seems to work well for them – and for me, for Rails, it has always been ActiveStorage, specifically with respect to images. I’ve been trying to use ActiveStorage this way since its initial release and I’ve just had crappy, crappy luck with it.

I’ve now started a new side project, imgarr, which depends entirely on ActiveStorage so I guess this is the year where I get serious and really learn ActiveStorage. This is what I refer to as the “going all in approach to learning something”.

Note: imgarr is currently in development and, like a platypus doesn’t so much.

What is ActiveStorage

ActiveStorage is a core Rails library which:

Code Examples

Let’s say that you have an image model in ActiveRecord which looks like this:

# == Schema Information
#
# Table name: images
#
#  id              :bigint           not null, primary key
#  date_created_at :date             not null
#  dollar_cost     :decimal(8, 2)
#  filename        :string           not null
#  filesize        :bigint
#  image_type      :string
#  ip_address      :string
#  name            :string
#  short_name      :string
#  user_agent      :string
#  view_count      :bigint
#  created_at      :datetime         not null
#  updated_at      :datetime         not null
#  user_id         :bigint           not null
#
# Indexes
#
#  index_images_on_date_created_at_and_id  (date_created_at,id)
#  index_images_on_short_name              (short_name) UNIQUE
#  index_images_on_user_id                 (user_id)
#  index_images_on_user_id_and_id          (user_id,id)
#
# Foreign Keys
#
#  fk_rails_...  (user_id => users.id)
#
class Image < ApplicationRecord
  belongs_to :user
  
  validates :user_id, presence: true
  validates :date_created_at, presence: true
  validates :short_name, uniqueness: true

  scope :ordered, -> { order("id DESC") }
  
  has_one_attached :main_image do |attachable|
    attachable.variant :thumbnail, resize: "100x100"
    attachable.variant :medium, resize: "300x300"
  end
end

Let’s ignore the block with attachable and start with the commands to create a square variant of 250x250 pixels:

image.main_image.variant(resize_to_fit: [250, 250])

That’s going to create a square image of 250 x 250 pixels. Now the first thing you are likely to notice is that image – if it was a phone picture taken in vertical orientation is that the aspect ratio is going to be off. The reason is that a transformation of 1:1 was applied to an image with a non 1:1 aspect ratio. The solution is to specify nil for one of the orientations. Let’s try this again with:

image.main_image.variant(resize_to_fit: [250, nil])

Now you’re going to get an image that is 250 pixels wide and a corresponding height based on the image’s underlying aspect ratio. That’s tremendously cool. Here’s the opposite orientation specified as nil:

image.main_image.variant(resize_to_fit: [nil, 250])

Let’s pull out Rails console and do some introspection on the methods available to the above code:

(image.main_image.variant(resize_to_fit: [250, nil]).methods - Object.methods).sort
[
    [0] :blob,
    [1] :download,
    [2] :image,
    [3] :key,
    [4] :process,
    [5] :processed,
    [6] :processed?,
    [7] :service,
    [8] :url,
    [9] :variation
]

Each of these methods is illustrated below.

blob

This gives you access to the raw blob of data represented by the ActiveStorage object:

image.main_image.variant(resize_to_fit: [250, nil]).blob
#<ActiveStorage::Blob:0x000000010e15d060> {
              :id => 26,
             :key => "mp6439g1huwfonfmkodbz9q5b2as",
        :filename => #<ActiveStorage::Filename:0x000000010e014ff0 @filename="IMG_3762.jpg">,
    :content_type => "image/jpeg",
        :metadata => {
        "identified" => true
    },
    :service_name => "local",
       :byte_size => 4227649,
        :checksum => "MjQVnFP8ANWqc3XqiZnAfA==",
      :created_at => Mon, 13 Feb 2023 09:37:16.525802000 UTC +00:00
}

download

image

key

process

processed

processed?

service

url

variation

image.main_image.variant(resize_to_fit: [250, nil]) ActiveStorageAttachment Load (0.5ms) SELECT “active_storage_attachments”.* FROM “active_storage_attachments” WHERE “active_storage_attachments”.”record_id” = $1 AND “active_storage_attachments”.”record_type” = $2 AND “active_storage_attachments”.”name” = $3 LIMIT $4 [[“record_id”, 24], [“record_type”, “Image”], [“name”, “main_image”], [“LIMIT”, 1]] ActiveStorageBlob Load (0.6ms) SELECT “active_storage_blobs”.* FROM “active_storage_blobs” WHERE “active_storage_blobs”.”id” = $1 LIMIT $2 [[“id”, 26], [“LIMIT”, 1]] #<ActiveStorageVariantWithRecord:0x000000010e1c57c8 @blob=#<ActiveStorageBlob id: 26, key: “mp6439g1huwfonfmkodbz9q5b2as”, filename: “IMG_3762.jpg”, content_type: “image/jpeg”, metadata: {“identified”=>true}, service_name: “local”, byte_size: 4227649, checksum: “MjQVnFP8ANWqc3XqiZnAfA”, created_at: “2023-02-13 09:37:16.525802000 +0000”>, @variation=#<ActiveStorageVariation:0x000000010e1c5930 @transformations={:format=>”jpg”, :resize_to_fit=>[250, nil]}» irb(main):003:0> (image.main_image.variant(resize_to_fit: [250, nil]).methods - Object.methods).sort [ [0] :blob, [1] :download, [2] :image, [3] :key, [4] :process, [5] :processed, [6] :processed?, [7] :service, [8] :url, [9] :variation ]