¿Cómo cambiar una columna anulable a no anulable en una migración de Rails?

188

Creé una columna de fecha en una migración anterior y la configuré como anulable. Ahora quiero cambiarlo para que no sea anulable. ¿Cómo hago para hacer esto suponiendo que haya filas nulas en esa base de datos? Estoy de acuerdo con establecer esas columnas en Time.now si actualmente son nulas.

Kevin Pang
fuente

Respuestas:

204

Si lo hace en una migración, probablemente podría hacerlo así:

# Make sure no null value exist
MyModel.where(date_column: nil).update_all(date_column: Time.now)

# Change the column to not allow null
change_column :my_models, :date_column, :datetime, null: false
DanneManne
fuente
1
Solo una nota, porque esto me hizo reventar mi base de datos de desarrollo. En lugar de hash utilizar la sintaxis explícita, como esto: MyModel.update_all({:date_column => Time.now}, {:date_column => nil}). La consulta en su forma original acaba de hacer que todos mis modelos tengan un valor nulo en el campo.
dimitarvp
Gracias por la actualización. Sé que este no fue el caso cuando escribí esta respuesta, pero no recuerdo qué versión de Ruby o RoR estaba usando en ese momento.
DanneManne
1
¿Tiene el uso del método 'arriba' / 'abajo' en esta migración, o puede usar el método de cambio simple en la migración?
EE33
2
El changemétodo no es tan adecuado para este caso porque (1) el update_allmétodo se ejecutará tanto en la migración como en una posible reversión. Puede que eso no sea lo peor, pero debido a que (2) la migración no tiene forma de saber de qué se cambió la columna en una posible reversión. Entonces, para este caso, me quedaría con upy down.
DanneManne
2
Para cualquier persona interesada, mi respuesta muestra cómo hacer esto en un solo paso.
Rick Smith
167

En Rails 4, esta es una mejor solución (DRYer):

change_column_null :my_models, :date_column, false

Para asegurarse de que no existan registros con NULLvalores en esa columna, puede pasar un cuarto parámetro, que es el valor predeterminado para los registros con NULLvalores:

change_column_null :my_models, :date_column, false, Time.now
mrbrdo
fuente
44
Esto causa problemas cuando la tabla ya tiene valores nulos. Ver mi respuesta
Rick Smith
55
También disponible en 3.2. Tiene un cuarto parámetro también para establecer el valor predeterminado donde los valores son nulos.
toxaq
1
Más 1 para change_column_null. Sin embargo, el comentario de Rick Smith anterior señala un caso muy válido.
0112
Actualizado para agregar la consulta para actualizar valores nulos. El cuarto parámetro (valor predeterminado) solo es útil cuando realmente desea tener un valor predeterminado para registros futuros también.
mrbrdo
3
En realidad, de acuerdo con los documentos de Rails 4.2, el cuarto parámetro NO establece un valor predeterminado para registros futuros: "El método acepta un cuarto argumento opcional para reemplazar los + NULL + s existentes con algún otro valor. Tenga en cuenta que el cuarto argumento no establece el valor predeterminado de una columna ".
Mike Fischer
70

Rails 4 (otras respuestas de Rails 4 tienen problemas):

def change
  change_column_null(:users, :admin, false, <put a default value here> )
  # change_column(:users, :admin, :string, :default => "")
end

Cambiar una columna con valores NULL para no permitir NULL causará problemas. Este es exactamente el tipo de código que funcionará bien en su configuración de desarrollo y luego se bloqueará cuando intente implementarlo en su producción EN VIVO . Primero debe cambiar los valores NULL a algo válido y luego no permitir NULL. El cuarto valor en change_column_nullhace exactamente eso. Ver documentación para más detalles.

Además, generalmente prefiero establecer un valor predeterminado para el campo, por lo que no necesitaré especificar el valor del campo cada vez que creo un nuevo objeto. Incluí el código comentado para hacer eso también.

Rick Smith
fuente
3
Para Rails 4, esta parece ser la respuesta más precisa y completa, incluida la configuración predeterminada comentada.
Mike Fischer
44
Si está agregando una nueva columna a una tabla y desea insertar nuevos valores para nulo, pero no desea agregar un valor predeterminado para la columna, puede hacerlo en su migración: add_column :users, :admin, :stringentonceschange_column_null(:admin, :string, false, "new_value_for_existing_records")
colsen
34

Cree una migración que tenga una change_columndeclaración con un :default =>valor.

change_column :my_table, :my_column, :integer, :default => 0, :null => false

Ver: change_column

Dependiendo del motor de la base de datos, es posible que deba usar change_column_null

jessecurry
fuente
1
Esto funcionó para mí. Usando MySql localmente. Cuando se presionó y ejecutó la aplicación en Heroku (Postgres), se quebró en una columna que no era nula cuando la escribía nula, con razón. Solo "change_column_null" funcionaría no podría usar "change_column ...: null => false" en MySql. Gracias.
rtfminc
1
Entonces
¿
1
Postges es más estricto que MySQL. Espero que lo requiera change_column_null.
jessecurry
3
@rtfminc Le recomiendo encarecidamente que use el mismo motor de base de datos en el desarrollo y en la producción, ya que evita muchos problemas cuando se trata de casos extremos.
yagooar 23/03/2013
12

Carriles 4:

def change
  change_column_null(:users, :admin, false )
end
transmisión pirata
fuente
1
Proporcione una descripción de sus respuestas.
Wahyu Kristianto
3

En Rails 4.02+ según los documentos no hay ningún método como update_allcon 2 argumentos. En cambio, uno puede usar este código:

# Make sure no null value exist
MyModel.where(date_column: nil).update_all(date_column: Time.now)

# Change the column to not allow null
change_column :my_models, :date_column, :datetime, null: false
jmarceli
fuente
2

No puede usar add_timestamps y null: false si tiene registros existentes, así que aquí está la solución:

def change
  add_timestamps(:buttons, null: true)

  Button.find_each { |b| b.update(created_at: Time.zone.now, updated_at: Time.zone.now) }

  change_column_null(:buttons, :created_at, false)
  change_column_null(:buttons, :updated_at, false)
end
Nicolas Maloeuvre
fuente