Bagaimana cara mengubah kolom nullable menjadi tidak nullable dalam migrasi Rails?

188

Saya membuat kolom tanggal dalam migrasi sebelumnya dan menetapkannya sebagai nullable. Sekarang saya ingin mengubahnya menjadi tidak dapat dibatalkan. Bagaimana cara saya melakukan ini dengan asumsi ada baris nol dalam database itu? Saya baik-baik saja dengan mengatur kolom-kolom itu ke Time.now sekarang jika itu nol.

Kevin Pang
sumber

Jawaban:

204

Jika Anda melakukannya dalam migrasi maka Anda mungkin bisa melakukannya seperti ini:

# 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
sumber
1
Hanya sebuah catatan, karena ini membuat saya menghancurkan basis data dev saya. Bukan menggunakan sintaks hash eksplisit, seperti ini: MyModel.update_all({:date_column => Time.now}, {:date_column => nil}). Permintaan dalam bentuk asli Anda hanya membuat semua model saya memiliki nilai nol di bidang.
dimitarvp
Terima kasih atas pembaruannya. Saya tahu ini tidak terjadi ketika saya menulis jawaban ini tetapi saya tidak dapat mengingat versi Ruby atau RoR mana yang saya gunakan saat itu.
DanneManne
1
Apakah Anda menggunakan metode 'naik' / 'turun' dalam migrasi ini, atau dapatkah Anda menggunakan metode perubahan sederhana dalam migrasi?
EE33
2
The changeMetode ini tidak begitu cocok untuk kasus ini karena (1) update_allmetode akan dieksekusi pada kedua bermigrasi dan revert potensial. Itu mungkin bukan yang terburuk tetapi karena (2) migrasi tidak memiliki cara untuk mengetahui dari mana kolom itu diubah dari potensi pemulihan. Jadi untuk kasus ini saya akan tetap dengan updan down.
DanneManne
2
Bagi siapa pun yang tertarik, jawaban saya menunjukkan bagaimana melakukan ini dalam satu langkah.
Rick Smith
167

Di Rails 4, ini adalah solusi (PENGERING) yang lebih baik:

change_column_null :my_models, :date_column, false

Untuk memastikan tidak ada catatan dengan NULLnilai di kolom itu, Anda bisa melewati parameter keempat, yang merupakan nilai default yang digunakan untuk catatan dengan NULLnilai:

change_column_null :my_models, :date_column, false, Time.now
mrbrdo
sumber
4
Ini menyebabkan masalah ketika tabel sudah memiliki nilai null. Lihat jawaban saya
Rick Smith
5
Juga tersedia dalam 3.2. Memiliki parameter ke-4 juga untuk pengaturan default di mana nilainya nol.
toxaq
1
Ditambah 1 untuk change_column_null. Namun komentar Rick Smith di atas menunjukkan kasus yang sangat valid.
0112
Diperbarui untuk menambahkan kueri untuk memperbarui nilai nol. Parameter ke-4 (nilai default) hanya berguna ketika Anda benar-benar ingin memiliki default untuk catatan di masa mendatang.
mrbrdo
3
Sebenarnya, menurut Rails 4.2 docs, param ke-4 TIDAK menetapkan nilai default untuk rekaman di masa depan: "Metode ini menerima argumen keempat opsional untuk mengganti + NULL + yang ada dengan beberapa nilai lain. Harap perhatikan argumen keempat tidak menetapkan default kolom. "
Mike Fischer
70

Rails 4 (jawaban Rails 4 lainnya bermasalah):

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

Mengubah kolom dengan nilai NULL di dalamnya untuk tidak mengizinkan NULL akan menyebabkan masalah. Ini persis jenis kode yang akan berfungsi dengan baik dalam pengaturan pengembangan Anda dan kemudian macet ketika Anda mencoba untuk menyebarkannya ke produksi LIVE Anda . Anda harus terlebih dahulu mengubah nilai NULL menjadi sesuatu yang valid dan kemudian melarang NULL . Nilai 4 di change_column_nullmelakukan hal itu. Lihat dokumentasi untuk lebih jelasnya.

Juga, saya umumnya lebih suka menetapkan nilai default untuk bidang sehingga saya tidak perlu menentukan nilai bidang setiap kali saya membuat objek baru. Saya menyertakan kode komentar untuk melakukannya juga.

Rick Smith
sumber
3
Untuk Rails 4, ini tampaknya menjadi jawaban yang paling akurat dan lengkap, termasuk pengaturan default yang dikomentari.
Mike Fischer
4
Jika Anda menambahkan kolom baru ke tabel dan ingin menyisipkan nilai baru untuk null, tetapi tidak ingin menambahkan nilai default untuk kolom, Anda dapat melakukan ini dalam migrasi Anda: add_column :users, :admin, :stringkemudianchange_column_null(:admin, :string, false, "new_value_for_existing_records")
colsen
34

Buat migrasi yang memiliki change_columnpernyataan dengan :default =>nilai.

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

Lihat: change_column

Bergantung pada mesin basis data yang mungkin perlu Anda gunakan change_column_null

jessecurry
sumber
1
Ini berhasil untuk saya. Menggunakan MySql secara lokal. Ketika mendorong dan menjalankan aplikasi di Heroku (Postgres) itu pecah pada kolom yang tidak nol ketika saya menulisnya nol - memang seharusnya begitu. Hanya "change_column_null" yang akan bekerja tidak dapat menggunakan "change_column ...: null => false" di MySql. Terima kasih.
rtfminc
1
jadi apa migrasi Anda setelah change_column_null
js111
1
Postges lebih ketat dari MySQL - saya berharap akan membutuhkannya change_column_null.
jessecurry
3
@rtfminc Saya sangat menyarankan Anda untuk menggunakan mesin database yang sama dalam pengembangan dan produksi, karena menghindari banyak masalah ketika datang ke tepi kasus.
yagooar
12

Rel 4:

def change
  change_column_null(:users, :admin, false )
end
siaran bajak laut
sumber
1
Harap berikan uraian jawaban Anda.
Wahyu Kristianto
3

Dalam Rails 4.02+ menurut dokumen tidak ada metode seperti update_alldengan 2 argumen. Sebagai gantinya, seseorang dapat menggunakan kode ini:

# 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
sumber
2

Anda tidak dapat menggunakan add_timestamps dan null: false jika Anda memiliki catatan yang ada, jadi inilah solusinya:

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
sumber