2019-06-24 21:17:42 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
class ConvertToActiveStorage < ActiveRecord::Migration[5.2]
|
|
|
|
require 'open-uri'
|
|
|
|
|
|
|
|
ID_PARTITION_LIMIT = 1_000_000_000
|
|
|
|
DIGEST = OpenSSL::Digest.const_get('SHA1').new
|
|
|
|
|
|
|
|
def up
|
|
|
|
ActiveRecord::Base.connection.raw_connection.prepare('active_storage_blob_statement', <<-SQL)
|
|
|
|
INSERT INTO active_storage_blobs (
|
|
|
|
key, filename, content_type, metadata, byte_size, checksum, created_at
|
|
|
|
) VALUES ($1, $2, $3, '{}', $4, $5, $6)
|
|
|
|
RETURNING id;
|
|
|
|
SQL
|
|
|
|
|
|
|
|
ActiveRecord::Base.connection.raw_connection.prepare('active_storage_attachment_statement', <<-SQL)
|
|
|
|
INSERT INTO active_storage_attachments (
|
|
|
|
name, record_type, record_id, blob_id, created_at
|
|
|
|
) VALUES ($1, $2, $3, $4, $5)
|
|
|
|
SQL
|
|
|
|
|
|
|
|
Rails.application.eager_load!
|
|
|
|
models = ApplicationRecord.descendants.reject(&:abstract_class?)
|
|
|
|
|
|
|
|
transaction do
|
|
|
|
models.each do |model|
|
|
|
|
attachments = model.column_names.map do |c|
|
|
|
|
$1 if c =~ /(.+)_file_name$/
|
|
|
|
end.compact
|
|
|
|
|
|
|
|
next if attachments.blank?
|
|
|
|
|
|
|
|
model.find_each.each do |instance|
|
|
|
|
attachments.each do |attachment|
|
|
|
|
next if instance.__send__("#{attachment}_file_name").blank?
|
|
|
|
|
|
|
|
res = ActiveRecord::Base.connection.raw_connection.exec_prepared(
|
|
|
|
'active_storage_blob_statement', [
|
|
|
|
key(instance, attachment),
|
|
|
|
instance.__send__("#{attachment}_file_name"),
|
|
|
|
instance.__send__("#{attachment}_content_type"),
|
|
|
|
instance.__send__("#{attachment}_file_size") || 0,
|
2019-06-28 14:17:09 +08:00
|
|
|
checksum(attachment),
|
2019-06-24 21:17:42 +08:00
|
|
|
instance.updated_at.iso8601
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
|
|
|
ActiveRecord::Base.connection.raw_connection.exec_prepared(
|
|
|
|
'active_storage_attachment_statement', [
|
|
|
|
attachment,
|
|
|
|
model.name,
|
|
|
|
instance.id,
|
|
|
|
res[0]['id'],
|
|
|
|
instance.updated_at.iso8601
|
|
|
|
]
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def down
|
2019-08-08 20:03:15 +08:00
|
|
|
raise ActiveRecord::IrreversibleMigration
|
2019-06-24 21:17:42 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
ID_PARTITION_LIMIT = 1_000_000_000
|
|
|
|
DIGEST = OpenSSL::Digest.const_get('SHA1').new
|
|
|
|
|
|
|
|
def id_partition(id)
|
|
|
|
if id < ID_PARTITION_LIMIT
|
|
|
|
format('%09d', id).scan(/\d{3}/).join('/')
|
|
|
|
else
|
|
|
|
format('%012d', id).scan(/\d{3}/).join('/')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def hash_data(instance, attachment)
|
|
|
|
"#{instance.class.to_s.underscore.pluralize}/#{attachment.pluralize}/#{instance.id}/original"
|
|
|
|
end
|
|
|
|
|
|
|
|
def interpolate(pattern, instance, attachment)
|
|
|
|
path = pattern
|
|
|
|
path = path.gsub(':class', instance.class.to_s.underscore.pluralize)
|
|
|
|
path = path.gsub(':attachment', attachment.pluralize)
|
|
|
|
path = path.gsub(':id_partition', id_partition(instance.id))
|
|
|
|
path = path.gsub(':hash', OpenSSL::HMAC.hexdigest(DIGEST,
|
|
|
|
ENV['PAPERCLIP_HASH_SECRET'],
|
|
|
|
hash_data(instance, attachment)))
|
|
|
|
path.gsub(':filename', instance.__send__("#{attachment}_file_name"))
|
|
|
|
end
|
|
|
|
|
|
|
|
def key(instance, attachment)
|
|
|
|
# SecureRandom.uuid
|
|
|
|
# Alternatively:
|
2019-06-28 14:17:09 +08:00
|
|
|
if ENV['PAPERCLIP_STORAGE'] == 's3'
|
|
|
|
interpolate(':class/:attachment/:id_partition/:hash/original/:filename', instance, attachment)
|
|
|
|
else
|
2019-07-05 22:56:05 +08:00
|
|
|
instance.class.generate_unique_secure_token
|
|
|
|
# File.join('storage', key.first(2), key.first(4).last(2))
|
2019-06-28 14:17:09 +08:00
|
|
|
end
|
2019-06-24 21:17:42 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def checksum(_attachment)
|
|
|
|
'dummy'
|
|
|
|
# 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
|