Resumen de Active Storage

Esta guía cubre cómo adjuntar archivos a tus modelos de Active Record.

Después de leer esta guía, sabrás:


1 ¿Qué es Active Storage?

Active Storage facilita la carga de archivos a un servicio de almacenamiento en la nube como Amazon S3, Google Cloud Storage o Microsoft Azure Storage y adjuntar esos archivos a objetos de Active Record. Viene con un servicio basado en disco local para desarrollo y pruebas, y admite la duplicación de archivos a servicios subordinados para copias de seguridad y migraciones.

Usando Active Storage, una aplicación puede transformar cargas de imágenes o generar representaciones de imágenes de cargas que no son imágenes como PDFs y videos, y extraer metadatos de archivos arbitrarios.

1.1 Requisitos

Varias características de Active Storage dependen de software de terceros que Rails no instalará, y deben instalarse por separado:

  • libvips v8.6+ o ImageMagick para análisis y transformaciones de imágenes
  • ffmpeg v3.4+ para previsualizaciones de video y ffprobe para análisis de video/audio
  • poppler o muPDF para previsualizaciones de PDF

El análisis y las transformaciones de imágenes también requieren la gema image_processing. Descoméntala en tu Gemfile, o agrégala si es necesario:

gem "image_processing", ">= 1.2"

CONSEJO: Comparado con libvips, ImageMagick es más conocido y está más disponible. Sin embargo, libvips puede ser hasta 10 veces más rápido y consumir 1/10 de la memoria. Para archivos JPEG, esto puede mejorarse aún más reemplazando libjpeg-dev con libjpeg-turbo-dev, que es 2-7 veces más rápido.

ADVERTENCIA: Antes de instalar y usar software de terceros, asegúrate de entender las implicaciones de licencia de hacerlo. MuPDF, en particular, está licenciado bajo AGPL y requiere una licencia comercial para algunos usos.

2 Configuración

$ bin/rails active_storage:install
$ bin/rails db:migrate

Esto configura la configuración y crea las tres tablas que usa Active Storage: active_storage_blobs, active_storage_attachments y active_storage_variant_records.

Tabla Propósito
active_storage_blobs Almacena datos sobre archivos cargados, como el nombre del archivo y el tipo de contenido.
active_storage_attachments Una tabla de unión polimórfica que conecta tus modelos con blobs. Si el nombre de clase de tu modelo cambia, necesitarás ejecutar una migración en esta tabla para actualizar el record_type subyacente al nuevo nombre de clase de tu modelo.
active_storage_variant_records Si el seguimiento de variantes está habilitado, almacena registros para cada variante que ha sido generada.

ADVERTENCIA: Si estás usando UUIDs en lugar de enteros como clave primaria en tus modelos, deberías establecer Rails.application.config.generators { |g| g.orm :active_record, primary_key_type: :uuid } en un archivo de configuración.

Declara servicios de Active Storage en config/storage.yml. Para cada servicio que tu aplicación use, proporciona un nombre y la configuración requerida. El ejemplo a continuación declara tres servicios llamados local, test y amazon:

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

amazon:
  service: S3
  access_key_id: ""
  secret_access_key: ""
  bucket: ""
  region: "" # por ejemplo, 'us-east-1'

Dile a Active Storage qué servicio usar configurando Rails.application.config.active_storage.service. Debido a que cada entorno probablemente usará un servicio diferente, se recomienda hacer esto por entorno. Para usar el servicio de disco del ejemplo anterior en el entorno de desarrollo, agregarías lo siguiente a config/environments/development.rb:

# Almacena archivos localmente.
config.active_storage.service = :local

Para usar el servicio S3 en producción, agregas lo siguiente a config/environments/production.rb:

# Almacena archivos en Amazon S3.
config.active_storage.service = :amazon

Para usar el servicio de prueba al probar, agregas lo siguiente a config/environments/test.rb:

# Almacena archivos cargados en el sistema de archivos local en un directorio temporal.
config.active_storage.service = :test

NOTA: Los archivos de configuración que son específicos del entorno tendrán prioridad: en producción, por ejemplo, el archivo config/storage/production.yml (si existe) tendrá prioridad sobre el archivo config/storage.yml.

Se recomienda usar Rails.env en los nombres de los buckets para reducir aún más el riesgo de destruir accidentalmente datos de producción.

amazon:
  service: S3
  # ...
  bucket: your_own_bucket-<%= Rails.env %>

google:
  service: GCS
  # ...
  bucket: your_own_bucket-<%= Rails.env %>

azure:
  service: AzureStorage
  # ...
  container: your_container_name-<%= Rails.env %>

Continúa leyendo para obtener más información sobre los adaptadores de servicio integrados (por ejemplo, Disk y S3) y la configuración que requieren.

2.1 Servicio de Disco

Declara un servicio de disco en config/storage.yml:

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

2.2 Servicio S3 (Amazon S3 y APIs compatibles con S3)

Para conectarte a Amazon S3, declara un servicio S3 en config/storage.yml:

amazon:
  service: S3
  access_key_id: ""
  secret_access_key: ""
  region: ""
  bucket: ""

Opcionalmente, proporciona opciones de cliente y carga:

amazon:
  service: S3
  access_key_id: ""
  secret_access_key: ""
  region: ""
  bucket: ""
  http_open_timeout: 0
  http_read_timeout: 0
  retry_limit: 0
  upload:
    server_side_encryption: "" # 'aws:kms' o 'AES256'
    cache_control: "private, max-age=<%= 1.day.to_i %>"

CONSEJO: Establece límites de tiempo HTTP y límites de reintento del cliente sensatos para tu aplicación. En ciertos escenarios de falla, la configuración predeterminada del cliente de AWS puede hacer que las conexiones se mantengan durante varios minutos y llevar a la cola de solicitudes.

Agrega la gema aws-sdk-s3 a tu Gemfile:

gem "aws-sdk-s3", require: false

NOTA: Las características principales de Active Storage requieren los siguientes permisos: s3:ListBucket, s3:PutObject, s3:GetObject y s3:DeleteObject. El acceso público además requiere s3:PutObjectAcl. Si tienes opciones de carga adicionales configuradas, como establecer ACLs, es posible que se requieran permisos adicionales.

NOTA: Si deseas usar variables de entorno, archivos de configuración estándar del SDK, perfiles, perfiles de instancia IAM o roles de tarea, puedes omitir las claves access_key_id, secret_access_key y region en el ejemplo anterior. El servicio S3 admite todas las opciones de autenticación descritas en la documentación del SDK de AWS.

