¿Cómo establecer un valor predeterminado para una columna de fecha y hora para registrar la hora de creación en una migración?

114

Considere el siguiente script de creación de tablas:

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

¿Es posible establecer el valor predeterminado como la hora actual?

Estoy tratando de encontrar un equivalente independiente de DB en rieles para las definiciones de columna SQL que se dan a continuación:

Sintaxis de Oracle

start_at DATE DEFAULT SYSDATE() 

Sintaxis de MySQL

start_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP

O

start_at DATETIME DEFAULT NOW()
Harish Shetty
fuente

Respuestas:

174

Esto se admite ahora en Rails 5.

Aquí hay una migración de muestra:

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

Vea la discusión en https://github.com/rails/rails/issues/27077 y responda allí por prathamesh-sonpatki

Será
fuente
14
Gran respuesta. Solo como un aparte, tenga en cuenta que en Postgres CURRENT_TIMESTAMPserá el momento del inicio de la transacción actual, por lo que varios registros creados en la misma transacción obtendrán el mismo valor. Si desea la hora actual real en que se ejecuta la declaración (ignorando el contexto de la transacción), consulte CLOCK_TIMESTAMP.
Abe Voelker
add_column :table_name, :start_date, :datetime, default: -> { 'CURRENT_TIMESTAMP' }Agregué algo como esto: y esto es lo que parece en el archivo schema.rb t.datetime "start_date", default: -> { "now()" }Pero cuando creé un nuevo registro, no se completó . ¿Alguna idea de por qué?
Sandip Subedi
@SandipSubedi lo mismo para mí. Sobre raíles 5.2.3.
courtimas
1
@courtsimas que debe hacer post.reloadpara obtener esos valores. Se explica aquí: stackoverflow.com/questions/53804787/…
Sandip Subedi
1
@SandipSubedi Me di cuenta de eso 5 minutos después de mi comentario. Al igual que con uuid generation. ¡Gracias!
courtimas
119

Puede agregar una función en un modelo como este:

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

Para que el modelo establezca la hora actual en el modelo.

También puede colocar algún código SQL en la migración para establecer el valor predeterminado en el nivel de la base de datos, algo como:

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

Configurando algo como esto:

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

provoca la ejecución de la función Time.now durante la migración, por lo que la tabla en la base de datos se crea así:

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

pero creo que no es lo que quieres.

Szymon Lipiński
fuente
1
Actualmente estoy configurando el valor en la devolución de llamada: before_create. <br> Estaba buscando algún tipo de magia AR aquí. Pasé algún tiempo mirando el código de Rails, pero no encontré ninguna solución. Pensé en preguntar por ahí para ver si hay alternativas.
Harish Shetty
Sugeriría hacerlo con una devolución de llamada en before_create.
jonnii
1
No quiero alterar la tabla DB porque quiero mantener mi código DB neutral. Esperaba que AR tuviera algún mecanismo para establecer el valor predeterminado para el campo Fecha y hora similar al campo created_at.
Harish Shetty
9
Tenga en cuenta que la devolución de llamada before_create se ejecuta antes de insertarla en la base de datos pero después de crear una instancia de su objeto (como con new()). Por self.foo = Time.nowlo que anulará el valor que le pueda dar new(). Sugiero en su self.foo = Time.current unless self.foo.present?lugar.
Fatih
2
No es que, al usar la ejecución, esto ponga una línea :starts_at, :default => 'now()'en el archivo schema.rb. Sin embargo, esto no funcionará cuando se use, rake db:schema:dumpque anulará el schema.rb con :starts_at, :default => '2015-05-29 09:46:33'(o cualquiera que sea la fecha en que inicie el script) ... triste ....
astreal
13

Active Record crea y actualiza automáticamente las operaciones de creación y actualización de marcas de tiempo si la tabla tiene campos llamados created_at/ created_ono updated_at/ updated_on. Fuente - api.rubyonrails.org

No necesita hacer nada más que tener esa columna.

Jim
fuente
Ya tengo esos campos en mi tabla. Necesito un campo adicional para que mi programador mantenga la fecha de inicio. Actualmente, estoy usando: before_create callback para establecer la fecha actual. Si me encuentro con este escenario con frecuencia, tengo que recurrir a escribir un complemento para alterar el manejo de valores predeterminados en el método 'to_sql' de la clase ColumnDefinition.
Harish Shetty
9

Estaba buscando soluciones similares pero terminé usando https://github.com/FooBarWidget/default_value_for .

El default_value_forcomplemento permite definir valores predeterminados para los modelos de ActiveRecord de manera declarativa. Por ejemplo:

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

Yo suelo hacer:

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

Entonces, tu schema.rbvas a tener algo como:

create_table "your_table", force: :cascade do |t|
  t.datetime "your_column", default: "now()"
end
Arturo Herrero
fuente
La desventaja: esta solución solo funciona cuando ejecuta rake db:migrate, no cuando carga su archivo de esquema con algo como rake db:schema:load.
Alter Lagos
8

Si necesita cambiar una columna DateTime existente en Rails 5 (en lugar de crear una nueva tabla como se especifica en otras respuestas) para que pueda aprovechar la capacidad de fecha predeterminada, puede crear una migración como esta:

class MakeStartsAtDefaultDateForFoo < ActiveRecord::Migration[5.0]
  def change
    change_column :foos, :starts_at, :datetime, default: -> { 'CURRENT_TIMESTAMP' }
  end
end
Matt Long
fuente
De mala forma votar algo en contra y no explicar qué problema se tomó con la respuesta. ¿Alguien tiene una idea de por qué se votó en contra? Muestra la sintaxis si desea cambiar una columna en lugar de crear una.
Matt Long
No soy yo quien votó negativamente, pero tengo dificultades para ver cómo esta respuesta difiere de la respuesta de will que precede a la suya por un año. La pregunta se trata de establecer un valor predeterminado y su respuesta tiene la misma cláusula lambda.
nurettin
2
@nurettin Entiendo su punto, pero en mi defensa, la sintaxis para crear una nueva columna es sutilmente diferente de cambiar una columna existente. Proporcionar esa sintaxis para aquellos que encontraron esta pregunta a través de la búsqueda en la web mientras intentaban agregar un valor predeterminado a su modelo de datos actual en lugar de crear un nuevo modelo / tabla por completo, probablemente sea bastante útil. ¿No? Está asumiendo que todos saben que pueden usar la misma lambda para change_column. Tal vez deberían darse cuenta de eso, pero es por eso que respondí aquí, para que no tengan que ir a ningún otro lugar para averiguarlo. ¡Salud!
Matt Long
4
FWIW Acabo de usar esta sintaxis y aprecio que para mi búsqueda de Google y mi problema particular esta respuesta me ayudó más.
Jay Killeen
1
No veo nada malo en que esto sea una respuesta en lugar de un comentario. Voto a favor. Creo que los votantes negativos están leyendo descuidadamente (¡como la mayoría de los que cierran preguntas!; P).
iconoclasta
-1

En la respuesta dada por @ szymon-lipiński (Szymon Lipiński), el método de ejecución no funcionó para mí. Lanzaba un error de sintaxis de MySQL.

La sintaxis de MySQL que funcionó para mí es esta.

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

Entonces, para establecer el valor predeterminado para una columna de fecha y hora en el script de migración, se puede hacer de la siguiente manera:

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
fuente