Wie lege ich einen Standardwert für eine Datetime-Spalte fest, um die Erstellungszeit bei einer Migration aufzuzeichnen?

114

Betrachten Sie das folgende Skript zur Tabellenerstellung:

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

Ist es möglich, den Standardwert als aktuelle Zeit festzulegen?

Ich versuche, in Rails ein DB-unabhängiges Äquivalent für die unten angegebenen SQL-Spaltendefinitionen zu finden:

Oracle-Syntax

start_at DATE DEFAULT SYSDATE() 

MySQL-Syntax

start_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP

ODER

start_at DATETIME DEFAULT NOW()
Harish Shetty
quelle

Antworten:

173

Dies wird jetzt in Rails 5 unterstützt.

Hier ist eine Beispielmigration:

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

Siehe Diskussion unter https://github.com/rails/rails/issues/27077 und antworte dort von prathamesh-sonpatki

Wille
quelle
14
Gute Antwort. Beachten Sie außerdem, dass in Postgres CURRENT_TIMESTAMPder Zeitpunkt des Starts der aktuellen Transaktion angegeben wird, sodass mehrere in derselben Transaktion erstellte Datensätze denselben Wert erhalten. Wenn Sie die aktuelle Uhrzeit der Anweisung anzeigen möchten (ohne Berücksichtigung des Transaktionskontexts), überprüfen Sie dies CLOCK_TIMESTAMP.
Abe Voelker
Ich habe so etwas hinzugefügt: add_column :table_name, :start_date, :datetime, default: -> { 'CURRENT_TIMESTAMP' }und so sieht es im schema.rb aus. t.datetime "start_date", default: -> { "now()" }Aber als ich einen neuen Datensatz erstellt habe, wird er nicht gefüllt . Irgendeine Idee warum?
Sandip Subedi
@ SandipSubedi das gleiche für mich. Auf Schienen 5.2.3.
Courtsimas
1
@courtsimas müssen Sie tun post.reload, um diese Werte zu erhalten. Es wird hier erklärt: stackoverflow.com/questions/53804787/…
Sandip Subedi
1
@ SandipSubedi Ich habe das 5 Minuten nach meinem Kommentar festgestellt. Genau wie bei der UUID-Generierung. Vielen Dank!
Courtsimas
119

Sie können einem Modell wie diesem eine Funktion hinzufügen:

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

Damit das Modell die aktuelle Zeit im Modell einstellt.

Sie können auch SQL-Code in die Migration einfügen, um den Standardwert auf Datenbankebene festzulegen.

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

So etwas einstellen:

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

bewirkt, dass die Time.now-Funktion während der Migration ausgeführt wird, sodass die Tabelle in der Datenbank wie folgt erstellt wird:

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

aber ich denke, dass es nicht das ist, was du willst.

Szymon Lipiński
quelle
1
Derzeit setze ich den Wert im Rückruf: before_create. <br> Ich habe hier nach einer Art AR-Magie gesucht. Ich habe einige Zeit mit dem Rails-Code verbracht, aber keine Lösung gefunden. Ich dachte, ich werde nachfragen, ob es Alternativen gibt.
Harish Shetty
Ich würde vorschlagen, dies mit einem Rückruf auf before_create zu tun.
Jonnii
1
Ich möchte die DB-Tabelle nicht ändern, da ich meinen Code DB neutral halten möchte. Ich hatte gehofft, dass AR einen Mechanismus hatte, um den Standardwert für das Datetime-Feld ähnlich dem Feld created_at festzulegen.
Harish Shetty
9
Beachten Sie, dass der Rückruf before_create vor dem Einfügen in die Datenbank ausgeführt wird, aber nachdem Sie Ihr Objekt instanziiert haben (wie bei new()). So self.foo = Time.nowwird der Wert außer Kraft setzen Sie geben könnte new(). Ich schlage self.foo = Time.current unless self.foo.present?stattdessen vor.
Fatih
2
Nicht, dass bei Verwendung von :starts_at, :default => 'now()'execute eine Zeile in die schema.rb eingefügt wird. Dies funktioniert jedoch nicht, wenn rake db:schema:dumpdie schema.rb mit :starts_at, :default => '2015-05-29 09:46:33'(oder unabhängig vom Datum, an dem Sie das Skript starten)
überschrieben wird
13

