Bagaimana cara mengatur nilai default untuk kolom datetime untuk mencatat waktu pembuatan dalam migrasi?

114

Pertimbangkan skrip pembuatan tabel di bawah ini:

create_table :foo do |t|
  t.datetime :starts_at, :null => false
end

Apakah mungkin untuk menyetel nilai default sebagai waktu saat ini?

Saya mencoba menemukan padanan independen DB di rel untuk definisi kolom SQL yang diberikan di bawah ini:

Sintaks Oracle

start_at DATE DEFAULT SYSDATE() 

Sintaks MySQL

start_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP

ATAU

start_at DATETIME DEFAULT NOW()
Harish Shetty
sumber

Jawaban:

174

Ini sekarang didukung di Rails 5.

Berikut ini contoh migrasi:

class CreatePosts < ActiveRecord::Migration[5.0]
  def change
    create_table :posts do |t|
      t.datetime :modified_at, default: -> { 'CURRENT_TIMESTAMP' }
      t.timestamps
    end
  end 
end

Lihat diskusi di https://github.com/rails/rails/issues/27077 dan jawab di sana oleh prathamesh-sonpatki

Akan
sumber
14
Jawaban yang bagus. Sebagai tambahan, ketahuilah bahwa di Postgres CURRENT_TIMESTAMPakan menjadi waktu dimulainya transaksi saat ini, jadi beberapa catatan yang dibuat dalam transaksi yang sama akan mendapatkan nilai yang sama. Jika Anda ingin waktu aktual eksekusi pernyataan (mengabaikan konteks transaksi), periksa CLOCK_TIMESTAMP.
Abe Voelker
Saya menambahkan sesuatu seperti ini: add_column :table_name, :start_date, :datetime, default: -> { 'CURRENT_TIMESTAMP' }dan seperti inilah tampilannya di schema.rb t.datetime "start_date", default: -> { "now()" }Tapi ketika saya membuat catatan baru, catatan itu tidak diisi. Tahu kenapa?
Sandip Subedi
@SandipSubedi sama untuk saya. Di rel 5.2.3.
courtimas
1
@courtsimas perlu Anda lakukan post.reloaduntuk mendapatkan nilai-nilai itu. Ini dijelaskan di sini: stackoverflow.com/questions/53804787/…
Sandip Subedi
1
@SandipSubedi Saya menyadari itu 5 menit setelah komentar saya. Sama seperti dengan generasi uuid. Terima kasih!
courtimas
119

Anda dapat menambahkan fungsi dalam model seperti ini:

  before_create :set_foo_to_now
  def set_foo_to_now
    self.foo = Time.now
  end

Sehingga model akan mengatur waktu saat ini dalam model.

Anda juga dapat menempatkan beberapa kode sql dalam migrasi untuk menyetel nilai default pada tingkat database, seperti:

execute 'alter table foo alter column starts_at set default now()'

Menyetel sesuatu seperti ini:

create_table :foo do |t|
  t.datetime :starts_at, :null => false, :default => Time.now
end

menyebabkan eksekusi fungsi Time.now selama migrasi sehingga tabel dalam database dibuat seperti ini:

create table foo ( starts_at timestamp not null default '2009-01-01 00:00:00');

tapi saya pikir itu bukan yang Anda inginkan.

Szymon Lipiński
sumber
1
Saat ini saya menyetel nilai di callback: before_create. <br> Saya sedang mencari beberapa jenis sihir AR di sini. Saya menghabiskan beberapa waktu melihat kode Rails, tetapi saya tidak menemukan solusi apa pun. Saya pikir saya akan bertanya-tanya untuk melihat apakah ada alternatif lain.
Harish Shetty
Saya akan menyarankan melakukannya dengan panggilan balik di before_create.
jonnii
1
Saya tidak ingin mengubah tabel DB karena saya ingin menjaga kode DB saya tetap netral. Saya berharap AR memiliki beberapa mekanisme untuk mengatur nilai default untuk bidang Datetime yang mirip dengan bidang create_at.
Harish Shetty
9
Ketahuilah bahwa callback before_create berjalan sebelum dimasukkan ke database tetapi setelah Anda membuat instance objek Anda (seperti dengan new()). Jadi self.foo = Time.nowakan menimpa nilai yang mungkin Anda berikan new(). Saya menyarankan self.foo = Time.current unless self.foo.present?sebagai gantinya.
Fatih
2
Bukan berarti, saat menggunakan eksekusi, ini akan membuat baris :starts_at, :default => 'now()'di schema.rb. Namun ini tidak akan berfungsi ketika menggunakan rake db:schema:dumpyang akan menimpa schema.rb dengan :starts_at, :default => '2015-05-29 09:46:33'(atau apa pun tanggal saat Anda meluncurkan skrip) ... sedih ....
astreal
13

