La guia de platanus
  • README
  • Acuerdos
    • Guía de Estilo
      • Ejemplo: Módulo para variables de entorno
  • Stack
    • Getting Started
    • Nuestro MVC extendido
    • Ruby/Rails
      • Power Types
        • General
        • Patrones
          • Commands
          • Utils
          • Services
          • Values
          • Observers
      • Potassium
      • Power API
      • Active Admin
        • General
        • Active Admin Addons
      • Pundit
      • Shrine
        • General
        • Manejo y procesamiento de imágenes
      • Pry
      • Strong Migrations
      • Data Migrate
      • Active Job
      • Gems
      • Engines - Modularización en Rails
    • JavaScript
      • Vue
        • General
        • Testing
      • AlpineJS
    • CSS
    • Mobile
      • Expo
      • React Navigation
      • Redux
        • Crear y conectar una slice en Redux
      • Styling
        • Usando Tailwind en React Native
      • Recursos
    • Resolviendo problemas (debugging)
    • Machine Learning
  • Setup
    • Configuración de tu entorno local
      • Instalación Base
        • OSX
        • Windows
        • Linux
      • Tecnologías
        • Ruby
        • Docker
        • Node
      • Herramientas
        • Linters
        • Editores
          • IDE/Editores de Código
            • Visual Studio Code
            • Sublime Text
        • Git
    • Configuración de proyectos
      • Getting Started
      • Heroku
      • Rails
      • Circle CI
      • Vue
      • Apple App Store
      • Google Play
      • Expo
      • S3
      • Git
      • Cloudflare
      • Sendgrid
      • Dominio + Mailing
      • Google Tag Manager, Analytics, Search Console, etc.
        • Google Tag Manager
          • Configurar Google Tag Manager
        • Google Analytics
        • Indexación en Google
        • Google Ads
      • Crear un bucket de S3
      • SlackBot
      • Google BigQuery
  • Deployment
    • Rails
    • Ruby Gems
    • Browser and Node (Open Source)
    • Mobile
      • Mobile Resources
      • Apple App Storage
      • Google Play
  • Upgrades
    • Upgrade de Vue 2 a Vue 3
    • Migración Hound → reviewdog
    • Upgrade de Postgresql
Con tecnología de GitBook
En esta página
  • ¿Por qué la usamos?
  • ¿Cómo la usamos?
  • Instalación
  • Generar nueva migración de data
  • Corriendo las migraciones de schema junto a las de data
  • Posibles problemas relacionados al deploy
  • Recursos útiles
  1. Stack
  2. Ruby/Rails

Data Migrate

AnteriorStrong MigrationsSiguienteActive Job

Última actualización hace 2 años

es una gema que introduce el concepto de migraciones de data, que corren junto a las migraciones normales de rails que modifican el schema.

¿Por qué la usamos?

El propósito principal de las migraciones normales de Rails es cambiar el schema de la aplicación. Sin embargo, a veces es necesario hacer cambios a la data misma, y a veces esos cambios deben estar coordinados con un cambio al schema. Se podría manipular la data en una misma migración corriente de Rails, pero data_migrate nos permite separar esa responsabilidad y mantenerlo más ordenado.

¿Cómo la usamos?

Instalación

La gema viene instalada si el proyecto se generó usando . También se incluyen configuración necesaria para que se corra al usar las tasks que nos da la gema. Si por alguna razón el proyecto no tiene la gema, se puede agregar, junto a la configuración mencionada, corriendo potassium install data_migrate.

Generar nueva migración de data

rails g data_migration backfill_some_column_in_some_model

Warning: Si se crea la migración de datos junto a una de schema hay que asegurarse que no compartan el mismo nombre. Si se llamaran igual, las clases creadas en ambas migraciones también tendrían el mismo nombre y Rails se podría confundir. Ver este issue para más detalles.

Corriendo las migraciones de schema junto a las de data

El README de la gema describe todos los comandos que agrega la gema. Como se menciona ahí, también se pueden ver corriendo rake -T data.

Hay que tener en mente que en vez de correr rake db:migrate, para correr ambos tipos de migraciones juntas se debe usar rake db:migrate:with_data. Este comando también está incluido en el archivo bin/release en nuestros proyectos, para que se corra en heroku al hacer deploy.