Para conectarte a una API de almacenamiento de objetos compatible con S3, como DigitalOcean Spaces, proporciona el endpoint:

digitalocean:
  service: S3
  endpoint: https://nyc3.digitaloceanspaces.com
  access_key_id: ...
  secret_access_key: ...
  # ...y otras opciones

Hay muchas otras opciones disponibles. Puedes consultarlas en la documentación del Cliente S3 de AWS.

2.3 Servicio de Almacenamiento de Microsoft Azure

Declara un servicio de Almacenamiento de Azure en config/storage.yml:

azure:
  service: AzureStorage
  storage_account_name: ""
  storage_access_key: ""
  container: ""

Agrega la gema azure-storage-blob a tu Gemfile:

gem "azure-storage-blob", "~> 2.0", require: false

2.4 Servicio de Almacenamiento en la Nube de Google

Declara un servicio de Almacenamiento en la Nube de Google en config/storage.yml:

google:
  service: GCS
  credentials: <%= Rails.root.join("path/to/keyfile.json") %>
  project: ""
  bucket: ""

Opcionalmente, proporciona un Hash de credenciales en lugar de una ruta de archivo de clave:

google:
  service: GCS
  credentials:
    type: "service_account"
    project_id: ""
    private_key_id: <%= Rails.application.credentials.dig(:gcs, :private_key_id) %>
    private_key: <%= Rails.application.credentials.dig(:gcs, :private_key).dump %>
    client_email: ""
    client_id: ""
    auth_uri: "https://accounts.google.com/o/oauth2/auth"
    token_uri: "https://accounts.google.com/o/oauth2/token"
    auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs"
    client_x509_cert_url: ""
  project: ""
  bucket: ""

Opcionalmente, proporciona un metadata de Cache-Control para establecer en los activos cargados:

google:
  service: GCS
  ...
  cache_control: "public, max-age=3600"

Opcionalmente, usa IAM en lugar de las credentials al firmar URLs. Esto es útil si estás autenticando tus aplicaciones GKE con Workload Identity, consulta esta publicación en el blog de Google Cloud para más información.

google:
  service: GCS
  ...
  iam: true

Opcionalmente, usa un GSA específico al firmar URLs. Al usar IAM, el servidor de metadatos será contactado para obtener el correo electrónico del GSA, pero este servidor de metadatos no siempre está presente (por ejemplo, pruebas locales) y puedes desear usar un GSA no predeterminado.

google:
  service: GCS
  ...
  iam: true
  gsa_email: "foobar@baz.iam.gserviceaccount.com"

Agrega la gema google-cloud-storage a tu Gemfile:

gem "google-cloud-storage", "~> 1.11", require: false

2.5 Servicio de Espejo

Puedes mantener múltiples servicios sincronizados definiendo un servicio de espejo. Un servicio de espejo replica cargas y eliminaciones a través de dos o más servicios subordinados.

Un servicio de espejo está destinado a ser usado temporalmente durante una migración entre servicios en producción. Puedes comenzar a reflejar en un nuevo servicio, copiar archivos preexistentes del servicio antiguo al nuevo, y luego ir completamente al nuevo servicio.

NOTA: El reflejo no es atómico. Es posible que una carga tenga éxito en el servicio primario y falle en cualquiera de los servicios subordinados. Antes de ir completamente al nuevo servicio, verifica que todos los archivos hayan sido copiados.

Define cada uno de los servicios que deseas reflejar como se describe arriba. Haz referencia a ellos por nombre al definir un servicio de espejo:

s3_west_coast:
  service: S3
  access_key_id: ""
  secret_access_key: ""
  region: ""
  bucket: ""

s3_east_coast:
  service: S3
  access_key_id: ""
  secret_access_key: ""
  region: ""
  bucket: ""

production:
  service: Mirror
  primary: s3_east_coast
  mirrors:
    - s3_west_coast

Aunque todos los servicios secundarios reciben cargas, las descargas siempre son manejadas por el servicio primario.

Los servicios de espejo son compatibles con cargas directas. Los archivos nuevos se cargan directamente al servicio primario. Cuando un archivo cargado directamente se adjunta a un registro, se encola un trabajo en segundo plano para copiarlo a los servicios secundarios.

2.6 Acceso público

Por defecto, Active Storage asume acceso privado a los servicios. Esto significa generar URLs firmadas, de un solo uso para blobs. Si prefieres hacer que los blobs sean accesibles públicamente, especifica public: true en el config/storage.yml de tu aplicación:

gcs: &gcs
  service: GCS
  project: ""

private_gcs:
  <<: *gcs
  credentials: <%= Rails.root.join("path/to/private_key.json") %>
  bucket: ""

public_gcs:
  <<: *gcs
  credentials: <%= Rails.root.join("path/to/public_key.json") %>
  bucket: ""
  public: true

Asegúrate de que tus buckets estén configurados adecuadamente para acceso público. Consulta la documentación sobre cómo habilitar permisos de lectura pública para Amazon S3, Google Cloud Storage y Microsoft Azure servicios de almacenamiento. Amazon S3 además requiere que tengas el permiso s3:PutObjectAcl.

Al convertir una aplicación existente para usar public: true, asegúrate de actualizar cada archivo individual en el bucket para que sea legible públicamente antes de cambiar.

3 Adjuntando Archivos a Registros

3.1 has_one_attached

La macro has_one_attached configura un mapeo de uno a uno entre registros y archivos. Cada registro puede tener un archivo adjunto.

Por ejemplo, supongamos que tu aplicación tiene un modelo User. Si deseas que cada usuario tenga un avatar, define el modelo User de la siguiente manera:

class User < ApplicationRecord
  has_one_attached :avatar
end

o si estás usando Rails 6.0+, puedes ejecutar un comando generador de modelo como este:

$ bin/rails generate model User avatar:attachment

Puedes crear un usuario con un avatar:

<%= form.file_field :avatar %>
class SignupController < ApplicationController
  def create
    user = User.create!(user_params)
    session[:user_id] = user.id
    redirect_to root_path
  end

  private
    def user_params
      params.require(:user).permit(:email_address, :password, :avatar)
    end
end

Llama a avatar.attach para adjuntar un avatar a un usuario existente:

user.avatar.attach(params[:avatar])

Llama a avatar.attached? para determinar si un usuario en particular tiene un avatar:

user.avatar.attached?

En algunos casos, es posible que desees anular un servicio predeterminado para un adjunto específico. Puedes configurar servicios específicos por adjunto usando la opción service con el nombre de tu servicio:

