Como alterar uma coluna anulável para não anulável em uma migração do Rails?

188

Criei uma coluna de data em uma migração anterior e a defini como anulável. Agora eu quero alterá-lo para não ser anulável. Como eu faço isso assumindo que existem linhas nulas nesse banco de dados? Eu estou bem em definir essas colunas como Time.now se elas estiverem atualmente nulas.

Kevin Pang
fonte

Respostas:

204

Se você fizer isso em uma migração, provavelmente poderá fazer o seguinte:

# 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
fonte
1
Apenas uma observação, porque isso me fez quebrar meu banco de dados de desenvolvimento. Prefere usar sintaxe de hash explícito, assim: MyModel.update_all({:date_column => Time.now}, {:date_column => nil}). A consulta em sua forma original apenas fez com que todos os meus modelos tenham valor nulo no campo.
dimitarvp
Obrigado pela atualização. Sei que esse não foi o caso quando escrevi essa resposta, mas não me lembro qual versão do Ruby ou RoR eu estava usando no momento.
DanneManne
1
Você usa o método 'up' / 'down' nesta migração ou pode usar o método de alteração simples na migração?
EE33
2
O changemétodo não é tão adequado para este caso porque (1) o update_allmétodo será executado na migração e em uma reversão em potencial. Isso pode não ser a pior coisa, mas porque (2) a migração não tem como saber do que a coluna foi alterada em uma possível reversão. Portanto, neste caso, eu ficaria com upe down.
precisa saber é o seguinte
2
Para qualquer pessoa interessada, minha resposta mostra como fazer isso em uma única etapa.
Rick Smith
167

No Rails 4, esta é uma solução melhor (DRYer):

change_column_null :my_models, :date_column, false

Para garantir que não existam registros com NULLvalores nessa coluna, você pode passar um quarto parâmetro, que é o valor padrão a ser usado para registros com NULLvalores:

change_column_null :my_models, :date_column, false, Time.now
mrbrdo
fonte
4
Isso causa problemas quando a tabela já possui valores nulos. Veja minha resposta
Rick Smith
5
Também disponível em 3.2. Também possui um quarto parâmetro para definir o padrão em que o valor é nulo.
toxaq
1
Mais 1 para change_column_null. No entanto, o comentário de Rick Smith acima indica um caso muito válido.
0112 de
Atualizado para adicionar a consulta para atualizar valores nulos. O quarto parâmetro (valor padrão) é útil apenas quando você também deseja ter um padrão para registros futuros.
Mrbrdo
3
Na verdade, de acordo com os documentos do Rails 4.2, o quarto parâmetro NÃO define um valor padrão para registros futuros: "O método aceita um quarto argumento opcional para substituir os + NULL + s existentes por algum outro valor. Observe que o quarto argumento não define o padrão de uma coluna ".
Mike Fischer
70

Rails 4 (outras respostas do Rails 4 têm problemas):

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

Alterar uma coluna com valores NULL para não permitir NULL causará problemas. Esse é exatamente o tipo de código que funcionará bem na sua configuração de desenvolvimento e falhará quando você tentar implantá-lo na sua produção do LIVE . Você deve primeiro alterar valores NULL para algo válido e, em seguida, desabilitar NULLs. O quarto valor em change_column_nullfaz exatamente isso. Veja a documentação para mais detalhes.

Além disso, geralmente prefiro definir um valor padrão para o campo, portanto não precisarei especificar o valor do campo toda vez que criar um novo objeto. Incluí o código comentado para fazer isso também.

Rick Smith
fonte
3
Para o Rails 4, essa parece ser a resposta mais precisa e completa, incluindo a configuração padrão comentada.
Mike Fischer
4
Se você estiver adicionando uma nova coluna a uma tabela e quiser inserir novos valores para null, mas não quiser adicionar um valor padrão para a coluna, poderá fazer isso na sua migração: add_column :users, :admin, :stringthenchange_column_null(:admin, :string, false, "new_value_for_existing_records")
colsen
34

Crie uma migração que tenha uma change_columninstrução com um :default =>valor.

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

Veja: change_column

Dependendo do mecanismo do banco de dados, você pode precisar usar change_column_null

jessecurry
fonte
1
Isso funcionou para mim. Usando o MySql localmente. Quando pressionado e executado, o aplicativo no Heroku (Postgres) era uma porcaria na coluna que não era nula quando eu escrevia um nulo - com razão. Somente "change_column_null" funcionaria não poderia usar "change_column ...: null => false" no MySql. Obrigado.
Rtfminc 03/07
1
Então, qual foi a sua migração após change_column_null
js111
1
Postges é mais rigoroso que o MySQL - eu esperaria que fosse necessário change_column_null.
Jessecurry
3
@rtfminc Eu recomendo fortemente que você use o mesmo mecanismo de banco de dados no desenvolvimento e na produção, pois evita muitos problemas quando se trata de casos extremos.
yagooar
12

Trilhos 4:

def change
  change_column_null(:users, :admin, false )
end
piratebroadcast
fonte
1
Forneça uma descrição de suas respostas.
Wahyu Kristianto
3

No Rails 4.02+, de acordo com os documentos, não há método como update_allcom 2 argumentos. Em vez disso, pode-se 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
fonte
2

Você não pode usar add_timestamps e null: false se você tiver registros existentes, então aqui está a solução:

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
fonte