Add timestamps to an existing table - ruby-on-rails

I need to add timestamps (created_at & updated_at) to an existing table. I tried the following code but it didn't work.
class AddTimestampsToUser < ActiveRecord::Migration
def change_table
add_timestamps(:users)
end
end

The timestamp helper is only available in the create_table block. You can add these columns by specifying the column types manually:
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
While this does not have the same terse syntax as the add_timestamps method you have specified above, Rails will still treat these columns as timestamp columns, and update the values normally.

Migrations are just two class methods (or instance methods in 3.1): up and down (and sometimes a change instance method in 3.1). You want your changes to go into the up method:
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
If you're in 3.1 then you could also use change (thanks Dave):
class AddTimestampsToUser < ActiveRecord::Migration
def change
change_table(:users) { |t| t.timestamps }
end
end
Perhaps you're confusing def change, def change_table, and change_table.
See the migration guide for further details.

#user1899434's response picked up on the fact that an "existing" table here could mean a table with records already in it, records that you might not want to drop. So when you add timestamps with null: false, which is the default and often desirable, those existing records are all invalid.
But I think that answer can be improved upon, by combining the two steps into one migration, as well as using the more semantic add_timestamps method:
def change
add_timestamps :projects, default: Time.zone.now
change_column_default :projects, :created_at, nil
change_column_default :projects, :updated_at, nil
end
You could substitute some other timestamp for DateTime.now, like if you wanted preexisting records to be created/updated at the dawn of time instead.

Your original code is very close to right, you just need to use a different method name. If you're using Rails 3.1 or later, you need to define a change method instead of change_table:
class AddTimestampsToUser < ActiveRecord::Migration
def change
add_timestamps(:users)
end
end
If you're using an older version you need to define up and down methods instead of change_table:
class AddTimestampsToUser < ActiveRecord::Migration
def up
add_timestamps(:users)
end
def down
remove_timestamps(:users)
end
end

class AddTimestampsToUser < ActiveRecord::Migration
def change
change_table :users do |t|
t.timestamps
end
end
end
Available transformations are
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

Nick Davies answer is the most complete in terms of adding timestamp columns to a table with existing data. Its only downside is that it will raise ActiveRecord::IrreversibleMigration on a db:rollback.
It should be modified like so to work in both directions:
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

The issue with most of the answers here is that if you default to Time.zone.now all records will have the time that the migration was run as their default time, which is probably not what you want. In rails 5 you can instead use now(). This will set the timestamps for existing records as the time the migration was run, and as the start time of the commit transaction for newly inserted records.
class AddTimestampsToUsers < ActiveRecord::Migration
def change
add_timestamps :users, default: -> { 'now()' }, null: false
end
end

def change
add_timestamps :table_name
end

Using Time.current is a good 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
or
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

I'm on rails 5.0 and none of these options worked.
The only thing that worked was using the type to be :timestamp and not :datetime
def change
add_column :users, :created_at, :timestamp
add_column :users, :updated_at, :timestamp
end

not sure when exactly this was introduced, but in rails 5.2.1 you can do this:
class AddTimestampsToMyTable < ActiveRecord::Migration[5.2]
def change
add_timestamps :my_table
end
end
for more see "using the change method" in the active record migrations docs.

This seems like a clean solution in Rails 5.0.7 (discovered the change_column_null method):
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

A lot of answers here, but I'll post mine too because none of the previous ones really worked for me :)
As some have noted, #add_timestamps unfortunately adds the null: false restriction, which will cause old rows to be invalid because they don't have these values populated. Most answers here suggest that we set some default value (Time.zone.now), but I wouldn't like to do that because these default timestamps for old data will not be correct. I don't see the value in adding incorrect data to the table.
So my migration was simply:
class AddTimestampsToUser < ActiveRecord::Migration
def change_table
add_column :projects, :created_at, :datetime
add_column :projects, :updated_at, :datetime
end
end
No null: false, no other restrictions. Old rows will continue being valid with created_at as NULL, and update_at as NULL (until some update is performed to the row). New rows will have created_at and updated_at populated as expected.

I made a simple function that you can call to add to each table (assuming you have a existing database) the created_at and updated_at fields:
# 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

add_timestamps(table_name, options = {}) public
Adds timestamps (created_at and updated_at) columns to table_name. Additional options (like null: false) are forwarded to #add_column.
class AddTimestampsToUsers < ActiveRecord::Migration
def change
add_timestamps(:users, null: false)
end
end

This is a simple one to add timestamp in existing table.
class AddTimeStampToCustomFieldMeatadata < ActiveRecord::Migration
def change
add_timestamps :custom_field_metadata
end
end

In rails 6 (and possibly earlier) if you try to add timestamps to an existing table with records already present like this:
def change
add_timestamps :table_name
end
you will get an error owing to the fact that add_timestamps by default declares the new colums as NOT NULL. You can work around this simply by adding null: true as an argument:
def change
add_timestamps :table_name, null: true
end