class User < ApplicationRecord
  has_one_attached :avatar, service: :google
end

Puedes configurar variantes específicas por adjunto llamando al método variant en el objeto adjuntable generado:

class User < ApplicationRecord
  has_one_attached :avatar do |attachable|
    attachable.variant :thumb, resize_to_limit: [100, 100]
  end
end

Llama a avatar.variant(:thumb) para obtener una variante de miniatura de un avatar:

<%= image_tag user.avatar.variant(:thumb) %>

También puedes usar variantes específicas para previsualizaciones:

class User < ApplicationRecord
  has_one_attached :video do |attachable|
    attachable.variant :thumb, resize_to_limit: [100, 100]
  end
end
<%= image_tag user.video.preview(:thumb) %>

Si sabes de antemano que tus variantes serán accedidas, puedes especificar que Rails las genere por adelantado:

class User < ApplicationRecord
  has_one_attached :video do |attachable|
    attachable.variant :thumb, resize_to_limit: [100, 100], preprocessed: true
  end
end

Rails encolará un trabajo para generar la variante después de que el adjunto se adjunte al registro.

NOTA: Dado que Active Storage se basa en asociaciones polimórficas, y las asociaciones polimórficas se basan en almacenar nombres de clase en la base de datos, esos datos deben permanecer sincronizados con el nombre de clase usado por el código Ruby. Al renombrar clases que usan has_one_attached, asegúrate de actualizar también los nombres de clase en la columna de tipo polimórfico active_storage_attachments.record_type de las filas correspondientes.

3.2 has_many_attached

La macro has_many_attached configura una relación de uno a muchos entre registros y archivos. Cada registro puede tener muchos archivos adjuntos.

Por ejemplo, supongamos que tu aplicación tiene un modelo Message. Si deseas que cada mensaje tenga muchas imágenes, define el modelo Message de la siguiente manera:

class Message < ApplicationRecord
  has_many_attached :images
end

o si estás usando Rails 6.0+, puedes ejecutar un comando generador de modelo como este:

$ bin/rails generate model Message images:attachments

Puedes crear un mensaje con imágenes:

class MessagesController < ApplicationController
  def create
    message = Message.create!(message_params)
    redirect_to message
  end

  private
    def message_params
      params.require(:message).permit(:title, :content, images: [])
    end
end

Llama a images.attach para agregar nuevas imágenes a un mensaje existente:

@message.images.attach(params[:images])

Llama a images.attached? para determinar si un mensaje en particular tiene alguna imagen:

@message.images.attached?

Anular el servicio predeterminado se hace de la misma manera que has_one_attached, usando la opción service:

class Message < ApplicationRecord
  has_many_attached :images, service: :s3
end

Configurar variantes específicas se hace de la misma manera que has_one_attached, llamando al método variant en el objeto adjuntable generado:

class Message < ApplicationRecord
  has_many_attached :images do |attachable|
    attachable.variant :thumb, resize_to_limit: [100, 100]
  end
end

NOTA: Dado que Active Storage se basa en asociaciones polimórficas, y las asociaciones polimórficas se basan en almacenar nombres de clase en la base de datos, esos datos deben permanecer sincronizados con el nombre de clase usado por el código Ruby. Al renombrar clases que usan has_many_attached, asegúrate de actualizar también los nombres de clase en la columna de tipo polimórfico active_storage_attachments.record_type de las filas correspondientes.

3.3 Adjuntando Objetos File/IO

A veces necesitas adjuntar un archivo que no llega a través de una solicitud HTTP. Por ejemplo, es posible que desees adjuntar un archivo que generaste en disco o descargaste desde una URL enviada por el usuario. También puedes querer adjuntar un archivo de prueba en una prueba de modelo. Para hacerlo, proporciona un Hash que contenga al menos un objeto IO abierto y un nombre de archivo:

@message.images.attach(io: File.open('/path/to/file'), filename: 'file.pdf')

Cuando sea posible, proporciona un tipo de contenido también. Active Storage intenta determinar el tipo de contenido de un archivo a partir de sus datos. Recurre al tipo de contenido que proporciones si no puede hacerlo.

@message.images.attach(io: File.open('/path/to/file'), filename: 'file.pdf', content_type: 'application/pdf')

Puedes omitir la inferencia del tipo de contenido a partir de los datos pasando identify: false junto con el content_type.

@message.images.attach(
  io: File.open('/path/to/file'),
  filename: 'file.pdf',
  content_type: 'application/pdf',
  identify: false
)

Si no proporcionas un tipo de contenido y Active Storage no puede determinar el tipo de contenido del archivo automáticamente, se establece por defecto en application/octet-stream.

Hay un parámetro adicional key que puede usarse para especificar carpetas/subcarpetas en tu Bucket S3. AWS S3 usa de otra manera una clave aleatoria para nombrar tus archivos. Este enfoque es útil si deseas organizar mejor los archivos de tu Bucket S3.

@message.images.attach(
  io: File.open('/path/to/file'),
  filename: 'file.pdf',
  content_type: 'application/pdf',
  key: "#{Rails.env}/blog_content/intuitive_filename.pdf",
  identify: false
)

De esta manera, el archivo se guardará en la carpeta [S3_BUCKET]/development/blog_content/ cuando pruebes esto desde tu entorno de desarrollo. Ten en cuenta que si usas el parámetro key, debes asegurarte de que la clave sea única para que la carga se realice correctamente. Se recomienda agregar al nombre del archivo una clave aleatoria única, algo como:

def s3_file_key
  "#{Rails.env}/blog_content/intuitive_filename-#{SecureRandom.uuid}.pdf"
end
@message.images.attach(
  io: File.open('/path/to/file'),
  filename: 'file.pdf',
  content_type: 'application/pdf',
  key: s3_file_key,
  identify: false
)

3.4 Reemplazo vs Adición de Adjuntos

Por defecto en Rails, adjuntar archivos a una asociación has_many_attached reemplazará cualquier adjunto existente.

Para mantener los adjuntos existentes, puedes usar campos de formulario ocultos con el signed_id de cada archivo adjunto:

<% @message.images.each do |image| %>
  <%= form.hidden_field :images, multiple: true, value: image.signed_id %>
<% end %>

<%= form.file_field :images, multiple: true %>

Esto tiene la ventaja de permitir eliminar adjuntos existentes selectivamente, por ejemplo, usando JavaScript para eliminar campos ocultos individuales.

3.5 Validación de Formularios

