Come impostare un valore predefinito per una colonna datetime per registrare l'ora di creazione in una migrazione?


114

Considera lo script di creazione della tabella di seguito:

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

È possibile impostare il valore predefinito come l'ora corrente?

Sto cercando di trovare un equivalente indipendente da DB in rails per le definizioni delle colonne SQL fornite di seguito:

Oracle Syntax

start_at DATE DEFAULT SYSDATE() 

Sintassi di MySQL

start_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP

O

start_at DATETIME DEFAULT NOW()

Risposte:


174

Questo è ora supportato in Rails 5.

Ecco un esempio di migrazione:

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

Vedere la discussione su https://github.com/rails/rails/issues/27077 e rispondere lì da prathamesh-sonpatki


14
Bella risposta. Per inciso, tieni presente che in Postgres CURRENT_TIMESTAMPsarà l'ora di inizio della transazione corrente, quindi più record creati nella stessa transazione avranno lo stesso valore. Se si desidera l'ora corrente effettiva in cui viene eseguita l'istruzione (ignorando il contesto della transazione), effettuare il check out CLOCK_TIMESTAMP.
Abe Voelker

Ho aggiunto qualcosa del genere: add_column :table_name, :start_date, :datetime, default: -> { 'CURRENT_TIMESTAMP' }e questo è ciò che sembra nello schema.rb t.datetime "start_date", default: -> { "now()" }Ma quando ho creato un nuovo record, non viene popolato. Qualche idea sul perché?
Sandip Subedi

@SandipSubedi lo stesso per me. Su rotaie 5.2.3.
courtimas il

1
@courtsimas che devi fare post.reloadper ottenere quei valori. È spiegato qui: stackoverflow.com/questions/53804787/…
Sandip Subedi

1
@SandipSubedi me ne sono accorto 5 minuti dopo il mio commento. Proprio come con la generazione di uuid. Grazie!
courtimas

119

Puoi aggiungere una funzione in un modello come questo:

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

In modo che il modello imposterà l'ora corrente nel modello.

Puoi anche inserire del codice sql nella migrazione per impostare il valore predefinito a livello di database, qualcosa come:

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

Impostazione qualcosa del genere:

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

causa l'esecuzione della funzione Time.now durante la migrazione, quindi la tabella nel database viene creata in questo modo:

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

ma penso che non sia quello che vuoi.


1
Attualmente sto impostando il valore nel callback: before_create. <br> Stavo cercando un qualche tipo di magia AR qui. Ho passato un po 'di tempo a guardare il codice Rails, ma non ho trovato alcuna soluzione. Ho pensato che chiederò in giro per vedere se ci sono alternative.
Harish Shetty

Suggerirei di farlo con una richiamata su before_create.
jonnii

1
Non voglio alterare la tabella DB perché voglio mantenere il mio codice DB neutro. Speravo che AR avesse un meccanismo per impostare il valore predefinito per il campo Datetime simile al campo created_at.
Harish Shetty

9
Tieni presente che il callback before_create viene eseguito prima dell'inserimento nel database ma dopo aver istanziato il tuo oggetto (come con new()). Quindi self.foo = Time.nowsovrascriverà il valore a cui potresti dare new(). Suggerisco self.foo = Time.current unless self.foo.present?invece.
Fatih

2
Non che, quando si utilizza l'esecuzione, questo inserirà una riga :starts_at, :default => 'now()'in schema.rb. Tuttavia questo non funzionerà quando si utilizza rake db:schema:dumpche sovrascriverà lo schema.rb con :starts_at, :default => '2015-05-29 09:46:33'(o qualunque sia la data in cui si avvia lo script) ... triste ....
circa

13

I timestamp di Active Record creano e aggiornano automaticamente le operazioni se la tabella ha campi denominati created_at/ created_ono updated_at/ updated_on. Fonte: api.rubyonrails.org

Non devi fare nient'altro che avere quella colonna.


Ho già quei campi nella mia tabella. Ho bisogno di un campo aggiuntivo per il mio scheduler per contenere la data di inizio. Attualmente sto usando: before_create callback per impostare la data corrente. Se incontro spesso questo scenario, devo ricorrere alla scrittura di un plug-in per modificare la gestione dei valori predefiniti nel metodo "to_sql" della classe ColumnDefinition.
Harish Shetty

9

Stavo cercando una soluzione simile ma ho finito di usare https://github.com/FooBarWidget/default_value_for .

Il default_value_forplugin consente di definire i valori predefiniti per i modelli ActiveRecord in modo dichiarativo. Per esempio:

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

9

Di solito faccio:

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

Quindi, il tuo schema.rbavrà qualcosa del tipo:

create_table "your_table", force: :cascade do |t|
  t.datetime "your_column", default: "now()"
end

Lo svantaggio: questa soluzione funziona solo quando esegui rake db:migrate, non quando carichi il tuo file schema con qualcosa di simile rake db:schema:load.
Alter Lagos,

8

Se è necessario modificare una colonna DateTime esistente in Rails 5 (anziché creare una nuova tabella come specificato in altre risposte) in modo che possa sfruttare la funzionalità di data predefinita, è possibile creare una migrazione come questa:

class MakeStartsAtDefaultDateForFoo < ActiveRecord::Migration[5.0]
  def change
    change_column :foos, :starts_at, :datetime, default: -> { 'CURRENT_TIMESTAMP' }
  end
end

Cattiva forma per votare qualcosa e non spiegare quale problema è stato preso con la risposta. Qualcuno ha idea del motivo per cui è stato respinto? Mostra la sintassi se si desidera modificare una colonna invece di crearne una.
Matt Long,

Non sono io quello che ha svalutato, ma ho difficoltà a vedere come questa risposta differisca dalla risposta della volontà che precede la tua di un anno. La domanda riguarda l'impostazione di un valore predefinito e la tua risposta ha la stessa clausola lambda.
nurettin

2
@nurettin Capisco il tuo punto, ma a mia difesa, la sintassi per creare una nuova colonna è leggermente diversa dalla modifica di una colonna esistente. Fornire quella sintassi a coloro che hanno trovato questa domanda tramite la ricerca sul Web mentre cercavano di aggiungere un valore predefinito al loro modello di dati corrente piuttosto che creare un nuovo modello / tabella del tutto è probabilmente molto utile. No? Stai assumendo che tutti sappiano che possono usare lo stesso lambda per change_column. Forse dovrebbero rendersene conto, ma è per questo che ho risposto qui, quindi non devono andare da nessun'altra parte per capirlo. Saluti!
Matt Long

4
FWIW Ho usato questa sintassi solo ora e apprezzo che per la mia ricerca su Google e il problema particolare questa risposta mi abbia aiutato di più.
Jay Killeen

1
Non vedo niente di sbagliato nel fatto che questa sia una risposta piuttosto che un commento. Upvoted. Penso che i downvoters stiano leggendo con noncuranza (come la maggior parte delle chiusure di domande!; P).
iconoclasta

-1

Nella risposta data da @ szymon-lipiński (Szymon Lipiński), il metodo di esecuzione non ha funzionato per me. Stava lanciando un errore di sintassi MySQL.

La sintassi di MySQL che ha funzionato per me è questa.

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

Quindi, per impostare il valore predefinito per una colonna datetime nello script di migrazione può essere fatto come segue:

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.