Ajouter des horodatages à une table existante

173

J'ai besoin d'ajouter des horodatages ( created_at& updated_at) à une table existante. J'ai essayé le code suivant mais cela n'a pas fonctionné.

class AddTimestampsToUser < ActiveRecord::Migration
    def change_table
        add_timestamps(:users)
    end
end
Léonel
la source

Réponses:

211

L'assistant d'horodatage n'est disponible que dans le create_tablebloc. Vous pouvez ajouter ces colonnes en spécifiant les types de colonnes manuellement:

class AddTimestampsToUser < ActiveRecord::Migration
  def change_table
    add_column :users, :created_at, :datetime, null: false
    add_column :users, :updated_at, :datetime, null: false
  end
end

Bien que cela n'ait pas la même syntaxe laconique que la add_timestampsméthode que vous avez spécifiée ci-dessus, Rails traitera toujours ces colonnes comme des colonnes d'horodatage et mettra à jour les valeurs normalement.

Ben Simpson
la source
10
Cela n'a pas fonctionné pour moi dans Rails 4. La solution ci-dessous par "mu est trop court" fonctionne.
newUserNameHere
21
rails g migration AddTimestampsToUser created_at:datetime updated_at:datetime- un raccourci pour générer la migration ci-dessus.
Konstantine Kalbazov
2
L'exécution de cette migration entraîne une erreur PG::NotNullViolation: ERROR: column "created_at" contains null value car ma table contient déjà des données qui violent la contrainte non nulle. Une meilleure façon de faire cela que de supprimer la contrainte non nulle dans un premier temps, puis de l'ajouter plus tard?
M. Habib
1
@ M.Habib Je ne pense pas, mais cette réponse résume bien tout en une seule migration.
littleforest
1
@ M.Habib dépend de ce que vous pensez avoir le plus de sens pour la valeur par défaut, vous pouvez le faire add_column :users, :updated_at, :datetime, null: false, default: Time.zone.now. Time.zone.nowest juste un exemple, vous devez utiliser la valeur qui convient à votre logique.
Delong Gao
91

Les migrations ne sont que deux méthodes de classe (ou méthodes d'instance dans 3.1): upet down(et parfois une changeméthode d'instance dans 3.1). Vous souhaitez que vos modifications soient intégrées à la upméthode:

class AddTimestampsToUser < ActiveRecord::Migration
  def self.up # Or `def up` in 3.1
    change_table :users do |t|
      t.timestamps
    end
  end
  def self.down # Or `def down` in 3.1
    remove_column :users, :created_at
    remove_column :users, :updated_at
  end
end

Si vous êtes dans 3.1, vous pouvez également utiliser change(merci Dave):

class AddTimestampsToUser < ActiveRecord::Migration
  def change
    change_table(:users) { |t| t.timestamps }
  end
end

Peut-être vous êtes confus def change, def change_tableet change_table.

Consultez le guide de migration pour plus de détails.

mu est trop court
la source
1
(Eh bien, il y a la changeméthode maintenant, bien que dans ce cas, pas le problème :)
Dave Newton
@Dave: C'est vrai, j'ai opté pour le générique pour éviter les problèmes de version, mais cela changevaut la peine d'être mentionné, je vais donc l'ajouter aussi.
mu est trop court le
C'est vrai mais j'ai entendu dire que cela change vraiment avec 3.1 et que le «down» s'en va vraiment. Rails pour comprendre la méthode de descente automatiquement. En avez-vous entendu parler?
Michael Durrant
@Michael: J'utilise MongoDB exclusivement avec l'application 3.1 sur laquelle je travaille donc je n'ai pas travaillé avec les migrations 3.1 AR. La documentation indique que tout évolue vers les méthodes d'instance (pour des raisons inconnues).
mu est trop court le
@MichaelDurrant, il existe de nombreux scénarios que le "changement" ne couvre pas pour le moment, si le haut / le bas disparaissent, il y aura des gens en colère :) (ajoutez une clause "sauf" dans votre migration de changement pour éviter les collisions de migration, et essayez revenir en arrière ...) Même 3 ans après avoir fait ce commentaire, je ne pense pas que cela change. :)
frandroid
76

Votre code d'origine est très proche de la droite, il vous suffit d'utiliser un nom de méthode différent. Si vous utilisez Rails 3.1 ou version ultérieure, vous devez définir une changeméthode au lieu de change_table:

class AddTimestampsToUser < ActiveRecord::Migration
  def change
    add_timestamps(:users)
  end
end

Si vous utilisez une version plus ancienne, vous devez définir upet des downméthodes au lieu de change_table:

class AddTimestampsToUser < ActiveRecord::Migration
  def up
    add_timestamps(:users)
  end

  def down
    remove_timestamps(:users)
  end
end
Georgebrock
la source
59

La réponse de @ user1899434 a retenu le fait qu'une table "existante" ici pourrait signifier une table contenant déjà des enregistrements, des enregistrements que vous ne voudriez peut-être pas supprimer. Ainsi, lorsque vous ajoutez des horodatages avec null: false, qui est la valeur par défaut et souvent souhaitable, ces enregistrements existants sont tous invalides.