Los adjuntos no se envían al servicio de almacenamiento hasta que se realiza un save exitoso en el registro asociado. Esto significa que si una presentación de formulario falla en la validación, cualquier nuevo adjunto se perderá y deberá cargarse nuevamente. Dado que las cargas directas se almacenan antes de que se envíe el formulario, pueden usarse para retener cargas cuando la validación falla:

<%= form.hidden_field :avatar, value: @user.avatar.signed_id if @user.avatar.attached? %>
<%= form.file_field :avatar, direct_upload: true %>

4 Eliminando Archivos

Para eliminar un adjunto de un modelo, llama a purge en el adjunto. Si tu aplicación está configurada para usar Active Job, la eliminación puede hacerse en segundo plano en su lugar llamando a purge_later. La purga elimina el blob y el archivo del servicio de almacenamiento.

# Destruye sincrónicamente el avatar y los archivos de recursos reales.
user.avatar.purge

# Destruye los modelos asociados y los archivos de recursos reales de forma asíncrona, a través de Active Job.
user.avatar.purge_later

5 Sirviendo Archivos

Active Storage admite dos formas de servir archivos: redirigiendo y proxying.

ADVERTENCIA: Todos los controladores de Active Storage son accesibles públicamente por defecto. Las URLs generadas son difíciles de adivinar, pero permanentes por diseño. Si tus archivos requieren un nivel más alto de protección, considera implementar Controladores Autenticados.

5.1 Modo de Redirección

Para generar una URL permanente para un blob, puedes pasar el blob al ayudante de vista url_for. Esto genera una URL con el signed_id del blob que se enruta al RedirectController del blob.

url_for(user.avatar)
# => https://www.example.com/rails/active_storage/blobs/redirect/:signed_id/my-avatar.png

El RedirectController redirige al endpoint del servicio real. Esta indirecta desacopla la URL del servicio de la real, y permite, por ejemplo, reflejar adjuntos en diferentes servicios para alta disponibilidad. La redirección tiene una expiración HTTP de 5 minutos.

Para crear un enlace de descarga, usa el ayudante rails_blob_{path|url}. Usar este ayudante te permite establecer la disposición.

rails_blob_path(user.avatar, disposition: "attachment")

ADVERTENCIA: Para prevenir ataques XSS, Active Storage fuerza el encabezado Content-Disposition a "attachment" para algunos tipos de archivos. Para cambiar este comportamiento, consulta las opciones de configuración disponibles en Configuración de Aplicaciones Rails.

Si necesitas crear un enlace desde fuera del contexto del controlador/vista (trabajos en segundo plano, trabajos cron, etc.), puedes acceder a rails_blob_path así:

Rails.application.routes.url_helpers.rails_blob_path(user.avatar, only_path: true)

5.2 Modo de Proxy

Opcionalmente, los archivos pueden ser proxied en su lugar. Esto significa que tus servidores de aplicación descargarán los datos del archivo del servicio de almacenamiento en respuesta a las solicitudes. Esto puede ser útil para servir archivos desde un CDN.

Puedes configurar Active Storage para usar proxying por defecto:

# config/initializers/active_storage.rb
Rails.application.config.active_storage.resolve_model_to_route = :rails_storage_proxy

O si deseas explícitamente hacer proxy de adjuntos específicos, hay ayudantes de URL que puedes usar en la forma de rails_storage_proxy_path y rails_storage_proxy_url.

<%= image_tag rails_storage_proxy_path(@user.avatar) %>

5.2.1 Poniendo un CDN delante de Active Storage

Además, para usar un CDN para los adjuntos de Active Storage, necesitarás generar URLs con modo de proxy para que sean servidos por tu aplicación y el CDN almacenará en caché el adjunto sin ninguna configuración adicional. Esto funciona de inmediato porque el controlador de proxy predeterminado de Active Storage establece un encabezado HTTP indicando al CDN que almacene en caché la respuesta.

También debes asegurarte de que las URLs generadas usen el host del CDN en lugar del host de tu aplicación. Hay múltiples formas de lograr esto, pero en general implica ajustar tu archivo config/routes.rb para que puedas generar las URLs adecuadas para los adjuntos y sus variaciones. Como ejemplo, podrías agregar esto:

# config/routes.rb
direct :cdn_image do |model, options|
  expires_in = options.delete(:expires_in) { ActiveStorage.urls_expire_in }

  if model.respond_to?(:signed_id)
    route_for(
      :rails_service_blob_proxy,
      model.signed_id(expires_in: expires_in),
      model.filename,
      options.merge(host: ENV['CDN_HOST'])
    )
  else
    signed_blob_id = model.blob.signed_id(expires_in: expires_in)
    variation_key  = model.variation.key
    filename       = model.blob.filename

    route_for(
      :rails_blob_representation_proxy,
      signed_blob_id,
      variation_key,
      filename,
      options.merge(host: ENV['CDN_HOST'])
    )
  end
end

y luego generar rutas así:

<%= cdn_image_url(user.avatar.variant(resize_to_limit: [128, 128])) %>

5.3 Controladores Autenticados

Todos los controladores de Active Storage son accesibles públicamente por defecto. Las URLs generadas usan un signed_id simple, lo que las hace difíciles de adivinar pero permanentes. Cualquiera que conozca la URL del blob podrá acceder a ella, incluso si una before_action en tu ApplicationController requeriría de otro modo un inicio de sesión. Si tus archivos requieren un nivel más alto de protección, puedes implementar tus propios controladores autenticados, basados en el ActiveStorage::Blobs::RedirectController, ActiveStorage::Blobs::ProxyController, ActiveStorage::Representations::RedirectController y ActiveStorage::Representations::ProxyController.

Para permitir solo a una cuenta acceder a su propio logo, podrías hacer lo siguiente:

# config/routes.rb
resource :account do
  resource :logo
end
# app/controllers/logos_controller.rb
class LogosController < ApplicationController
  # A través de ApplicationController:
  # include Authenticate, SetCurrentAccount

  def show
    redirect_to Current.account.logo.url
  end
end
<%= image_tag account_logo_path %>

Y luego deberías deshabilitar las rutas predeterminadas de Active Storage con:

config.active_storage.draw_routes = false

para evitar que los archivos sean accedidos con las URLs accesibles públicamente.

6 Descargando Archivos

A veces necesitas procesar un blob después de que se haya cargado, por ejemplo, para convertirlo a un formato diferente. Usa el método download del adjunto para leer los datos binarios de un blob en memoria:

binary = user.avatar.download

