Skip to content

Commit

Permalink
Add upgrader for active storage
Browse files Browse the repository at this point in the history
  • Loading branch information
tvdeyen committed Dec 3, 2024
1 parent 853d5ad commit 38b4972
Show file tree
Hide file tree
Showing 19 changed files with 368 additions and 28 deletions.
13 changes: 13 additions & 0 deletions app/models/alchemy/attachment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ class Attachment < BaseRecord
include Alchemy::Taggable
include Alchemy::TouchElements

attr_readonly(
:legacy_image_file_name,
:legacy_image_file_size,
:legacy_image_file_uid
)

deprecate(
:legacy_image_file_name,
:legacy_image_file_size,
:legacy_image_file_uid,
deprecator: Alchemy::Deprecation
)

# Use ActiveStorage file attachments
has_one_attached :file, service: :alchemy_cms

Expand Down
19 changes: 19 additions & 0 deletions app/models/alchemy/picture.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,25 @@ def self.preprocessor_class=(klass)
@_preprocessor_class = klass
end

attr_readonly(
:legacy_image_file_format,
:legacy_image_file_height,
:legacy_image_file_name,
:legacy_image_file_size,
:legacy_image_file_uid,
:legacy_image_file_width
)

deprecate(
:legacy_image_file_format,
:legacy_image_file_height,
:legacy_image_file_name,
:legacy_image_file_size,
:legacy_image_file_uid,
:legacy_image_file_width,
deprecator: Alchemy::Deprecation
)

# Use ActiveStorage image processing
has_one_attached :image_file, service: :alchemy_cms do |attachable|
# Only works in Rails 7.1
Expand Down
13 changes: 13 additions & 0 deletions db/migrate/20240611080918_rename_alchemy_attachment_file.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class RenameAlchemyAttachmentFile < ActiveRecord::Migration[7.0]
COLUMNS = %i[
file_name
file_size
file_uid
]

def change
COLUMNS.each do |column|
rename_column :alchemy_attachments, column, :"legacy_#{column}"
end
end
end
16 changes: 16 additions & 0 deletions db/migrate/20240611080918_rename_alchemy_picture_image_file.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class RenameAlchemyPictureImageFile < ActiveRecord::Migration[7.0]
COLUMNS = %i[
image_file_format
image_file_height
image_file_name
image_file_size
image_file_uid
image_file_width
]

def change
COLUMNS.each do |column|
rename_column :alchemy_pictures, column, :"legacy_#{column}"
end
end
end
1 change: 0 additions & 1 deletion lib/alchemy/test_support/factories/attachment_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,5 @@
end

name { "image" }
file_name { "image.png" }
end
end
108 changes: 108 additions & 0 deletions lib/alchemy/upgrader/eight_zero.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
require "alchemy/shell"
require "alchemy/upgrader/tasks/active_storage_migration"
require "benchmark"
require "dragonfly"
require "dragonfly_svg"
require "fileutils"
require "thor"

module Alchemy
class Upgrader::EightZero < Upgrader
include Thor::Base
include Thor::Actions

class << self
def install_active_storage
Rake::Task["active_storage:install"].invoke
Rake::Task["db:migrate"].invoke

text = <<-YAML.strip_heredoc
alchemy_cms:
service: Disk
root: <%= Rails.root.join("storage") %>
YAML

storage_yml = Rails.application.root.join("config/storage.yml")
if File.exist?(storage_yml)
task.insert_into_file(storage_yml, text)
else
task.create_file(storage_yml, text)
end
end

def prepare_dragonfly_config
task.prepend_to_file "config/initializers/dragonfly.rb", <<~RUBY
require "dragonfly"
require "dragonfly_svg"
RUBY
end

def migrate_pictures_to_active_storage
Dragonfly.logger = Rails.logger

Alchemy::Picture.class_eval do
extend Dragonfly::Model
dragonfly_accessor :legacy_image_file, app: :alchemy_pictures
end

pictures_without_as_attachment = Alchemy::Picture.where.missing(:image_file_attachment)
count = pictures_without_as_attachment.count
if count > 0
log "Migrating #{count} Dragonfly image file(s) to ActiveStorage."
realtime = Benchmark.realtime do
pictures_without_as_attachment.find_each do |picture|
Alchemy::Upgrader::Tasks::ActiveStorageMigration.migrate_picture(picture)
print "."
end
end
puts "\nDone in #{realtime.round(2)}s!"
else
log "No Dragonfly image files for migration found.", :skip
end
end

def migrate_attachments_to_active_storage
Dragonfly.logger = Rails.logger

Alchemy::Attachment.class_eval do
extend Dragonfly::Model
dragonfly_accessor :legacy_file, app: :alchemy_attachments
end

attachments_without_as_attachment = Alchemy::Attachment.where.missing(:file_attachment)
count = attachments_without_as_attachment.count
if count > 0
log "Migrating #{count} Dragonfly attachment file(s) to ActiveStorage."
realtime = Benchmark.realtime do
attachments_without_as_attachment.find_each do |attachment|
Alchemy::Upgrader::Tasks::ActiveStorageMigration.migrate_attachment(attachment)
print "."
end
end
puts "\nDone in #{realtime.round(2)}s!"
else
log "No Dragonfly attachment files for migration found.", :skip
end
end

def remove_dragonfly_todo
todo <<-TXT.strip_heredoc
Please check if all pictures and attachments are migrated to ActiveStorage.
If so, you can remove the Dragonfly gem, its configuration and storage by running:
rm config/initializers/dragonfly.rb
rm -rf uploads
TXT
end

private

