# Migrando de Paperclip a ActiveStorage Paperclip y ActiveStorage resuelven problemas similares con soluciones similares, por lo que pasar de uno a otro es simple. El proceso de ir desde Paperclip hacia ActiveStorage es como sigue: 1. Implementa las migraciones a la base de datos de ActiveStorage. 2. Configura el almacenamiento. 3. Copia la base de datos. 4. Copia los archivos. 5. Actualiza tus pruebas. 6. Actualiza tus vistas. 7. Actualiza tus controladores. 8. Actualiza tus modelos. ## Implementa las migraciones a la base de datos de ActiveStorage Sigue [las instrucciones para instalar ActiveStorage]. Muy probablemente vas a querer agregar la gema `mini_magick` a tu Gemfile. ```sh rails active_storage:install ``` [las instrucciones para instalar ActiveStorage]: https://github.com/rails/rails/blob/master/activestorage/README.md#installation ## Configura el almacenamiento De nuevo, sigue [las instrucciones para configurar ActiveStorage]. [las instrucciones para configurar ActiveStorage]: http://edgeguides.rubyonrails.org/active_storage_overview.html#setup ## Copia la base de datos. Las tablas `active_storage_blobs` y`active_storage_attachments` son en donde ActiveStorage espera encontrar los metadatos del archivo. Paperclip almacena los metadatos del archivo directamente en en la tabla del objeto asociado. Vas a necesitar escribir una migración para esta conversión. Proveer un script simple, es complicado porque están involucrados tus modelos. ¡Pero lo intentaremos! Así sería para un `User` con un `avatar` en Paperclip: ```ruby class User < ApplicationRecord has_attached_file :avatar end ``` Tus migraciones de Paperclip producirán una tabla como la siguiente: ```ruby create_table "users", force: :cascade do |t| t.string "avatar_file_name" t.string "avatar_content_type" t.integer "avatar_file_size" t.datetime "avatar_updated_at" end ``` Y tu la convertirás en estas tablas: ```ruby create_table "active_storage_attachments", force: :cascade do |t| t.string "name", null: false t.string "record_type", null: false t.integer "record_id", null: false t.integer "blob_id", null: false t.datetime "created_at", null: false t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true end ``` ```ruby create_table "active_storage_blobs", force: :cascade 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"], name: "index_active_storage_blobs_on_key", unique: true end ``` Así que asumiendo que quieres dejar los archivos en el mismo lugar, _esta es tu migración_. De otra forma, ve la siguiente sección primero y modifica la migración como corresponda. ```ruby Dir[Rails.root.join("app/models/**/*.rb")].sort.each { |file| require file } class ConvertToActiveStorage < ActiveRecord::Migration[5.2] require 'open-uri' def up # postgres get_blob_id = 'LASTVAL()' # mariadb # get_blob_id = 'LAST_INSERT_ID()' # sqlite # get_blob_id = 'LAST_INSERT_ROWID()' active_storage_blob_statement = ActiveRecord::Base.connection.raw_connection.prepare(<<-SQL) INSERT INTO active_storage_blobs ( key, filename, content_type, metadata, byte_size, checksum, created_at ) VALUES (?, ?, ?, '{}', ?, ?, ?) SQL active_storage_attachment_statement = ActiveRecord::Base.connection.raw_connection.prepare(<<-SQL) INSERT INTO active_storage_attachments ( name, record_type, record_id, blob_id, created_at ) VALUES (?, ?, ?, #{get_blob_id}, ?) SQL 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 model.find_each.each do |instance| attachments.each do |attachment| active_storage_blob_statement.execute( 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 ) active_storage_attachment_statement. execute(attachment, model.name, instance.id, instance.updated_at.iso8601) end end end end active_storage_attachment_statement.close active_storage_blob_statement.close end def down raise ActiveRecord::IrreversibleMigration end private def key(instance, attachment) SecureRandom.uuid # Alternativamente: # instance.send("#{attachment}_file_name") end def checksum(attachment) # archivos locales almacenados en disco: url = attachment.path Digest::MD5.base64digest(File.read(url)) # archivos remotos almacenados en la computadora de alguién más: # url = attachment.url # Digest::MD5.base64digest(Net::HTTP.get(URI(url))) end end ``` ## Copia los archivos La migración de arriba deja los archivos como estaban. Sin embargo, los servicios de Paperclip y ActiveStorage utilizan diferentes ubicaciones. Por defecto, Paperclip se ve así: ``` public/system/users/avatars/000/000/004/original/the-mystery-of-life.png ``` Y ActiveStorage se ve así: ``` storage/xM/RX/xMRXuT6nqpoiConJFQJFt6c9 ``` Ese `xMRXuT6nqpoiConJFQJFt6c9` es el valor de `active_storage_blobs.key`. En la migración de arriba usamos simplemente el nombre del archivo, pero tal vez quieras usar un UUID. Migrando los archivos en un hospedaje externo (S3, Azure Storage, GCS, etc.) está fuera del alcance de este documento inicial. Así es como se vería para un almacenamiento local: ```ruby #!bin/rails runner class ActiveStorageBlob < ActiveRecord::Base end class ActiveStorageAttachment < ActiveRecord::Base belongs_to :blob, class_name: 'ActiveStorageBlob' belongs_to :record, polymorphic: true end ActiveStorageAttachment.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 ``` ## Actualiza tus pruebas En lugar de utilizar `have_attached_file`, será necesario que escribas tu propio matcher. Aquí hay un matcher similar _en espíritu_ al que Paperclip provee: ```ruby RSpec::Matchers.define :have_attached_file do |name| matches do |record| file = record.send(name) file.respond_to?(:variant) && file.respond_to?(:attach) end end ``` ## Actualiza tus vistas En Paperclip se ven así: ```ruby image_tag @user.avatar.url(:medium) ``` En ActiveStorage se ven así: ```ruby image_tag @user.avatar.variant(resize: "250x250") ``` ## Actualiza tus controladores Esto no debería _requerir_ ningúna actualización. Sin embargo, si te fijas en el schema de tu base de datos, notaras un join. Por ejemplo si tu controlador tiene: ```ruby def index @users = User.all.order(:name) end ``` Y tu vista tiene: ```