diff --git a/bin/move_paperclip_to_activestorage b/bin/move_paperclip_to_activestorage new file mode 100755 index 0000000..875213c --- /dev/null +++ b/bin/move_paperclip_to_activestorage @@ -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 diff --git a/config/initializers/new_framework_defaults_6_0.rb b/config/initializers/new_framework_defaults_6_0.rb new file mode 100644 index 0000000..92240ef --- /dev/null +++ b/config/initializers/new_framework_defaults_6_0.rb @@ -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 diff --git a/db/migrate/20200413194248_create_active_storage_tables.active_storage.rb b/db/migrate/20200413194248_create_active_storage_tables.active_storage.rb new file mode 100644 index 0000000..0b2ce25 --- /dev/null +++ b/db/migrate/20200413194248_create_active_storage_tables.active_storage.rb @@ -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 diff --git a/db/migrate/20200413210200_convert_to_active_storage.rb b/db/migrate/20200413210200_convert_to_active_storage.rb new file mode 100644 index 0000000..d50f95d --- /dev/null +++ b/db/migrate/20200413210200_convert_to_active_storage.rb @@ -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 diff --git a/db/migrate/20200414002409_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.active_storage.rb b/db/migrate/20200414002409_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.active_storage.rb new file mode 100644 index 0000000..ff5d72c --- /dev/null +++ b/db/migrate/20200414002409_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.active_storage.rb @@ -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 diff --git a/lib/tasks/paperclip_to_activestorage.rake b/lib/tasks/paperclip_to_activestorage.rake new file mode 100644 index 0000000..8059be1 --- /dev/null +++ b/lib/tasks/paperclip_to_activestorage.rake @@ -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