Es posible que desees descargar un blob a un archivo en disco para que un programa externo (por ejemplo, un escáner de virus o un transcodificador de medios) pueda operar sobre él. Usa el método open del adjunto para descargar un blob a un archivo temporal en disco:

message.video.open do |file|
  system '/path/to/virus/scanner', file.path
  # ...
end

Es importante saber que el archivo no está disponible en el callback after_create, sino solo en el after_create_commit.

7 Analizando Archivos

Active Storage analiza archivos una vez que se han cargado encolando un trabajo en Active Job. Los archivos analizados almacenarán información adicional en el hash de metadatos, incluyendo analyzed: true. Puedes verificar si un blob ha sido analizado llamando a analyzed? en él.

El análisis de imágenes proporciona atributos de width y height. El análisis de videos proporciona estos, así como duration, angle, display_aspect_ratio, y booleanos video y audio para indicar la presencia de esos canales. El análisis de audio proporciona atributos de duration y bit_rate.

8 Mostrando Imágenes, Videos y PDFs

Active Storage admite representar una variedad de archivos. Puedes llamar a representation en un adjunto para mostrar una variante de imagen, o una previsualización de un video o PDF. Antes de llamar a representation, verifica si el adjunto puede ser representado llamando a representable?. Algunos formatos de archivo no pueden ser previsualizados por Active Storage de inmediato (por ejemplo, documentos de Word); si representable? devuelve false, es posible que desees enlazar a el archivo en su lugar.

<ul>
  <% @message.files.each do |file| %>
    <li>
      <% if file.representable? %>
        <%= image_tag file.representation(resize_to_limit: [100, 100]) %>
      <% else %>
        <%= link_to rails_blob_path(file, disposition: "attachment") do %>
          <%= image_tag "placeholder.png", alt: "Download file" %>
        <% end %>
      <% end %>
    </li>
  <% end %>
</ul>

Internamente, representation llama a variant para imágenes, y a preview para archivos previsualizables. También puedes llamar a estos métodos directamente.

8.1 Carga Perezosa vs Inmediata

Por defecto, Active Storage procesará las representaciones de manera perezosa. Este código:

image_tag file.representation(resize_to_limit: [100, 100])

Generará una etiqueta <img> con el src apuntando al ActiveStorage::Representations::RedirectController. El navegador hará una solicitud a ese controlador, que realizará lo siguiente:

  1. Procesar el archivo y cargar el archivo procesado si es necesario.
  2. Devolver una redirección 302 al archivo ya sea a
    • el servicio remoto (por ejemplo, S3).
    • o ActiveStorage::Blobs::ProxyController, que devolverá el contenido del archivo si el modo de proxy está habilitado.

Cargar el archivo de manera perezosa permite que características como URLs de un solo uso funcionen sin ralentizar tus cargas de página iniciales.

Esto funciona bien en la mayoría de los casos.

Si deseas generar URLs para imágenes de inmediato, puedes llamar a .processed.url:

image_tag file.representation(resize_to_limit: [100, 100]).processed.url

El rastreador de variantes de Active Storage mejora el rendimiento de esto, almacenando un registro en la base de datos si la representación solicitada ha sido procesada antes. Por lo tanto, el código anterior solo hará una llamada API al servicio remoto (por ejemplo, S3) una vez, y una vez que una variante esté almacenada, usará esa. El rastreador de variantes se ejecuta automáticamente, pero puede ser desactivado a través de config.active_storage.track_variants.

Si estás renderizando muchas imágenes en una página, el ejemplo anterior podría resultar en consultas N+1 cargando todos los registros de variantes. Para evitar estas consultas N+1, usa los alcances nombrados en ActiveStorage::Attachment.

message.images.with_all_variant_records.each do |file|
  image_tag file.representation(resize_to_limit: [100, 100]).processed.url
end

8.2 Transformando Imágenes

Transformar imágenes te permite mostrar la imagen en las dimensiones de tu elección. Para crear una variación de una imagen, llama a variant en el adjunto. Puedes pasar cualquier transformación admitida por el procesador de variantes al método. Cuando el navegador accede a la URL de la variante, Active Storage transformará perezosamente el blob original en el formato especificado y redirigirá a su nueva ubicación de servicio.

<%= image_tag user.avatar.variant(resize_to_limit: [100, 100]) %>

Si se solicita una variante, Active Storage aplicará automáticamente transformaciones dependiendo del formato de la imagen:

  1. Los tipos de contenido que son variables (según lo dictado por config.active_storage.variable_content_types) y no se consideran imágenes web (según lo dictado por config.active_storage.web_image_content_types), serán convertidos a PNG.

  2. Si quality no está especificado, se usará la calidad predeterminada del procesador de variantes para el formato.

Active Storage puede usar ya sea Vips o MiniMagick como procesador de variantes. La configuración predeterminada depende de tu versión config.load_defaults, y el procesador puede ser cambiado configurando config.active_storage.variant_processor.

Los parámetros disponibles están definidos por la gema image_processing y dependen del procesador de variantes que estés usando, pero ambos admiten los siguientes parámetros:

Parámetro Ejemplo Descripción
resize_to_limit resize_to_limit: [100, 100] Reduce el tamaño de la imagen para ajustarse dentro de las dimensiones especificadas mientras conserva la relación de aspecto original. Solo redimensionará la imagen si es más grande que las dimensiones especificadas.
resize_to_fit resize_to_fit: [100, 100] Redimensiona la imagen para ajustarse dentro de las dimensiones especificadas mientras conserva la relación de aspecto original. Reducirá el tamaño de la imagen si es más grande que las dimensiones especificadas o aumentará si es más pequeña.
resize_to_fill resize_to_fill: [100, 100] Redimensiona la imagen para llenar las dimensiones especificadas mientras conserva la relación de aspecto original. Si es necesario, recortará la imagen en la dimensión más grande.
resize_and_pad resize_and_pad: [100, 100] Redimensiona la imagen para ajustarse dentro de las dimensiones especificadas mientras conserva la relación de aspecto original. Si es necesario, rellenará el área restante con color transparente si la imagen de origen tiene canal alfa, negro de lo contrario.
crop crop: [20, 50, 300, 300] Extrae un área de una imagen. Los dos primeros argumentos son los bordes izquierdo y superior del área a extraer, mientras que los dos últimos argumentos son el ancho y la altura del área a extraer.
rotate rotate: 90 Rota la imagen por el ángulo especificado.

image_processing tiene todos los parámetros disponibles en su propia documentación para ambos procesadores Vips y MiniMagick.