The answers before seem right however I faced issues if my table already has entries.
I would get 'ERROR: column created_at contains null values'.
To fix, I used:
def up
add_column :projects, :created_at, :datetime, default: nil, null: false
add_column :projects, :updated_at, :datetime, default: nil, null: false
end
I then used the gem migration_data to add the time for current projects on the migration such as:
def data
Project.update_all created_at: Time.now
end
Then all projects created after this migration will be correctly updated. Make sure the server is restarted too so that Rails ActiveRecord starts tracking the timestamps on the record.

You can use a migration like this to add a created_at and updated_at columns to an existing table with existing records. This migration sets the created_at and updated_at fields of existing records to the current date time.
For the sake of this example say the table name is users and the model name is User
class AddTimestampsToTcmOrders < ActiveRecord::Migration[6.0]
def up
# Add timestamps to the users table with null as true cause there are existing records
add_timestamps(:users, null: true)
# Update existing records with non-nil timestamp values
User.update_all(created_at: DateTime.now, updated_at: DateTime.now)
# change columns so they can't be nil
change_column(:users, :updated_at, :datetime, null: false, precision: 6)
change_column(:users, :created_at, :datetime, null: false, precision: 6)
end
def down
remove_column :users, :updated_at
remove_column :users, :created_at
end
end

For those who don't use Rails but do use activerecord, the following also adds a column to an existing model, example is for an integer field.
ActiveRecord::Schema.define do
change_table 'MYTABLE' do |table|
add_column(:mytable, :my_field_name, :integer)
end
end

It's change, not change_table for Rails 4.2:
class AddTimestampsToUsers < ActiveRecord::Migration
def change
add_timestamps(:users)
end
end

I personally used the following, and it updated all previous records with the current time/date:
add_column :<table>, :created_at, :datetime, default: Time.zone.now, null: false
add_column :<table>, :updated_at, :datetime, default: Time.zone.now, null: false

I ran into the same issue on Rails 5 trying to use
change_table :my_table do |t|
t.timestamps
end
I was able to add the timestamp columns manually with the following:
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

Related

Difference between t.change and change_column in rails?

I am new in learning ruby on rails, I am confused when to use change_column and when to use t.change for migrations?
For example
class CreateProducts < ActiveRecord::Migration[7.0]
def change
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
The def change method is part of all migrations. This method contains the changes that you want to apply during a given migration.
All your migrations will either have change method or an up and down method. If you define a change method like this:
class CreateProducts < ActiveRecord::Migration[7.0]
def change
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
then when you apply this migration, a table will be created, and when you roll back this migration, Rails will try to generate a reverse of the migration. In this case, the reverse of create_table would be to drop the table.
Now suppose you already have this table created, but then you realize that you want to limit the length of the name field, then you can generate a migration to do that. This migration will use change_column method because you are now trying to change the definition of an existing column.
class LimitProductName < ActiveRecord::Migration[7.0]
def change
change_column :products, :name, :string, limit: 100
end
end

rails migration using up/down to add and update existing records

I am having to add a 2 new columns to an existing table, those are created_at, updated_at. However the created_at and updated_at fields should not be null except there are several existing records in my db so a simple change migration is not working. I decided to go with an up/down migration because i needed to update those records first before setting those fields to not null.
My migration files looks like so:
Class AddColumnsToCharge < ActiveRecord::Migration[6.0]
def up
add_column :charges, :created_at, :datetime
add_column :charges, :updated_at, :datetime
Charge.update_all(created_at: Time.now, updated_at: Time.now)
end
def down
change_column :charges, :created_at, :datetime, null:false
change_column :charges, :updated_at, :datetime, null:false
end
end
Now that seems to work because I no longer get errors complaining about existing columns with created_at/updated_at fields as null values. However my schema.rb does not show those fields as null: false instead i see
create_table "charges", force: :cascade do |t|
t.bigint 'amount'
t.datetime 'created_at'
t.datetime 'updated_at'
what I expected to see was:
create_table "charges", force: :cascade do |t|
t.bigint 'amount'
t.datetime 'created_at', null: false
t.datetime 'updated_at', null: false
Is my down not being executed or am i doing something wrong in my migration file?(Thanks for any help in advance)
down is used when you run rails db:rollback, is that what you were trying to do?
up is used when running rails db:migrate. Usually Rails knows what to do so using change is enough in migrations, but sometimes you need do use up/down so the migration is reversible.
From the docs
You can also use the old style of migration using up and down methods
instead of the change method. The up method should describe the
transformation you'd like to make to your schema, and the down method
of your migration should revert the transformations done by the up
method. In other words, the database schema should be unchanged if you
do an up followed by a down. For example, if you create a table in the
up method, you should drop it in the down method.
You need this:
class AddTimestamps < ActiveRecord::Migration[6.1]
def change
reversible do |dir|
change_table :charges do |t|
dir.up do
t.timestamps
Charge.update_all(created_at: Time.now, updated_at: Time.now)
end
dir.down do
remove_column :charges, :created_at
remove_column :charges, :updated_at
end
end
end
end
end
The default value of created_at and updated_at is not reflected in the schema b/c it's always populated by ActiveRecord, they don't have database-configured default values. The value assigned is dynamic, you can only put fixed default values in the schema.
The null: false is automatically added (Rails 5 onwards) when you specify created_at using the timestamps method. The option is not automatically added when you explicitly create/remove the created_at/updated_at columns.
It sounds to me like you're looking for something like this.
add_column :charges, :created_at, :datetime
add_column :charges, :updated_at, :datetime
change_column_null(:charges, :created_at, false, Time.now)
change_column_null(:charges, :updated_at, false, Time.now)
Check this out change_column_null
I was able to solve my issue the long way by running two different migrations.
My first migration added the new columns:
Class AddColumnsToCharge < ActiveRecord::Migration[6.0]
def change
add_column :charges, :created_at, :datetime
add_column :charges, :updated_at, :datetime
end
end
Before Running the second migration I went into the rails console and updated the current records: Charge.update_all(created_at: Time.now, updated_at: Time.now)
The second migration:
Class AddColumnsToCharge < ActiveRecord::Migration[6.0]
def change
change_column :charges, :created_at, :datetime, null:false
change_column :charges, :updated_at, :datetime, null:false
end
end
This answer does not answer the initial question but got the job done which is ultimately what i wanted.