Rekaman Aktif secara otomatis cap waktu membuat dan memperbarui operasi jika tabel memiliki bidang bernama created_at/ created_onatau updated_at/ updated_on. Sumber - api.rubyonrails.org

Anda tidak perlu melakukan apa pun kecuali memiliki kolom itu.

Jim
sumber
Saya sudah memiliki bidang tersebut di tabel saya. Saya membutuhkan bidang tambahan untuk penjadwal saya untuk menampung tanggal mulai. Saat ini, saya menggunakan: callback before_create untuk menyetel tanggal sekarang. Jika saya sering menghadapi skenario ini, saya harus menggunakan plugin untuk mengubah penanganan nilai default dalam metode 'to_sql' dari kelas ColumnDefinition.
Harish Shetty
9

Saya sedang mencari solusi serupa tetapi saya akhirnya menggunakan https://github.com/FooBarWidget/default_value_for .

The default_value_forPlugin memungkinkan seseorang untuk menentukan nilai default untuk model ActiveRecord secara deklaratif. Sebagai contoh:

class User < ActiveRecord::Base
  default_value_for :name, "(no name)"
  default_value_for :last_seen do
    Time.now
  end
end

u = User.new
u.name       # => "(no name)"
u.last_seen  # => Mon Sep 22 17:28:38 +0200 2008
Giovanni Cappellotto
sumber
9

Saya biasanya melakukan:

def change
  execute("
    ALTER TABLE your_table
    ALTER COLUMN your_column
    SET DEFAULT CURRENT_TIMESTAMP
  ")
end

Jadi, Anda schema.rbakan memiliki sesuatu seperti:

create_table "your_table", force: :cascade do |t|
  t.datetime "your_column", default: "now()"
end
Arturo Herrero
sumber
Sisi negatifnya: solusi ini hanya berfungsi saat Anda menjalankan rake db:migrate, bukan saat Anda memuat file skema dengan sesuatu seperti rake db:schema:load.
Alter Lagos
8

Jika Anda perlu mengubah sebuah DateTime kolom yang ada di Rails 5 (bukan menciptakan tabel baru sebagaimana ditentukan dalam jawaban lain) sehingga dapat mengambil keuntungan dari kemampuan tanggal default, Anda dapat membuat migrasi seperti ini:

class MakeStartsAtDefaultDateForFoo < ActiveRecord::Migration[5.0]
  def change
    change_column :foos, :starts_at, :datetime, default: -> { 'CURRENT_TIMESTAMP' }
  end
end
Matt Long
sumber
Bentuk buruk untuk memilih sesuatu dan tidak menjelaskan masalah apa yang diambil dengan jawabannya. Ada yang tahu mengapa ini ditolak? Ini menampilkan sintaks jika Anda ingin mengubah kolom daripada membuatnya.
Matt Long
Saya bukan orang yang downvoted, tapi saya kesulitan melihat bagaimana jawaban ini berbeda dari jawaban wasiat yang mendahului Anda dengan satu tahun. Pertanyaannya adalah tentang menyetel default, dan jawaban Anda memiliki klausa lambda yang sama.
nurettin
2
@nurettin Saya mengerti maksud Anda, tetapi dalam pembelaan saya, sintaks untuk membuat kolom baru sedikit berbeda dari mengubah kolom yang ada. Menyediakan sintaks itu bagi mereka yang menemukan pertanyaan ini melalui penelusuran web ketika mencoba menambahkan default ke model data mereka saat ini daripada membuat model / tabel baru sama sekali mungkin cukup membantu. Tidak? Anda mengasumsikan semua orang tahu bahwa mereka dapat menggunakan lambda yang sama untuk change_column. Mungkin mereka harus menyadarinya, tapi itulah mengapa saya menjawab di sini - jadi mereka tidak perlu pergi ke tempat lain untuk mencari tahu. Bersulang!
Matt Long
4
FWIW Saya baru saja menggunakan sintaks ini dan menghargai itu untuk pencarian google saya dan masalah khusus jawaban ini paling membantu saya.
Jay Killeen
1
Saya tidak melihat ada yang salah dengan ini menjadi jawaban daripada komentar. Suara positif. Saya pikir downvoters hanya membaca sembarangan (seperti kebanyakan penutup pertanyaan!; P).
iconoclast
-1

Dalam jawaban yang diberikan oleh @ szymon-lipiński (Szymon Lipiński), metode eksekusi tidak berhasil untuk saya. Itu membuat kesalahan sintaks MySQL.

Sintaks MySQL yang berhasil untuk saya adalah ini.

execute "ALTER TABLE mytable CHANGE `column_name` `column_name` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP"

Jadi untuk mengatur nilai default kolom datetime pada script migrasi dapat dilakukan sebagai berikut:

def up
  create_table :foo do |t|
    t.datetime :starts_at, :null => false
  end

  execute "ALTER TABLE `foo` CHANGE `starts_at` `starts_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP"
end
Sony Mathew
sumber