Algunos parámetros, incluidos los listados arriba, aceptan opciones adicionales específicas del procesador que pueden pasarse como pares key: value dentro de un hash:

<!-- Vips admite configurar `crop` para muchas de sus transformaciones -->
<%= image_tag user.avatar.variant(resize_to_fill: [100, 100, { crop: :centre }]) %>

Si migras una aplicación existente entre MiniMagick y Vips, las opciones específicas del procesador deberán actualizarse:

<!-- MiniMagick -->
<%= image_tag user.avatar.variant(resize_to_limit: [100, 100], format: :jpeg, sampling_factor: "4:2:0", strip: true, interlace: "JPEG", colorspace: "sRGB", quality: 80) %>

<!-- Vips -->
<%= image_tag user.avatar.variant(resize_to_limit: [100, 100], format: :jpeg, saver: { subsample_mode: "on", strip: true, interlace: true, quality: 80 }) %>

8.3 Previsualizando Archivos

Algunos archivos que no son imágenes pueden ser previsualizados: es decir, pueden presentarse como imágenes. Por ejemplo, un archivo de video puede ser previsualizado extrayendo su primer fotograma. De inmediato, Active Storage admite previsualizar videos y documentos PDF. Para crear un enlace a una previsualización generada perezosamente, usa el método preview del adjunto:

<%= image_tag message.video.preview(resize_to_limit: [100, 100]) %>

Para agregar soporte para otro formato, agrega tu propio previsualizador. Consulta la documentación de ActiveStorage::Preview para más información.

9 Cargas Directas

Active Storage, con su biblioteca JavaScript incluida, admite cargar directamente desde el cliente a la nube.

9.1 Uso

  1. Incluye activestorage.js en el paquete JavaScript de tu aplicación.

    Usando el pipeline de activos:

    //= require activestorage
    

    Usando el paquete npm:

    import * as ActiveStorage from "@rails/activestorage"
    ActiveStorage.start()
    
  2. Agrega direct_upload: true a tu campo de archivo:

    <%= form.file_field :attachments, multiple: true, direct_upload: true %>
    

    O, si no estás usando un FormBuilder, agrega el atributo de datos directamente:

    <input type="file" data-direct-upload-url="<%= rails_direct_uploads_url %>" />
    
  3. Configura CORS en servicios de almacenamiento de terceros para permitir solicitudes de carga directa.

  4. ¡Eso es todo! Las cargas comienzan al enviar el formulario.

9.2 Configuración de CORS (Cross-Origin Resource Sharing)

Para hacer que las cargas directas a un servicio de terceros funcionen, deberás configurar el servicio para permitir solicitudes de origen cruzado desde tu aplicación. Consulta la documentación de CORS para tu servicio:

Toma en cuenta permitir:

  • Todos los orígenes desde los cuales se accede a tu aplicación
  • El método de solicitud PUT
  • Los siguientes encabezados:
    • Content-Type
    • Content-MD5
    • Content-Disposition (excepto para Azure Storage)
    • x-ms-blob-content-disposition (solo para Azure Storage)
    • x-ms-blob-type (solo para Azure Storage)
    • Cache-Control (para GCS, solo si cache_control está configurado)

No se requiere configuración de CORS para el servicio de Disco ya que comparte el origen de tu aplicación.

9.2.1 Ejemplo: Configuración de CORS para S3

[
  {
    "AllowedHeaders": [
      "Content-Type",
      "Content-MD5",
      "Content-Disposition"
    ],
    "AllowedMethods": [
      "PUT"
    ],
    "AllowedOrigins": [
      "https://www.example.com"
    ],
    "MaxAgeSeconds": 3600
  }
]

9.2.2 Ejemplo: Configuración de CORS para Google Cloud Storage

[
  {
    "origin": ["https://www.example.com"],
    "method": ["PUT"],
    "responseHeader": ["Content-Type", "Content-MD5", "Content-Disposition"],
    "maxAgeSeconds": 3600
  }
]

9.2.3 Ejemplo: Configuración de CORS para Azure Storage

<Cors>
  <CorsRule>
    <AllowedOrigins>https://www.example.com</AllowedOrigins>
    <AllowedMethods>PUT</AllowedMethods>
    <AllowedHeaders>Content-Type, Content-MD5, x-ms-blob-content-disposition, x-ms-blob-type</AllowedHeaders>
    <MaxAgeInSeconds>3600</MaxAgeInSeconds>
  </CorsRule>
</Cors>

9.3 Eventos de JavaScript para Cargas Directas

Nombre del evento Objetivo del evento Datos del evento (event.detail) Descripción
direct-uploads:start <form> Ninguno Se envió un formulario que contiene archivos para campos de carga directa.
direct-upload:initialize <input> {id, file} Despachado para cada archivo después de enviar el formulario.
direct-upload:start <input> {id, file} Una carga directa está comenzando.
direct-upload:before-blob-request <input> {id, file, xhr} Antes de hacer una solicitud a tu aplicación para obtener metadatos de carga directa.
direct-upload:before-storage-request <input> {id, file, xhr} Antes de hacer una solicitud para almacenar un archivo.
direct-upload:progress <input> {id, file, progress} A medida que progresan las solicitudes para almacenar archivos.
direct-upload:error <input> {id, file, error} Ocurrió un error. Se mostrará una alerta a menos que se cancele este evento.
direct-upload:end <input> {id, file} Una carga directa ha terminado.
direct-uploads:end <form> Ninguno Todas las cargas directas han terminado.

9.4 Ejemplo

Puedes usar estos eventos para mostrar el progreso de una carga.

direct-uploads

Para mostrar los archivos cargados en un formulario:

// direct_uploads.js

addEventListener("direct-upload:initialize", event => {
  const { target, detail } = event
  const { id, file } = detail
  target.insertAdjacentHTML("beforebegin", `
    <div id="direct-upload-${id}" class="direct-upload direct-upload--pending">
      <div id="direct-upload-progress-${id}" class="direct-upload__progress" style="width: 0%"></div>
      <span class="direct-upload__filename"></span>
    </div>
  `)
  target.previousElementSibling.querySelector(`.direct-upload__filename`).textContent = file.name
})

addEventListener("direct-upload:start", event => {
  const { id } = event.detail
  const element = document.getElementById(`direct-upload-${id}`)
  element.classList.remove("direct-upload--pending")
})

addEventListener("direct-upload:progress", event => {
  const { id, progress } = event.detail
  const progressElement = document.getElementById(`direct-upload-progress-${id}`)
  progressElement.style.width = `${progress}%`
})

