Your Teacher
Chris Oliver
Hi, I'm Chris. I'm the creator of GoRails, Hatchbox.io and Jumpstart. I spend my time creating tutorials and tools to help Ruby on Rails developers build apps better and faster.
About This Episode
Now that the Paperclip gem has been deprecated, it's recommended that you migrate your apps to ActiveStorage
Notes
Resources
db/migrate/convert_to_active_storage.rb
Dir[Rails.root.join("app/models/**/*.rb")].sort.each { |file| require file }
class ConvertToActiveStorage < ActiveRecord::Migration[5.2]
require 'open-uri'
def up
# postgres
# get_blob_id = 'LASTVAL()'
# mysql / mariadb
# get_blob_id = 'LAST_INSERT_ID()'
# sqlite
get_blob_id = 'LAST_INSERT_ROWID()'
active_storage_blob_statement = ActiveRecord::Base.connection.raw_connection.prepare(<<-SQL)
INSERT INTO active_storage_blobs (
key, filename, content_type, metadata, byte_size,
checksum, created_at
) VALUES (?, ?, ?, '{}', ?, ?, ?)
SQL
active_storage_attachment_statement = ActiveRecord::Base.connection.raw_connection.prepare(<<-SQL)
INSERT INTO active_storage_attachments (
name, record_type, record_id, blob_id, created_at
) VALUES (?, ?, ?, #{get_blob_id}, ?)
SQL
models = ActiveRecord::Base.descendants.reject(&:abstract_class?)
transaction do
models.each do |model|
attachments = model.column_names.map do |c|
if c =~ /(.+)_file_name$/
$1
end
end.compact
model.find_each.each do |instance|
attachments.each do |attachment|
if instance.send(attachment).exists?
active_storage_blob_statement.execute(
key(instance, attachment),
instance.send("#{attachment}_file_name"),
instance.send("#{attachment}_content_type"),
instance.send("#{attachment}_file_size"),
checksum(instance.send(attachment)),
instance.updated_at.iso8601
)
active_storage_attachment_statement.
execute(attachment, model.name, instance.id, instance.updated_at.iso8601)
end
end
end
end
end
active_storage_attachment_statement.close
active_storage_blob_statement.close
end
def down
raise ActiveRecord::IrreversibleMigration
end
private
def key(instance, attachment)
# SecureRandom.uuid
# Alternatively:
filename = instance.send("#{attachment}_file_name")
klass = instance.class.table_name
id = instance.id
id_partition = ("%09d".freeze % id).scan(/\d{3}/).join("/".freeze)
"#{klass}/#{attachment.pluralize}/#{id_partition}/original/#{filename}"
end
def checksum(attachment)
# local files stored on disk:
#url = attachment.path
#Digest::MD5.base64digest(File.read(url))
# remote files stored on another person's computer:
url = attachment.url
Digest::MD5.base64digest(Net::HTTP.get(URI(url)))
end
end
lib/tasks/paperclip.rake
namespace :paperclip do
task migrate: :environment do
klass = User
attachment = 'avatar'
name_field = :"#{attachment}_file_name"
klass.where.not(name_field => nil).find_each do |instance|
# This step helps us catch any attachments we might have uploaded that
# don't have an explicit file extension in the filename
filename = instance.send("#{attachment}_file_name")
next if filename.blank?
id = instance.id
id_partition = ("%09d".freeze % id).scan(/\d{3}/).join("/".freeze)
url = "https://nyc3.digitaloceanspaces.com/gorails/#{klass.table_name}/#{attachment.pluralize}/#{id_partition}/original/#{filename}"
instance.send(attachment.to_sym).attach(
io: open(url),
filename: instance.send(name_field),
content_type: instance.send(:"#{attachment}_content_type")
)
end
end
end