Mais je pense que cette réponse peut être améliorée, en combinant les deux étapes en une seule migration, ainsi qu'en utilisant la méthode plus sémantique add_timestamps:

def change
  add_timestamps :projects, default: Time.zone.now
  change_column_default :projects, :created_at, nil
  change_column_default :projects, :updated_at, nil
end

Vous pouvez remplacer un autre horodatage DateTime.now, par exemple si vous vouliez que les enregistrements préexistants soient créés / mis à jour à l'aube des temps à la place.

Nick Davies
la source
2
Incroyable. Je vous remercie! Juste une remarque - Time.zone.nowc'est ce qui devrait être utilisé si nous voulons que notre code obéisse au fuseau horaire correct.
John Gallagher
4
Il y a un problème avec la définition de la valeur par défaut à Time.zone.nowlaquelle il renverra l'instance Time qui est créée lorsque la migration est exécutée et utilise simplement cette heure comme valeur par défaut. Les nouveaux objets n'obtiendront pas de nouvelle instance Time.
Tovi Newman
38
class AddTimestampsToUser < ActiveRecord::Migration
  def change
    change_table :users do |t|
      t.timestamps
    end
  end
end

Les transformations disponibles sont

change_table :table do |t|
  t.column
  t.index
  t.timestamps
  t.change
  t.change_default
  t.rename
  t.references
  t.belongs_to
  t.string
  t.text
  t.integer
  t.float
  t.decimal
  t.datetime
  t.timestamp
  t.time
  t.date
  t.binary
  t.boolean
  t.remove
  t.remove_references
  t.remove_belongs_to
  t.remove_index
  t.remove_timestamps
end

http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html

Pradeep Sanjaya
la source
10

La réponse de Nick Davies est la plus complète en termes d'ajout de colonnes d'horodatage à une table avec des données existantes. Son seul inconvénient est qu'il augmentera ActiveRecord::IrreversibleMigrationsur undb:rollback .

Il doit être modifié ainsi pour fonctionner dans les deux sens:

def change
  add_timestamps :campaigns, default: DateTime.now
  change_column_default :campaigns, :created_at, from: DateTime.now, to: nil
  change_column_default :campaigns, :updated_at, from: DateTime.now, to: nil
end
Lightyrs
la source
Cela n'a pas fonctionné exactement comme écrit pour moi sur Rails 4.2.7 (je pense que change_column_defaultcela ne prend pas en charge fromet todans cette version?), Mais j'ai pris cette idée et créé des up/downméthodes au lieu d'une seule changeméthode et cela a fonctionné comme un charme!
gar
8
def change
  add_timestamps :table_name
end
Ian Vaughan
la source
4

Je ne sais pas exactement quand cela a été introduit, mais dans les rails 5.2.1, vous pouvez le faire:

class AddTimestampsToMyTable < ActiveRecord::Migration[5.2]
  def change
    add_timestamps :my_table
  end
end

pour en savoir plus, consultez « Utilisation de la méthode de modification » dans la documentation sur les migrations d'enregistrements actifs.

Reef Loretto
la source
Je ne l'ai pas fait fonctionner avec Migration [5.1]; puis j'ai changé le nombre en [5.2] et Rails m'a dit que je ne pouvais utiliser que 5.1, 5.0 ou 4.2. J'ai essayé avec 5.0 sans succès, puis avec 4.2 avec succès.
Est-ce
Vieux, je sais, mais si vous avez des enregistrements existants, ajoutez: , null: trueaprès le:my_table
jomar
2

J'ai créé une fonction simple que vous pouvez appeler pour ajouter à chaque table (en supposant que vous avez une base de données existante) les champs created_at et updated_at :

  # add created_at and updated_at to each table found.
  def add_datetime
    tables = ActiveRecord::Base.connection.tables
    tables.each do |t|
      ActiveRecord::Base.connection.add_timestamps t  
    end    
  end
Roger
la source
2

add_timestamps (nom_table, options = {}) public

Ajoute des colonnes d'horodatage (created_at et updated_at) à nom_table. Des options supplémentaires (comme null: false) sont transmises à #add_column.

class AddTimestampsToUsers < ActiveRecord::Migration
  def change
    add_timestamps(:users, null: false)
  end
end
Almawhoob
la source
1

Les réponses précédentes semblent correctes, mais j'ai rencontré des problèmes si ma table contient déjà des entrées.

J'obtiendrais «ERREUR: la colonne created_atcontient des nullvaleurs».

Pour réparer, j'ai utilisé:

def up
  add_column :projects, :created_at, :datetime, default: nil, null: false
  add_column :projects, :updated_at, :datetime, default: nil, null: false
end

J'ai ensuite utilisé le gem migration_data pour ajouter le temps des projets en cours sur la migration tels que:

def data
  Project.update_all created_at: Time.now
end

Ensuite, tous les projets créés après cette migration seront correctement mis à jour. Assurez-vous que le serveur est également redémarré afin que Rails ActiveRecordcommence à suivre les horodatages sur l'enregistrement.