addEventListener("direct-upload:error", event => {
  event.preventDefault()
  const { id, error } = event.detail
  const element = document.getElementById(`direct-upload-${id}`)
  element.classList.add("direct-upload--error")
  element.setAttribute("title", error)
})

addEventListener("direct-upload:end", event => {
  const { id } = event.detail
  const element = document.getElementById(`direct-upload-${id}`)
  element.classList.add("direct-upload--complete")
})

Agrega estilos:

/* direct_uploads.css */

.direct-upload {
  display: inline-block;
  position: relative;
  padding: 2px 4px;
  margin: 0 3px 3px 0;
  border: 1px solid rgba(0, 0, 0, 0.3);
  border-radius: 3px;
  font-size: 11px;
  line-height: 13px;
}

.direct-upload--pending {
  opacity: 0.6;
}

.direct-upload__progress {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  opacity: 0.2;
  background: #0076ff;
  transition: width 120ms ease-out, opacity 60ms 60ms ease-in;
  transform: translate3d(0, 0, 0);
}

.direct-upload--complete .direct-upload__progress {
  opacity: 0.4;
}

.direct-upload--error {
  border-color: red;
}

input[type=file][data-direct-upload-url][disabled] {
  display: none;
}

9.5 Soluciones personalizadas de arrastrar y soltar

Puedes usar la clase DirectUpload para este propósito. Al recibir un archivo de tu biblioteca de elección, instancia un DirectUpload y llama a su método create. Create toma un callback para invocar cuando la carga se completa.

import { DirectUpload } from "@rails/activestorage"

const input = document.querySelector('input[type=file]')

// Vincula al arrastre de archivos - usa el ondrop en un elemento padre o usa una
//  biblioteca como Dropzone
const onDrop = (event) => {
  event.preventDefault()
  const files = event.dataTransfer.files;
  Array.from(files).forEach(file => uploadFile(file))
}

// Vincula a la selección normal de archivos
input.addEventListener('change', (event) => {
  Array.from(input.files).forEach(file => uploadFile(file))
  // podrías borrar los archivos seleccionados del input
  input.value = null
})

const uploadFile = (file) => {
  // tu formulario necesita el campo de archivo direct_upload: true, que
  //  proporciona data-direct-upload-url
  const url = input.dataset.directUploadUrl
  const upload = new DirectUpload(file, url)

  upload.create((error, blob) => {
    if (error) {
      // Maneja el error
    } else {
      // Agrega un input oculto con un nombre apropiado al formulario con un
      //  valor de blob.signed_id para que los IDs de blob sean
      //  transmitidos en el flujo de carga normal
      const hiddenField = document.createElement('input')
      hiddenField.setAttribute("type", "hidden");
      hiddenField.setAttribute("value", blob.signed_id);
      hiddenField.name = input.name
      document.querySelector('form').appendChild(hiddenField)
    }
  })
}

9.6 Seguimiento del progreso de la carga de archivos

Cuando usas el constructor DirectUpload, es posible incluir un tercer parámetro. Esto permitirá que el objeto DirectUpload invoque el método directUploadWillStoreFileWithXHR durante el proceso de carga. Luego puedes adjuntar tu propio manejador de progreso al XHR para satisfacer tus necesidades.

import { DirectUpload } from "@rails/activestorage"

class Uploader {
  constructor(file, url) {
    this.upload = new DirectUpload(file, url, this)
  }

  uploadFile(file) {
    this.upload.create((error, blob) => {
      if (error) {
        // Maneja el error
      } else {
        // Agrega un input oculto con un nombre apropiado al formulario
        // con un valor de blob.signed_id
      }
    })
  }

  directUploadWillStoreFileWithXHR(request) {
    request.upload.addEventListener("progress",
      event => this.directUploadDidProgress(event))
  }

  directUploadDidProgress(event) {
    // Usa event.loaded y event.total para actualizar la barra de progreso
  }
}

9.7 Integración con Bibliotecas o Frameworks

Una vez que recibas un archivo de la biblioteca que has seleccionado, necesitas crear una instancia de DirectUpload y usar su método "create" para iniciar el proceso de carga, agregando cualquier encabezado adicional requerido según sea necesario. El método "create" también requiere que se proporcione una función de callback que se activará una vez que la carga haya terminado.

import { DirectUpload } from "@rails/activestorage"

class Uploader {
  constructor(file, url, token) {
    const headers = { 'Authentication': `Bearer ${token}` }
    // INFO: Enviar encabezados es un parámetro opcional. Si eliges no enviar encabezados,
    //       la autenticación se realizará usando cookies o datos de sesión.
    this.upload = new DirectUpload(file, url, this, headers)
  }

  uploadFile(file) {
    this.upload.create((error, blob) => {
      if (error) {
        // Maneja el error
      } else {
        // Usa el blob.signed_id como referencia de archivo en la siguiente solicitud
      }
    })
  }

  directUploadWillStoreFileWithXHR(request) {
    request.upload.addEventListener("progress",
      event => this.directUploadDidProgress(event))
  }

  directUploadDidProgress(event) {
    // Usa event.loaded y event.total para actualizar la barra de progreso
  }
}

Para implementar autenticación personalizada, se debe crear un nuevo controlador en la aplicación Rails, similar al siguiente:

class DirectUploadsController < ActiveStorage::DirectUploadsController
  skip_forgery_protection
  before_action :authenticate!

  def authenticate!
    @token = request.headers['Authorization']&.split&.last

    head :unauthorized unless valid_token?(@token)
  end
end

NOTA: Usar Cargas Directas puede a veces resultar en un archivo que se carga, pero nunca se adjunta a un registro. Considera purgar cargas no adjuntas.

10 Pruebas

Usa file_fixture_upload para probar la carga de un archivo en una prueba de integración o controlador. Rails maneja los archivos como cualquier otro parámetro.

class SignupController < ActionDispatch::IntegrationTest
  test "can sign up" do
    post signup_path, params: {
      name: "David",
      avatar: file_fixture_upload("david.png", "image/png")
    }

    user = User.order(:created_at).last
    assert user.avatar.attached?
  end
end

10.1 Descartando Archivos Creados Durante Pruebas

10.1.1 Pruebas del Sistema

