Migrations and helpers to move from Paperclip to ActiveStorage
To migrate the application as of April 2020: • leave models/screenshot.rb with “has_attached_file” • bundle exec rake db:migrate • bin/move_paperclip_to_activestorage • set models/screenshot.rb to “has_one_attached”
This commit is contained in:
parent
a0203d32af
commit
8adf682a0b
6 changed files with 239 additions and 0 deletions
18
bin/move_paperclip_to_activestorage
Executable file
18
bin/move_paperclip_to_activestorage
Executable file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!bin/rails runner
|
||||||
|
|
||||||
|
# Move images from Paperclip path to ActiveRecord path
|
||||||
|
|
||||||
|
ActiveStorage::Attachment.find_each do |attachment|
|
||||||
|
name = attachment.name
|
||||||
|
|
||||||
|
source = attachment.record.send(name).path
|
||||||
|
dest_dir = File.join(
|
||||||
|
"storage",
|
||||||
|
attachment.blob.key.first(2),
|
||||||
|
attachment.blob.key.first(4).last(2))
|
||||||
|
dest = File.join(dest_dir, attachment.blob.key)
|
||||||
|
|
||||||
|
FileUtils.mkdir_p(dest_dir)
|
||||||
|
puts "Moving #{source} to #{dest}"
|
||||||
|
FileUtils.cp(source, dest)
|
||||||
|
end
|
||||||
45
config/initializers/new_framework_defaults_6_0.rb
Normal file
45
config/initializers/new_framework_defaults_6_0.rb
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Be sure to restart your server when you modify this file.
|
||||||
|
#
|
||||||
|
# This file contains migration options to ease your Rails 6.0 upgrade.
|
||||||
|
#
|
||||||
|
# Once upgraded flip defaults one by one to migrate to the new default.
|
||||||
|
#
|
||||||
|
# Read the Guide for Upgrading Ruby on Rails for more info on each option.
|
||||||
|
|
||||||
|
# Don't force requests from old versions of IE to be UTF-8 encoded.
|
||||||
|
# Rails.application.config.action_view.default_enforce_utf8 = false
|
||||||
|
|
||||||
|
# Embed purpose and expiry metadata inside signed and encrypted
|
||||||
|
# cookies for increased security.
|
||||||
|
#
|
||||||
|
# This option is not backwards compatible with earlier Rails versions.
|
||||||
|
# It's best enabled when your entire app is migrated and stable on 6.0.
|
||||||
|
# Rails.application.config.action_dispatch.use_cookies_with_metadata = true
|
||||||
|
|
||||||
|
# Change the return value of `ActionDispatch::Response#content_type` to Content-Type header without modification.
|
||||||
|
# Rails.application.config.action_dispatch.return_only_media_type_on_content_type = false
|
||||||
|
|
||||||
|
# Return false instead of self when enqueuing is aborted from a callback.
|
||||||
|
# Rails.application.config.active_job.return_false_on_aborted_enqueue = true
|
||||||
|
|
||||||
|
# Send Active Storage analysis and purge jobs to dedicated queues.
|
||||||
|
# Rails.application.config.active_storage.queues.analysis = :active_storage_analysis
|
||||||
|
# Rails.application.config.active_storage.queues.purge = :active_storage_purge
|
||||||
|
|
||||||
|
# When assigning to a collection of attachments declared via `has_many_attached`, replace existing
|
||||||
|
# attachments instead of appending. Use #attach to add new attachments without replacing existing ones.
|
||||||
|
# Rails.application.config.active_storage.replace_on_assign_to_many = true
|
||||||
|
|
||||||
|
# Use ActionMailer::MailDeliveryJob for sending parameterized and normal mail.
|
||||||
|
#
|
||||||
|
# The default delivery jobs (ActionMailer::Parameterized::DeliveryJob, ActionMailer::DeliveryJob),
|
||||||
|
# will be removed in Rails 6.1. This setting is not backwards compatible with earlier Rails versions.
|
||||||
|
# If you send mail in the background, job workers need to have a copy of
|
||||||
|
# MailDeliveryJob to ensure all delivery jobs are processed properly.
|
||||||
|
# Make sure your entire app is migrated and stable on 6.0 before using this setting.
|
||||||
|
# Rails.application.config.action_mailer.delivery_job = "ActionMailer::MailDeliveryJob"
|
||||||
|
|
||||||
|
# Enable the same cache key to be reused when the object being cached of type
|
||||||
|
# `ActiveRecord::Relation` changes by moving the volatile information (max updated at and count)
|
||||||
|
# of the relation's cache key into the cache version to support recycling cache key.
|
||||||
|
# Rails.application.config.active_record.collection_cache_versioning = true
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
# This migration comes from active_storage (originally 20170806125915)
|
||||||
|
class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
create_table :active_storage_blobs do |t|
|
||||||
|
t.string :key, null: false
|
||||||
|
t.string :filename, null: false
|
||||||
|
t.string :content_type
|
||||||
|
t.text :metadata
|
||||||
|
t.bigint :byte_size, null: false
|
||||||
|
t.string :checksum, null: false
|
||||||
|
t.datetime :created_at, null: false
|
||||||
|
|
||||||
|
t.index [ :key ], unique: true
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table :active_storage_attachments do |t|
|
||||||
|
t.string :name, null: false
|
||||||
|
t.references :record, null: false, polymorphic: true, index: false
|
||||||
|
t.references :blob, null: false
|
||||||
|
|
||||||
|
t.datetime :created_at, null: false
|
||||||
|
|
||||||
|
t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
|
||||||
|
t.foreign_key :active_storage_blobs, column: :blob_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
105
db/migrate/20200413210200_convert_to_active_storage.rb
Normal file
105
db/migrate/20200413210200_convert_to_active_storage.rb
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
# Convert the screenshots from the deprecated Paperclip gem schema
|
||||||
|
# to the new ActiveStorage schema built into Rails 5.2.
|
||||||
|
|
||||||
|
Dir[Rails.root.join("app/models/**/*.rb")].sort.each { |file| require file }
|
||||||
|
|
||||||
|
class ConvertToActiveStorage < ActiveRecord::Migration[5.2]
|
||||||
|
require 'open-uri'
|
||||||
|
|
||||||
|
def up
|
||||||
|
puts "Running up..."
|
||||||
|
# postgres
|
||||||
|
get_blob_id = 'LASTVAL()'
|
||||||
|
# mysql / mariadb
|
||||||
|
# get_blob_id = 'LAST_INSERT_ID()'
|
||||||
|
# sqlite
|
||||||
|
# get_blob_id = 'LAST_INSERT_ROWID()'
|
||||||
|
|
||||||
|
puts "Preparing queries"
|
||||||
|
|
||||||
|
active_storage_blob_statement = 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)
|
||||||
|
SQL
|
||||||
|
|
||||||
|
active_storage_attachment_statement = 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, #{get_blob_id}, $4)
|
||||||
|
SQL
|
||||||
|
|
||||||
|
|
||||||
|
Rails.application.eager_load!
|
||||||
|
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
|
||||||
|
|
||||||
|
if attachments.blank?
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
model.find_each.each do |instance|
|
||||||
|
puts "Model: #{instance}"
|
||||||
|
attachments.each do |attachment|
|
||||||
|
puts "Attachment: #{attachment}"
|
||||||
|
if instance.send(attachment).path.blank?
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
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"),
|
||||||
|
checksum(instance.send(attachment)),
|
||||||
|
instance.updated_at.iso8601
|
||||||
|
])
|
||||||
|
|
||||||
|
ActiveRecord::Base.connection.raw_connection.exec_prepared(
|
||||||
|
'active_storage_attachment_statement', [
|
||||||
|
attachment,
|
||||||
|
model.name,
|
||||||
|
instance.id,
|
||||||
|
instance.updated_at.iso8601,
|
||||||
|
])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
# This migration comes from active_storage (originally 20180723000244)
|
||||||
|
class AddForeignKeyConstraintToActiveStorageAttachmentsForBlobId < ActiveRecord::Migration[6.0]
|
||||||
|
def up
|
||||||
|
return if foreign_key_exists?(:active_storage_attachments, column: :blob_id)
|
||||||
|
|
||||||
|
if table_exists?(:active_storage_blobs)
|
||||||
|
add_foreign_key :active_storage_attachments, :active_storage_blobs, column: :blob_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
34
lib/tasks/paperclip_to_activestorage.rake
Normal file
34
lib/tasks/paperclip_to_activestorage.rake
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
namespace :debshots do
|
||||||
|
desc "Convert images from Paperclip schema to ActiveStorage schema"
|
||||||
|
|
||||||
|
task paperclip_to_activestorage: :environment do
|
||||||
|
klass = Screenshot
|
||||||
|
attachment = 'image'
|
||||||
|
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
|
||||||
|
|
||||||
|
puts "----------"
|
||||||
|
filename = instance.send("#{attachment}_file_name")
|
||||||
|
puts filename
|
||||||
|
|
||||||
|
next if filename.blank?
|
||||||
|
|
||||||
|
id = instance.id
|
||||||
|
id_partition = ("%09d".freeze % id).scan(/\d{3}/).join("/".freeze)
|
||||||
|
path = "storage/#{klass.table_name}/#{attachment.pluralize}/#{id_partition}/original/#{filename}"
|
||||||
|
puts path
|
||||||
|
|
||||||
|
#url = "https://nyc3.digitaloceanspaces.com/gorails/#{klass.table_name}/#{attachment.pluralize}/#{id_partition}/original/#{filename}"
|
||||||
|
|
||||||
|
instance.send(attachment.to_sym).attach(
|
||||||
|
# io: open(url),
|
||||||
|
io: File.open(path),
|
||||||
|
filename: instance.send(name_field),
|
||||||
|
content_type: instance.send(:"#{attachment}_content_type")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Add table
Add a link
Reference in a new issue