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.
| Aspect | Paperclip | Active Storage |
|---|---|---|
| Maintenance | Archived (no updates since 2018) | Ships with Rails core |
| Storage architecture | Columns on the parent model | Polymorphic blobs + attachments tables |
| Metadata storage | avatar_file_name, avatar_content_type on users | active_storage_blobs (shared across models) |
| Cloud backends | paperclip-storage-* gems | Built-in S3, GCS, Azure adapters |
| File processing | ImageMagick via gem | Active Storage variants + representations |
| Security | CVE-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.