Usar este comando es importante ya que si se corrieran primero las de schema y luego las de data podrían haber problemas. Para entender esto consideremos el siguiente ejemplo:

Teníamos un usuario con una columna string address. Recientemente se cambió el modelo de datos y address pasó a ser su propio modelo Location con información adicional. Para esto se realizaron tres migraciones:

  1. Una de schema que genera la tabla para el modelo Location y agrega la referencia a la tabla de usuarios

  2. Una de data que para cada usuario le crea una Location y parsea el contenido de la columna address al formato del nuevo modelo

  3. Una de schema que elimina la columna address de los usuarios

Si se corrieran las migraciones por separado usando rake db:migrate y luego rake data:migrate (orden 1 -> 3 -> 2) la migración de data se caería ya que se eliminó la columna address antes. Para esto usamos rake db:migrate:with_data que las corre todas en orden de creación.

Posibles problemas relacionados al deploy

Hay algunos casos en que pueden haber problemas con la aplicación en staging o producción:

  1. Cuando ocurre un problema y es necesario restaurar un backup y correr las migraciones que se hayan generado entre la fecha del backup y el presente. Esto puede generar problemas con las migraciones de datos si en ellas se accede a cosas que existían a nivel de código cuando se generó el backup pero con la versión actual del código ya no existen. Este no es un problema exclusivo de la gema, siempre que se manipule data en migraciones puede suceder esto.

    Para evitar lo anterior hay un par de alternativas:

    • Como ejemplo, digamos que tenemos un usuario que pasa de tener una columna boolean que indica si es gerente o no, a tener un string role:

      class BackfillRoleInUsers < ActiveRecord::Migration[6.0]
        class MigrationUser < ApplicationRecord
          self.table_name = :users
        end
      
        def up
          MigrationUser.all.each do |user|
            user.update!(role: user.is_manager ? 'manager' : 'worker')
          end
        end
      
        def down
          raise ActiveRecord::IrreversibleMigration
        end
      end
    • Usar ActiveRecord::Base.connection.execute(query) para correr una consulta SQL directamente, donde query es el string con esa query. Esto también nos obligaría a usar solo lo que existe en DB al momento de correr la migración.

  2. class BackfillRoleInUsers < ActiveRecord::Migration[6.0]
      class MigrationUser < ApplicationRecord
        self.table_name = :users
      end
    
      def up
        MigrationUser.reset_column_information 
        MigrationUser.all.each do |user|
          user.update!(role: user.is_manager ? 'manager' : 'worker')
        end
      end
    
      def down
        raise ActiveRecord::IrreversibleMigration
      end
    end	

Recursos útiles

Definir un modelo "temporal" en la migración de data, asociado a la tabla que se use, y usar este exclusivamente. Esto nos independiza del modelo real, y nos obliga a usar solo lo que esté efectivamente definido en la tabla al momento de correr la migración de data. Es decir, no se correrán validaciones ni callbacks del modelo original, ni tampoco se podrán usar scopes definidos ahí. Este es el approach que mencionan en la .

Cuando se corre una migración que crea una nueva columna, y una migración de data a continuación que hace backfill de esa columna. A veces pasa que la migración de data se corre sin problemas, pero los cambios en verdad no se aplicaron. Este problema es especialmente peligroso, porque es posible que en local este problema no ocurra, pero sí en staging/production. Esto pasa porque Rails cachea la información de las columnas al empezar las migraciones, y en la migración de datos se usa ese cache, por lo que el modelo no tiene esa nueva columna, entonces no sabe como guardar ese valor, pero tampoco falla porque sí existe la columna a nivel de DB (esta es una deducción de lo que hemos podido observar en casos que esto ha ocurrido). Esto puede ocurrir incluso si se hace el modelo temporal del paso anterior (suponemos que esto implica que el cache es a nivel de la tabla, no del modelo). Para evitar este problema, la recomendación es **siempre empezar las migraciones de datos reseteando la información de las columnas de todos los modelos que vayamos a usar, usando **. Con esto, la migración del punto anterior quedaría así:

: caso detallado en que hay problemas por diferencias entre el código que se usa en la migración de data y el codebase del proyecto al momento de correrla en producción. Explica también como reemplazar el código problemático por SQL directo

Data Migrate
Potassium
annotate
guía de estilo de rails de rubocop
reset_column_information
Repo
Otro ejemplo de posible problema en deploy