When using change_column_default on a column with no default, what should be the 'from' value?

This is my create table migration. Please note that I didn't provide the default value for price.
class CreateProducts < ActiveRecord::Migration[5.0]
def change
create_table :products do |t|
t.string :name
t.decimal :price, precision: 8, scale: 2
t.timestamps
end
end
end
Now I want to set a default value. According to the Migration guide, I should provide from to make it reversible. What value should I provide?
class ChangeProductsPriceDefault < ActiveRecord::Migration[5.0]
def change
change_column_default :products, :price, from: 'WHAT_TO_WRITE_HERE?', to: 0
end
end
"No default" means default is NULL, so:
change_column_default :products, :price, from: nil, to: 0
There is a description about nil as default: http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-change_column_default

Default rails id column name in Rails

I wanna rename the column id, how do I do?
I want to set number to primary key and auto increment, and id to just string of user id.
How do I do?
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :user
t.timestamps
end
rename_column :users, :id, :number
end
end
I did like above, but it didn't work.
Even if I wouldn't recommend it, here is how I guess you can do it:
in your migration:
def up
create_table :users, id: false do |t|
t.string :user
t.integer :number, null: false, index: true, unique: true
t.timestamps
end
execute %Q{ ALTER TABLE "users" ADD PRIMARY KEY ("number"); }
end
def down
drop_table :users
end
in your model:
self.primay_key = 'number'

When I run the rake:db migrate command I get an error "Uninitialized constant CreateArticles"

I created a model ruby script/generate model Article (simple enuff)
Here is the migration file create_articles.rb:
def self.up
create_table :articles do |t|
t.column :user_id, :integer
t.column :title, :string
t.column :synopsis, :text, :limit => 1000
t.column :body, :text, :limit => 20000
t.column :published, :boolean, :default => false
t.column :created_at, :datetime
t.column :updated_at, :datetime
t.column :published_at, :datetime
t.column :category_id, :integer
end
def self.down
drop_table :articles
end
end
When I run the rake:db migrate command I receive an error rake aborted! "Uninitialized constant CreateArticles."
Does anyone know why this error keeps happening?
Be sure that your file name and class name say the same thing(except the class name is camel cased).The contents of your migration file should look something like this, simplified them a bit too:
#20090106022023_create_articles.rb
class CreateArticles < ActiveRecord::Migration
def self.up
create_table :articles do |t|
t.belongs_to :user, :category
t.string :title
t.text :synopsis, :limit => 1000
t.text :body, :limit => 20000
t.boolean :published, :default => false
t.datetime :published_at
t.timestamps
end
end
def self.down
drop_table :articles
end
end
It's possible to get the given error if your class names don't match inflections (like acronyms) from config/initializers/inflections.rb.
For example, if your inflections include:
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym 'DOG'
end
then you might need to make sure the class in your migration is:
class CreateDOGHouses < ActiveRecord::Migration[5.0]
rather than:
class CreateDogHouses < ActiveRecord::Migration[5.0]
Not super common, but if you generate a migration or a model or something, and then add part of it to inflections afterwards it may happen. (The example here will cause NameError: uninitialized constant CreateDOGHouses if your class name is CreateDogHouses, at least with Rails 5.)
If you're getting this error and it's NOT because of the migration file name, there is another possible solution. Open the class directly in the migration like this:
class SomeClass < ActiveRecord::Base; end
It should now be possible to use SomeClass within the migration.
The top answer solved for me. Just leaving this here in case it helps.
Example
If your migration file is called
20210213040840_add_first_initial_only_to_users.rb
then the class name in your migration file should be
AddFirstInitialOnlyToUsers
Note: if the class name doesn't match, it will error even if the difference is just a lower case t instead of an upper case 'T' in 'To' - so be careful of that!

Resources