Database

Safe Paperclip to Active Storage Migration Script

BLUF (Bottom Line Up Front): Paperclip is deprecated. Migrating to Active Storage requires translating your legacy database columns into the active_storage_blobs and active_storage_attachments tables. You do not need to physically move the files on S3. You only need a secure data migration script to remap the polymorphic association.

Paperclip vs Active Storage (Rails 6+) attachment handling
AspectPaperclipActive Storage
MaintenanceArchived (no updates since 2018)Ships with Rails core
Storage architectureColumns on the parent modelPolymorphic blobs + attachments tables
Metadata storageavatar_file_name, avatar_content_type on usersactive_storage_blobs (shared across models)
Cloud backendspaperclip-storage-* gemsBuilt-in S3, GCS, Azure adapters
File processingImageMagick via gemActive Storage variants + representations
SecurityCVE-2017-0889 path traversal (legacy)Signed URLs with expiry by default

Phase 1: The Architecture Difference

Paperclip stored metadata directly on the parent model (e.g., users table had avatar_file_name, avatar_content_type). Active Storage uses a polymorphic association architecture, decoupling the file metadata into dedicated tables.

Synthetic Engineering Context: The Migration Gap

Before running the script, your database schema looks like this:

-- Legacy Paperclip Columns
SELECT id, avatar_file_name, avatar_content_type, avatar_file_size FROM users LIMIT 1;
-- Returns: 1 | profile.jpg | image/jpeg | 10245

Phase 2: The Migration Script (PoC)

To migrate without downloading and re-uploading terabytes of S3 migration data, we generate the Active Storage records directly.

# lib/tasks/migrate_paperclip.rake
namespace :storage do
  desc "Migrate Paperclip data to Active Storage"
  task paperclip_to_active_storage: :environment do
    User.where.not(avatar_file_name: nil).find_each do |user|
      ActiveRecord::Base.transaction do
        # 1. Create the Blob using legacy Paperclip data
        blob = ActiveStorage::Blob.create!(
          key: "users/avatars/#{user.id}/original/#{user.avatar_file_name}",
          filename: user.avatar_file_name,
          content_type: user.avatar_content_type,
          byte_size: user.avatar_file_size,
          checksum: user.avatar_updated_at.to_i.to_s # Dummy checksum for legacy
        )

        # 2. Create the Attachment (Polymorphic mapping)
        ActiveStorage::Attachment.create!(
          name: "avatar",
          record_type: "User",
          record_id: user.id,
          blob_id: blob.id
        )
      end
    end
    puts "Migration complete."
  end
end

This script creates the exact data structures Active Storage expects, pointing directly to the existing S3 keys.

Need Help Stabilizing Your Legacy App? Data migrations involving file storage carry a high risk of data loss or corrupted links. Our team at USEO has extensive experience migrating complex Paperclip architectures to Active Storage with zero data loss.

Contact us for a Technical Debt Audit