Migrating from Shrine to ActiveStorage
I recently worked on a migration from Shrine to ActiveStorage. The combination of Shrine and Uppy worked for a while, but using just ActiveStorage and a default file field turns out cleaner and more maintainable.
This post lays out the few steps involved.
Installing ActiveStorage
The first step was to install ActiveStorage.
bin/rails active_storage:install
bin/rails db:migrate
Model changes
The Photo
model was stripped of the Shrine functionality by removing:
include ImageUploader::Attachment(:image)
In return it got:
has_one_attached :image
Changes in the views
All occurrences of image_url()
where replaced with image.variant()
.
# before
image_tag @photo.image_url(:small)
# after
image_tag @photo.image.variant(:small)
The variants are defined in the model like this:
has_one_attached :image do |attachable|
attachable.variant :small, resize_to_limit: [300, 300]
attachable.variant :medium, resize_to_limit: [600, 600]
# ... etcetera
end
Migrating the image files
Shrine saves meta information of an uploaded file in the image_data
field. By using this information the file can be opened. It’s then passed to the ActiveStorage attach
method that takes care of the actual upload (and post-processing).
The job was implemented as follows. By checking for already attached images the job is idempotent and can be run multiple times in case of any unforeseen exceptions.
class ShrineMigrationJob < ActiveJob::Base
def perform
Photo.where.not(image_data: nil).find_each do |photo|
unless photo.image.attached?
data = JSON.parse(photo.image_data)
path = 'storage/'+data['id']
filename = data['metadata']['filename']
file = File.open(path)
file.rewind
photo.image.attach(io: file, filename:)
file.close
end
end
end
end
All with all the migration from Shrine to ActiveStorage turned out to be a walk in the park.