Active Record erstellt und aktualisiert automatisch Zeitstempel, wenn die Tabelle Felder mit dem Namen created_at/ created_onoder updated_at/ enthält updated_on. Quelle - api.rubyonrails.org

Sie müssen nichts anderes tun, als diese Spalte zu haben.

Jim
quelle
Ich habe diese Felder bereits in meiner Tabelle. Ich benötige ein zusätzliches Feld für meinen Scheduler, um das Startdatum zu speichern. Derzeit verwende ich: before_create callback, um das aktuelle Datum festzulegen. Wenn dieses Szenario häufig auftritt, muss ich ein Plugin schreiben, um die Standardwertbehandlung in der Methode 'to_sql' der ColumnDefinition-Klasse zu ändern.
Harish Shetty
9

Ich habe nach ähnlichen Lösungen gesucht, aber https://github.com/FooBarWidget/default_value_for nicht mehr verwendet .

Mit dem default_value_forPlugin können Standardwerte für ActiveRecord-Modelle deklarativ definiert werden. Beispielsweise:

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
quelle
9

Normalerweise mache ich:

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

Also, du schema.rbwirst so etwas haben wie:

create_table "your_table", force: :cascade do |t|
  t.datetime "your_column", default: "now()"
end
Arturo Herrero
quelle
Der Nachteil: Diese Lösung funktioniert nur, wenn Sie ausgeführt werden rake db:migrate, nicht, wenn Sie Ihre Schemadatei mit etwas wie laden rake db:schema:load.
Alter Lagos
8

Wenn Sie eine vorhandene DateTime-Spalte in Rails 5 ändern müssen (anstatt eine neue Tabelle wie in anderen Antworten angegeben zu erstellen), damit die Standard-Datumsfunktion genutzt werden kann, können Sie eine Migration wie folgt erstellen:

class MakeStartsAtDefaultDateForFoo < ActiveRecord::Migration[5.0]
  def change
    change_column :foos, :starts_at, :datetime, default: -> { 'CURRENT_TIMESTAMP' }
  end
end
Matt Long
quelle
Schlechte Form, um etwas abzustimmen und nicht zu erklären, welches Problem mit der Antwort gelöst wurde. Hat jemand eine Idee, warum dies abgelehnt wurde? Es zeigt die Syntax an, wenn Sie eine Spalte ändern möchten, anstatt eine zu erstellen.
Matt Long
Ich bin nicht derjenige, der abgelehnt hat, aber ich habe Schwierigkeiten zu sehen, wie sich diese Antwort von der Antwort des Willens unterscheidet, die Ihrer um ein Jahr vorausgeht. Bei der Frage geht es darum, einen Standard festzulegen, und Ihre Antwort enthält dieselbe Lambda-Klausel.
Nurettin
2
@nurettin Ich verstehe, aber zu meiner Verteidigung unterscheidet sich die Syntax zum Erstellen einer neuen Spalte geringfügig vom Ändern einer vorhandenen Spalte. Es ist wahrscheinlich sehr hilfreich, diese Syntax für diejenigen bereitzustellen, die diese Frage über die Websuche gefunden haben, während sie versucht haben, ihrem aktuellen Datenmodell einen Standard hinzuzufügen, anstatt ein neues Modell / eine neue Tabelle zu erstellen. Nein? Sie gehen davon aus, dass jeder weiß, dass er dasselbe Lambda für eine change_column verwenden kann. Vielleicht sollten sie das erkennen, aber deshalb habe ich hier geantwortet - damit sie nirgendwo anders hingehen müssen, um das herauszufinden. Prost!
Matt Long
4
FWIW Ich habe gerade diese Syntax verwendet und weiß zu schätzen, dass mir diese Antwort für meine Google-Suche und mein spezielles Problem am meisten geholfen hat.
Jay Killeen
1
Ich sehe nichts falsches daran, dass dies eher eine Antwort als ein Kommentar ist. Upvoted. Ich denke, die Downvoter lesen nur nachlässig (wie die meisten Fragenschließer !; P).
Bilderstürmer
-1

In der Antwort von @ szymon-lipiński (Szymon Lipiński) hat die Ausführungsmethode bei mir nicht funktioniert. Es wurde ein MySQL-Syntaxfehler ausgelöst.

Die MySQL-Syntax, die für mich funktioniert hat, ist diese.

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

Das Festlegen des Standardwerts für eine datetime-Spalte im Migrationsskript kann folgendermaßen erfolgen:

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
quelle