dbrody
la source
1

Beaucoup de réponses ici, mais je posterai la mienne aussi car aucune des précédentes n'a vraiment fonctionné pour moi :)

Comme certains l'ont noté, #add_timestampsajoute malheureusement la null: falserestriction, ce qui rendra les anciennes lignes invalides car elles n'ont pas ces valeurs remplies. La plupart des réponses ici suggèrent que nous définissions une valeur par défaut (Time.zone.now ), mais je n'aimerais pas le faire car ces horodatages par défaut pour les anciennes données ne seront pas corrects. Je ne vois pas l'intérêt d'ajouter des données incorrectes à la table.

Ma migration était donc simplement:

class AddTimestampsToUser < ActiveRecord::Migration
  def change_table
    add_column :projects, :created_at, :datetime
    add_column :projects, :updated_at, :datetime
  end
end

Non null: false, pas d'autres restrictions. Les anciennes lignes continueront d'être valides avec created_atas NULLet update_atas NULL(jusqu'à ce qu'une mise à jour soit effectuée sur la ligne). Les nouvelles lignes auront created_atet seront updated_atremplies comme prévu.

Kostis
la source
1

Le problème avec la plupart des réponses ici est que si vous par défaut, Time.zone.nowtous les enregistrements auront l'heure à laquelle la migration a été exécutée comme heure par défaut, ce qui n'est probablement pas ce que vous voulez. Dans les rails 5, vous pouvez utiliser à la place now(). Cela définira les horodatages pour les enregistrements existants comme l'heure à laquelle la migration a été exécutée et comme l'heure de début de la transaction de validation pour les enregistrements nouvellement insérés.

class AddTimestampsToUsers < ActiveRecord::Migration def change add_timestamps :users, default: -> { 'now()' }, null: false end end

jlesse
la source
1

L'utilisation Time.currentest un bon style https://github.com/rubocop-hq/rails-style-guide#timenow

def change
  change_table :users do |t|
    t.timestamps default: Time.current
    t.change_default :created_at, from: Time.current, to: nil
    t.change_default :updated_at, from: Time.current, to: nil
  end
end

ou

def change
  add_timestamps :users, default: Time.current
  change_column_default :users, :created_at, from: Time.current, to: nil
  change_column_default :users, :updated_at, from: Time.current, to: nil
end
Shilovk
la source
1

Ceci est simple pour ajouter un horodatage dans une table existante.

class AddTimeStampToCustomFieldMeatadata < ActiveRecord::Migration
  def change
    add_timestamps :custom_field_metadata
  end
end
Dinesh Vaitage
la source
0

Pour ceux qui n'utilisent pas Rails mais utilisent activerecord, ce qui suit ajoute également une colonne à un modèle existant, par exemple pour un champ entier.

ActiveRecord::Schema.define do
  change_table 'MYTABLE' do |table|
    add_column(:mytable, :my_field_name, :integer)
  end
end
peter
la source
0

Ce n'est changepas change_tablepour Rails 4.2:

class AddTimestampsToUsers < ActiveRecord::Migration
  def change
    add_timestamps(:users)
  end
end
Igor T.
la source
0

Cela semble être une solution propre dans Rails 5.0.7 (découverte de la méthode change_column_null):

def change
  add_timestamps :candidate_offices, default: nil, null: true
  change_column_null(:candidate_offices, :created_at, false, Time.zone.now)
  change_column_null(:candidate_offices, :created_at, false, Time.zone.now)
end
Wes Gamble
la source
0

Je suis sur les rails 5.0 et aucune de ces options n'a fonctionné.

La seule chose qui fonctionnait était d'utiliser le type à être: timestamp et non: datetime

def change
    add_column :users, :created_at, :timestamp
    add_column :users, :updated_at, :timestamp
end
Vishnu Narang
la source
-1

J'ai personnellement utilisé ce qui suit, et il a mis à jour tous les enregistrements précédents avec l'heure / la date actuelle:

add_column :<table>, :created_at, :datetime, default: Time.zone.now, null: false
add_column :<table>, :updated_at, :datetime, default: Time.zone.now, null: false
Jaime
la source
-2

J'ai rencontré le même problème sur Rails 5 en essayant d'utiliser

change_table :my_table do |t|
    t.timestamps
end

J'ai pu ajouter les colonnes d'horodatage manuellement avec les éléments suivants:

change_table :my_table do |t|
    t.datetime :created_at, null: false, default: DateTime.now
    t.datetime :updated_at, null: false, default: DateTime.now
end
Andres Rosales
la source
cela ne définit-il pas toujours la valeur par défaut avec l'heure au moment où la migration a été exécutée? (donc pas vraiment un horodatage dynamique géré par la DB)
Guillaume Petit
pour les enregistrements qui existent déjà dans votre base de données, oui, il définira le created_at et le updated_at sur la date-heure de l'exécution de la migration. Sans avoir ces valeurs au préalable, indiquez comment vous initialiseriez ces valeurs. EDIT: Ce serait juste considéré comme le début de l'histoire de cette ligne
Andres Rosales