def task
@_task || new
end
end
end
end
100 changes: 100 additions & 0 deletions lib/alchemy/upgrader/tasks/active_storage_migration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
require "active_storage/service"
require "active_storage/service/disk_service"

module Alchemy
class Upgrader
module Tasks
class ActiveStorageMigration
DEFAULT_CONTENT_TYPE = "application/octet-stream"
DISK_SERVICE = ActiveStorage::Service::DiskService
SERVICE_NAME = :alchemy_cms

METADATA = {
identified: true, # Skip identifying file type
analyzed: true, # Skip analyze job
composed: true # Skip checksum check
}

class << self
def migrate_picture(picture)
Alchemy::Deprecation.silence do
uid = picture.legacy_image_file_uid
key = key_for_uid(uid)
content_type = Mime::Type.lookup_by_extension(picture.legacy_image_file_format) || DEFAULT_CONTENT_TYPE
Alchemy::Picture.transaction do
blob = ActiveStorage::Blob.create!(
key: key,
filename: picture.legacy_image_file_name,
byte_size: picture.legacy_image_file_size,
content_type: content_type,
# Prevents (down)loading the original file
metadata: METADATA.merge(
width: picture.legacy_image_file_width,
height: picture.legacy_image_file_height
),
service_name: SERVICE_NAME
)
picture.create_image_file_attachment!(
name: :image_file,
record: picture,
blob: blob
)
end
move_file(Rails.root.join("uploads/pictures", uid), key)
end
end

def migrate_attachment(attachment)
Alchemy::Deprecation.silence do
uid = attachment.legacy_file_uid
key = key_for_uid(uid)
Alchemy::Attachment.transaction do
blob = ActiveStorage::Blob.create!(
key: key,
filename: attachment.legacy_file_name,
byte_size: attachment.legacy_file_size,
content_type: attachment.file_mime_type.presence || DEFAULT_CONTENT_TYPE,
metadata: METADATA,
service_name: SERVICE_NAME
)
attachment.create_file_attachment!(
record: attachment,
name: :file,
blob: blob
)
end
move_file(Rails.root.join("uploads/attachments", uid), key)
end
end

private

# ActiveStorage::Service::DiskService stores files in a folder structure
# based on the first two characters of the file uid.
def key_for_uid(uid)
case service
when DISK_SERVICE
uid.split("/").last
else
uid
end
end

def move_file(uid, key)
case service
when DISK_SERVICE
if File.exist?(uid)
service.send(:make_path_for, key)
FileUtils.cp uid, service.send(:path_for, key)
end
end
end

def service
ActiveStorage::Blob.services.fetch(SERVICE_NAME)
end
end
end
end
end
end
13 changes: 13 additions & 0 deletions lib/generators/alchemy/install/install_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,19 @@ def install_assets
end
end

def install_active_storage
rake "active_storage:install:migrations"
end

def set_active_storage_service
insert_into_file app_config_path.join("storage.yml"), <<-YAML.strip_heredoc
alchemy_cms:
service: Disk
root: <%= Rails.root.join("storage") %>
YAML
end

def copy_demo_views
return if options[:skip_demo_files]

Expand Down
32 changes: 31 additions & 1 deletion lib/tasks/alchemy/upgrade.rake
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ require "alchemy/version"
namespace :alchemy do
desc "Upgrades your app to AlchemyCMS v#{Alchemy::VERSION}."
task upgrade: [
"alchemy:upgrade:prepare"
"alchemy:upgrade:prepare",
"alchemy:upgrade:8.0:run"
] do
Alchemy::Upgrader.display_todos
end
Expand All @@ -28,5 +29,34 @@ namespace :alchemy do
task config: [:environment] do
Alchemy::Upgrader.copy_new_config_file
end

namespace "8.0" do
task "run" => [
"alchemy:upgrade:8.0:install_active_storage",
"alchemy:upgrade:8.0:prepare_dragonfly_config",
"alchemy:upgrade:8.0:migrate_pictures_to_active_storage",
"alchemy:upgrade:8.0:migrate_attachments_to_active_storage"
]

desc "Install active_storage"
task :install_active_storage do
Alchemy::Upgrader::EightZero.install_active_storage
end

desc "Prepare Dragonfly config"
task :prepare_dragonfly_config do
Alchemy::Upgrader::EightZero.prepare_dragonfly_config
end

desc "Migrate pictures to active_storage"
task :migrate_pictures_to_active_storage do
Alchemy::Upgrader::EightZero.migrate_pictures_to_active_storage
end

desc "Migrate attachments to active_storage"
task :migrate_attachments_to_active_storage do
Alchemy::Upgrader::EightZero.migrate_attachments_to_active_storage
end
end
end
end
2 changes: 2 additions & 0 deletions spec/dummy/config/initializers/dragonfly.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require "dragonfly"
require "dragonfly_svg"
# frozen_string_literal: true

# AlchemyCMS Dragonfly configuration.
Expand Down
4 changes: 4 additions & 0 deletions spec/dummy/config/storage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,7 @@ local:
# service: Mirror
# primary: local
# mirrors: [ amazon, google, microsoft ]

alchemy_cms:
service: Disk
root: <%= Rails.root.join("storage") %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# This migration comes from alchemy (originally 20240611080918)
class RenameAlchemyAttachmentFile < ActiveRecord::Migration[7.0]
COLUMNS = %i[
file_name
file_size
file_uid
]

def change
COLUMNS.each do |column|
rename_column :alchemy_attachments, column, :"legacy_#{column}"
end
end
end
Loading

0 comments on commit 38b4972

Please sign in to comment.