En Buk, la plataforma está siempre en constante crecimiento y aplicando mejoras, por lo que las migraciones son una parte esencial de la aplicación. Estas nacen por nuevos requerimientos, o por razones que en un inicio no pudimos divisar o que estaban fuera del alcance inicial de una tarjeta. Esto hace que necesitemos migraciones que crean nuevas tablas, agreguen columnas nuevas, eliminen atributos existentes, la necesidad de nuevas claves foráneas, etc.
A simple vista, estas nuevas necesidades en nuestra base de datos no se resuelven con un código complejo, pero cuando ya tienes una aplicación lo suficientemente grande, utilizada por centenares de usuarios y donde además trabajan en la aplicación decenas de desarrolladores, una migración que no está bien pensada puede generar varios problemas. Estos problemas pueden ser:
Cuando hacemos una migración, independiente de lo que esta implique, debemos siempre hacernos las siguientes preguntas: ¿la aplicación va correr de manera correcta después de ejecutar la migración? Si la base de datos es lo suficientemente grande, ¿esto no genera problemas en el proceso de deploy? Para ayudarte en este proceso clave es necesario hacer unas migraciones fuertes o mejor conocidas ‘Strong Migration’
Son una forma de hacer migraciones de tal manera que sean lo menos invasivas a la aplicación, y que al ejecutarlas, no afecte en su funcionamiento. Estas se realizan con pequeños cambios que van directos a tu aplicación en producción, para que en el caso de que algún pase falle, revertir el cambio no sea complejo. Uno de los casos más típicos donde se necesita crear un strong migration, es agregar una columna NOT NULL
con un valor por defecto.
Un ejemplo de un caso específico: supongamos que tienes la tabla Restaurante
y sus atributos son nombre
/ estilo
/ rating
y que a este modelo necesitas agregarle el atributo vegetariano
de tipo booleano para saber si cumple esta condición o no, y quieres que este valor sea NOT NULL
y además por default tenga valor False
.
Usando el ejemplo anterior, tenemos que relizar una seguidilla de pasos y para este caso nos vamos a basar en un ejemplo de strong migration en el framework Ruby on Rails. Cada paso es un cambio directo a tu rama de producción, para así estar seguros del funcionamiento del cambio que se quiere hacer.
En el modelo Restaurante
donde quiero agregar la columna, lo primero que hay que hacer es ignorar la columna que se quiere agregar, en este caso vegetariano
. Esto no afecta al funcionamiento, ya que se trata de ignorar algo que no existe (todavia)
self.ignored_columns = [:vegetariano]
Ya que la columna está ignorada, para efectos de aplicación esta columna no existe, entonces agregarla a la migración que crea la columna no va afectar al desarrollo de la aplicación. Esta migración tiene que tener el valor por defecto que indicamos más arriba, y así cada vez que se crea un nuevo registro en el modelo, guardará su valor. Ojo que aún no se le define como NOT NULL
.
def up
add_column :restaurante, :vegetariano, :boolean
change_column_default :restaurante, :vegetariano, false
end
Ahora se tiene que hacer un cambio que elimine el ignored_columns
agregado en el paso 1. Además se tiene que agregar que, cada vez que se cree un nuevo registro en el modelo, se le asigne el valor por defecto. Esto se realiza para evitar por alguna razón que exista un valor null en la columna al momento de editar o crear un registro. Para esto en general se usa el callback before_save
en el modelo.
before_save do
self.vegetariano = false if vegetariano.nil?
end
Tambien, dependiendo el caso, se tiene que cambiar las partes de la aplicación donde se crea o edita algún registro del modelo para evitar lo anteriormente explicado
Ya que la columna vegetariano
se agregó en pleno funcionamiento, es posible que algún registro en el pasado haya quedado con el valor NULL
y así evitamos que en el paso siguiente de dejar la columna como NOT NULL
no se ejecute un error en la app. Para hacer esto se crea una migración de la siguiente manera
def up
Restaurante.where(vegetariano: nil).in_batches do |rows|
row.update_all(vegetariano: false)
end
end
Se aplica en in_batches
para que en el proceso de deploy no sea tan invasivo el cambio, porque puede que el modelo tenga muchos datos.
NOT NULL
Ya que sabemos con certeza que no existen valores NULL
en la columna por todos los pasos anteriores, podemos ahora crear una migración para que la columna vegetariano
sea NOT NULL
def up
change_column_null :restaurante, :vegetariano, false
end
Puede que este proceso sea un poco lento pero ayuda que al crear un nueva columna o algún cambio importante en la base datos esto no afecte al funcionamiento de la aplicación.
Esta es una de las tantas formas de crear una strong migration, ya que hay muchos otros casos de aplicación. Por ejemplo agregar un foreign key
, cambiar el nombre de una tabla, eliminar una columna, etc. La idea es siempre aplicar esta filosofía al momento de hacer un cambio importante en la base de datos.
Si te interesó este y muchos otros artículos que tratamos en este blog, no dudes en revisar las oportunidades de trabajo que tenemos en Buk y ¡únete a nosotros!
En este repositorio hay otros casos como este en StrongMigrations