Historically I've been using the Refile gem for attaching files to my Rails models. But the gem hasn't been maintained for a long time and since Rails 5.2 ActiveStorage has been my preferred way to go.
These days I've been working on upgrading some of our older Rails 5.1 applications to Rails 6.0 and now I started seeing deprecation warnings on Refile, so It's time to say goodbye to Refile and hello to ActiveStorage.
Here I'll show you how I migrated my users avatar images from Refile to ActiveStorage.
The basics
On my User model I've replaced Refile's attachment :avatar
with ActiveStorage's has_one_attached :avatar
In my Users#form
view I've replaced the Refile field:
<%= f.attachment_field :avatar %>
with a regular file_field
for ActiveStorage:
<%= f.file_field :image %>
Finally in my views when I wanna show the avatar image I moved from Refile's:
<%= image_tag attachment_url(user, :avatar, :fill, 75, 75) %>
to ActiveStorage:
<%= image_tag user.avatar.variant(resize_to_limit: [75, 75]) %>
You should check out the Rails guides for more options on ActiveStorage.
The migration
Great, with my frontend ready to handle ActiveStorage let's move on to the actual file migrations.
I've been using AWS S3 for hosting my Refile files.
Refile relies on the AWS SDK v2. Before I ran this migration I've removed the Refile gems including the AWS SDK v2 with the following gems for ActiveStorage in my Gemfile:
gem "aws-sdk-s3", require: false
gem 'image_processing'
Historically my User model just had a single Refile database field: avatar_id
(string).
I wish I had used Refile's content_type detection as well, but I didn't, so in my migration I decided to use ImageMagick to detect the file type of my users images.
I created the following migration file in db/migrate/moving_from_refile_to_active_storage.rb
:
require 'mini_magick' # included by the image_processing gem
require 'aws-sdk-s3' # included by the aws-sdk-s3 gem
class User < ActiveRecord::Base
has_one_attached :avatar
end
class MovingFromRefileToActiveStorage < ActiveRecord::Migration[6.0]
def up
puts 'Connecting to AWS S3'
s3_client = Aws::S3::Client.new(
access_key_id: ENV['AWS_S3_ACCESS_KEY'],
secret_access_key: ENV['AWS_S3_SECRET'],
region: ENV['AWS_S3_REGION']
)
puts 'Migrating user avatar images from Refile to ActiveStorage'
User.where.not(avatar_id: nil).find_each do |user|
tmp_file = Tempfile.new
# Read S3 object to our tmp_file
s3_client.get_object(
response_target: tmp_file.path,
bucket: ENV['AWS_S3_BUCKET'],
key: "store/#{user.avatar_id}"
)
# Find content_type of S3 file using ImageMagick
# If you've been smart enough to save :avatar_content_type with Refile, you can use this value instead
content_type = MiniMagick::Image.new(tmp_file.path).mime_type
# Attach tmp file to our User as an ActiveStorage attachment
user.avatar.attach(
io: tmp_file,
filename: "avatar.#{content_type.split('/').last}",
content_type: content_type
)
if user.avatar.attached?
user.save # Save our changes to the user
puts "- migrated #{user.try(:name)}'s avatar image."
else
puts "- \e[31mFailed to migrate the avatar image for user ##{user.id} with Refile id #{user.avatar_id}\e[0m"
end
tmp_file.close
end
# Now remove the actual Refile column
remove_column :users, :avatar_id, :string
# If you've created other Refile fields like *_content_type, you can safely remove those as well
# remove_column :users, :avatar_content_type, :string
end
def down
raise ActiveRecord::IrreversibleMigration
end
end
Just to be safe I chose not to delete Refile files from my S3 bucket in this migration file. I could of course have done so, but I choose to delete the storage
and cache
folders in my bucket manually instead.
That's it. After having run this migration all my users avatar images are now handled through ActiveStorage.
Good bye Refile.
Top comments (2)
Thank you for sharing, my final dynamic file:
Thanks for sharing this! Super useful technique!