Las pruebas del sistema limpian los datos de prueba mediante la reversión de una transacción. Debido a que destroy nunca se llama en un objeto, los archivos adjuntos nunca se limpian. Si deseas limpiar los archivos, puedes hacerlo en un callback after_teardown. Hacerlo aquí asegura que todas las conexiones creadas durante la prueba estén completas y no recibirás un error de Active Storage diciendo que no puede encontrar un archivo.

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  # ...
  def after_teardown
    super
    FileUtils.rm_rf(ActiveStorage::Blob.service.root)
  end
  # ...
end

Si estás usando pruebas paralelas y el DiskService, debes configurar cada proceso para usar su propia carpeta para Active Storage. De esta manera, el callback teardown solo eliminará archivos de las pruebas del proceso relevante.

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  # ...
  parallelize_setup do |i|
    ActiveStorage::Blob.service.root = "#{ActiveStorage::Blob.service.root}-#{i}"
  end
  # ...
end

Si tus pruebas del sistema verifican la eliminación de un modelo con adjuntos y estás usando Active Job, configura tu entorno de prueba para usar el adaptador de cola en línea para que el trabajo de purga se ejecute inmediatamente en lugar de en un momento desconocido en el futuro.

# Usa el procesamiento de trabajos en línea para hacer que las cosas sucedan inmediatamente
config.active_job.queue_adapter = :inline

10.1.2 Pruebas de Integración

De manera similar a las Pruebas del Sistema, los archivos cargados durante las Pruebas de Integración no se limpiarán automáticamente. Si deseas limpiar los archivos, puedes hacerlo en un callback teardown.

class ActionDispatch::IntegrationTest
  def after_teardown
    super
    FileUtils.rm_rf(ActiveStorage::Blob.service.root)
  end
end

Si estás usando pruebas paralelas y el servicio de Disco, debes configurar cada proceso para usar su propia carpeta para Active Storage. De esta manera, el callback teardown solo eliminará archivos de las pruebas del proceso relevante.

class ActionDispatch::IntegrationTest
  parallelize_setup do |i|
    ActiveStorage::Blob.service.root = "#{ActiveStorage::Blob.service.root}-#{i}"
  end
end

10.2 Agregando Adjuntos a Fixtures

Puedes agregar adjuntos a tus fixtures existentes. Primero, querrás crear un servicio de almacenamiento separado:

# config/storage.yml

test_fixtures:
  service: Disk
  root: <%= Rails.root.join("tmp/storage_fixtures") %>

Esto le dice a Active Storage dónde "cargar" archivos de fixture, por lo que debe ser un directorio temporal. Al hacer que sea un directorio diferente al servicio test regular, puedes separar archivos de fixture de archivos cargados durante una prueba.

A continuación, crea archivos de fixture para las clases de Active Storage:

# active_storage/attachments.yml
david_avatar:
  name: avatar
  record: david (User)
  blob: david_avatar_blob
# active_storage/blobs.yml
david_avatar_blob: <%= ActiveStorage::FixtureSet.blob filename: "david.png", service_name: "test_fixtures" %>

Luego coloca un archivo en tu directorio de fixtures (la ruta predeterminada es test/fixtures/files) con el nombre de archivo correspondiente. Consulta la documentación de ActiveStorage::FixtureSet para más información.

Una vez que todo esté configurado, podrás acceder a los adjuntos en tus pruebas:

class UserTest < ActiveSupport::TestCase
  def test_avatar
    avatar = users(:david).avatar

    assert avatar.attached?
    assert_not_nil avatar.download
    assert_equal 1000, avatar.byte_size
  end
end

10.2.1 Limpiando Fixtures

Mientras que los archivos cargados en las pruebas se limpian al final de cada prueba, solo necesitas limpiar los archivos de fixture una vez: cuando todas tus pruebas se completen.

Si estás usando pruebas paralelas, llama a parallelize_teardown:

class ActiveSupport::TestCase
  # ...
  parallelize_teardown do |i|
    FileUtils.rm_rf(ActiveStorage::Blob.services.fetch(:test_fixtures).root)
  end
  # ...
end

Si no estás ejecutando pruebas paralelas, usa Minitest.after_run o el equivalente para tu marco de prueba (por ejemplo, after(:suite) para RSpec):

# test_helper.rb

Minitest.after_run do
  FileUtils.rm_rf(ActiveStorage::Blob.services.fetch(:test_fixtures).root)
end

10.3 Configurando servicios

Puedes agregar config/storage/test.yml para configurar servicios que se usarán en el entorno de prueba. Esto es útil cuando se usa la opción service.

class User < ApplicationRecord
  has_one_attached :avatar, service: :s3
end

Sin config/storage/test.yml, el servicio s3 configurado en config/storage.yml se usa, incluso cuando se ejecutan pruebas.

La configuración predeterminada se usaría y los archivos se cargarían en el proveedor de servicios configurado en config/storage.yml.

En este caso, puedes agregar config/storage/test.yml y usar el servicio de Disco para el servicio s3 para evitar enviar solicitudes.

test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

s3:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

11 Implementando Soporte para Otros Servicios en la Nube

Si necesitas admitir un servicio en la nube diferente a estos, deberás implementar el Servicio. Cada servicio extiende ActiveStorage::Service implementando los métodos necesarios para cargar y descargar archivos a la nube.

12 Purgando Cargas No Adjuntas

Hay casos en los que se carga un archivo pero nunca se adjunta a un registro. Esto puede suceder cuando se usan Cargas Directas. Puedes consultar registros no adjuntos usando el alcance unattached. A continuación se muestra un ejemplo usando una tarea rake personalizada.

namespace :active_storage do
  desc "Purga blobs de Active Storage no adjuntos. Ejecutar regularmente."
  task purge_unattached: :environment do
    ActiveStorage::Blob.unattached.where(created_at: ..2.days.ago).find_each(&:purge_later)
  end
end

ADVERTENCIA: La consulta generada por ActiveStorage::Blob.unattached puede ser lenta y potencialmente disruptiva en aplicaciones con bases de datos más grandes.


Comentarios

Se te anima a ayudar a mejorar la calidad de esta guía.

Por favor contribuye si ves algún error tipográfico o errores fácticos. Para comenzar, puedes leer nuestra sección de contribuciones a la documentación.

También puedes encontrar contenido incompleto o cosas que no están actualizadas. Por favor agrega cualquier documentación faltante para main. Asegúrate de revisar Guías Edge primero para verificar si los problemas ya están resueltos o no en la rama principal. Revisa las Guías de Ruby on Rails para estilo y convenciones.

Si por alguna razón detectas algo que corregir pero no puedes hacerlo tú mismo, por favor abre un issue.

Y por último, pero no menos importante, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.