Rails 7 Is it possible to remove data before migration? - ruby-on-rails

I want to create migration where I delete some records and then change column type. Something like that:
def up
Company.where.not(activation_date: nil).delete_all
change_column :companies, :activation_date, :datetime
end
def down
Company.where.not(activation_date: nil).delete_all
change_column :companies, :activation_date, :time
end
But it looks like change_column execute before .delete_all because migration throws an error about converting date to datetime. Am I doing something wrong or it just can't be solved this way?
#Edit
Error:
PG::DatatypeMismatch: ERROR: column "activation_date" cannot be cast automatically to type timestamp without time zone
Solution:
def up
Company.where.not(activation_date: nil).delete_all
change_column :companies, :activation_date, "timestamp USING ('2000-1-1'::date + activation_date)"
end
def down
Company.where.not(activation_date: nil).delete_all
change_column :companies, :activation_date, :time
end

Related

Rails migration to change column type from text to json (Postgresql)

I've been trying unsuccessfully to change a column type in my Postgres database from text to json. Here's what I've tried...
class ChangeNotesTypeInPlaces < ActiveRecord::Migration[5.0]
def up
execute 'ALTER TABLE places ALTER COLUMN notes TYPE json USING (notes::json)'
end
def down
execute 'ALTER TABLE places ALTER COLUMN notes TYPE text USING (notes::text)'
end
end
Also...
class ChangeNotesTypeInPlaces < ActiveRecord::Migration[5.0]
def up
change_column :places, :notes, 'json USING CAST(notes AS json)'
end
def down
change_column :places, :notes, 'text USING CAST(notes AS text)'
end
end
Both of these return the same error...
PG::InvalidTextRepresentation: ERROR: invalid input syntax for type json
Using Rails 5.1.x and PostgreSQL 9.4, here is what worked for me when converting text columns (containing valid json) to jsonb columns :
class ChangeTextColumnsToJson < ActiveRecord::Migration[5.1]
def change
change_column :table_name, :column_name, :jsonb, using: 'column_name::text::jsonb'
end
end
I was able to accomplish it using:
def change
change_column :places, :notes, :json, using: 'notes::JSON'
end
This won't be reversible though; I imagine you can split it out into separate up and down definitions, but I didn't feel the need to.
I just solved a similar problem. Trying to adapt it for your question, it would look (mostly?) like this.
class ChangeNotesTypeInPlaces < ActiveRecord::Migration[5.0]
def up
change_column :places, :notes, :jsonb, using: 'CAST(value AS JSON)'
end
def down
change_column :places, :notes, :text
end
end

Cast old string values to datetime with migration in Rails PostgreSQL

I had a couple of date fields in a database table, however they are firstly initiated as string, not datetime. Therefore, I wanted to change those value types to datetype with a migration,
class ChangeDateColumnsToDateTime < ActiveRecord::Migration
def change
change_column :users, :flight_date_departure, :datetime
change_column :users, :flight_date, :datetime
change_column :users, :appointment_date, :datetime
end
end
however it can not cast old string values to datetimes that exists in database currently, saying that
PG::DatatypeMismatch: ERROR: column "flight_date_departure" cannot be cast automatically to type timestamp without time zone. HINT: You might need to specify "USING flight_date_departure::timestamp without time zone". We've done it without problem in a SQLite database, however there is this problem for PostgreSQL. How can I modify my migration so that I do not lose old values and properly convert them to datetime?
I've tried the way bellow and it worked like a charm:
change_column :table_name, :column_name, 'timestamp USING CAST(column_name AS timestamp)'
try like this:
change_column :table_name, :column_name, 'datetime USING CAST(column_name AS datetime)'
here is my working code
class AddSrCreateDateToCart < ActiveRecord::Migration[5.2]
def change
add_column :carts, :sr_create_date, :datetime
change_column :carts, :sr_create_date, 'datetime USING CAST(sr_create_date AS timestamp)'
end
end
postgres is now downloading the data
It would be better to make this a reversible migration.
def up
change_column :table_name, :column_name, 'timestamp USING CAST(column_name AS timestamp)'
end
def down
change_column :table_name, :column_name, 'date USING CAST(column_name AS date)'
end

Rails: change column type, but keep data

I have a model with a column of type integer which I want to convert to type string. Now I'm looking for the best way to change the column type without losing the data. Is there a painless way to accomplish this?
A standard migration using the change_column method will convert integers to strings without any data loss. rake db:rollback will also do the reverse migration without error if required.
Here is the test migration I used to confirm this behaviour:
class ChangeAgeToString < ActiveRecord::Migration
def self.up
change_column :users, :age, :string
end
def self.down
change_column :users, :age, :integer
end
end
for postgres
in migration
change_column :table_name, :field,'boolean USING (CASE field WHEN \'your any string as true\' THEN \'t\'::boolean ELSE \'f\'::boolean END)'
and to any valid type similar
for postgresql, change table column datatype integer to string,
rails migration like this with up and down actions
class ChangeAgeToString < ActiveRecord::Migration
def self.up
change_column :users, :age, 'varchar USING CAST(age AS varchar)', null: false
end
def self.down
change_column :users, :age, 'integer USING CAST(age AS integer)', null: false, default: 0
end
end
If it's a one-off you can just change the column type in the database (since no info is lost moving from int to varchar)
For MySQL, this would do it:
ALTER TABLE t1 MODIFY col1 VARCHAR(256)
If you're using SQLite you won't have to do anything.
You can try something like this:
change_column :table_name, :column_name, 'integer USING CAST(column_name AS integer)'
or even better:
change_column :table_name, :column_name, :integer, using: 'column_name::integer'
You can read more about this topic here: https://kolosek.com/rails-change-database-column
If you use Postgres, you can't implicitly cast a string back to an integer, so the way to make the change reversible is:
class ChangeAgeToString < ActiveRecord::Migration
def self.up
change_column :users, :age, :string
end
def self.down
add_column :age_integer
User.connection.execute('UPDATE users SET age_integer = cast(age as int)')
remove_column :users, :age
rename_column :users, :age_integer, :age
end
end

Rails & Postgres: Migration to change_colomn gives error "cannot be cast to type timestamp without time zone"

A Rails migration to turn a "deleted_at" time column to a datetime column failed. Any ideas on how to solve this? It's a fresh install of Postgres if that is relevant.
-- change_column(:products, :deleted_at, :datetime)
PGError: ERROR: column "deleted_at" cannot be cast to type timestamp without time zone
: ALTER TABLE "products" ALTER COLUMN "deleted_at" TYPE timestamp
In Rails this would look something like
class ChangeStatusUpdatedAtToDateTime < ActiveRecord::Migration
def up
remove_column :bookings, :status_updated_at
add_column :bookings, :status_updated_at, :datetime
end
def down
remove_column :bookings, :status_updated_at
add_column :bookings, :status_updated_at, :time
end
end
If you had data you wanted to transfer you could use the following code (not tested!):
class ChangeStatusUpdatedAtToDateTime < ActiveRecord::Migration
def up
add_column :bookings, :temp_status_updated_at, :datetime
Booking.update_all("temp_status_updated_at = updated_at")
remove_column :bookings, :status_updated_at
rename_column :bookings, :temp_status_updated_at, :status_updated_at
end
def down
add_column :bookings, :temp_status_updated_at, :time
Booking.update_all("temp_status_updated_at = updated_at")
remove_column :bookings, :status_updated_at
rename_column :bookings, :temp_status_updated_at, :status_updated_at
end
end
You can't alter a field's type from time to timestamp ("datetime"), because the values couldn't be converted -- the database doesn't know the date.
You can, however, drop and re-create the column:
ALTER TABLE products DROP COLUMN deleted_at;
ALTER TABLE products ADD COLUMN deleted_at timestamp;
Or if this field was set to NOT NULL, you should instead do:
ALTER TABLE products ADD COLUMN deleted_at timestamp NOT NULL;
But if you insist on retaining fake values in this table like Sean, you can use ALTER...TYPE...USING like this:
ALTER TABLE products ALTER COLUMN deleted_at TYPE timestamp USING
CASE WHEN deleted_at IS NOT NULL THEN timestamp '1970-01-01 00:00:00' END;
-- Or:
ALTER TABLE products ALTER COLUMN deleted_at
TYPE timestamp USING date '1970-01-01' + deleted_at;

How do I change column type in Heroku?

I am trying to rake the db:migrations into my heorku instance and I get an error. The FAQ described my error as below:
Cannot change column type
Example: PGError: ERROR: column
“verified_at” cannot be cast to type
“date”
Cause: PostgreSQL doesn’t know how to
cast all the rows in that table to the
specified type. Most likely it means
you have an integer or a string in
that column.
Solution: Inspect your records and
make sure they can be converted to the
new type. Sometimes it’s easier to
just avoid using change_column,
renaming/creating a new column
instead.
How do I change this migration now. This is the problem that I have. For my Contacts table, I created the following:
t.string :date_entered
In a later migration, I do the following:
change_column :contacts, :date_entered, :date
This change_column appears to be the problem.
Should I...change by hand that migration? Is there a way I can clean the data in my tables (I didn't know Heroku would recognize the data in the table because I'm doing a rake).
I obviously need to change this value and it is used throughout my application. Thanks.
This is what I am trying...thoughts?
def self.up
#change_column :contacts, :date_entered, :date
#this fails in postgres, so trying the same outcome
rename_column :contacts, :date_entered, :date_entered_old
add_column :contacts, :date_entered, :date
remove_column :contacts, :date_entered_old
end
def self.down
add_column :contacts, :date_entered_old
remove_column :contacts, :date_entered
rename_column :contacts, :date_entered_old, :date_entered
end
Do the following:
rename the column A
create the new column B as date
move the data from A to B
remove A
In other words
def self.up
rename_column :contacts, :date_entered, :date_entered_string
add_column :contacts, :date_entered, :date
Contact.reset_column_information
Contact.find_each { |c| c.update_attribute(:date_entered, c.date_entered_string) }
remove_column :contacts, :date_entered_string
end
This is a modified and tested version of Simone Carletti's solution
class ModifyContacts < ActiveRecord::Migration
def self.up
rename_column :contacts, :date_entered, :date_entered_string
add_column :contacts, :date_entered, :date
Contact.reset_column_information
Contact.find(:all).each { |contact| contact.update_attribute(:date_entered, contact.date_entered_string) }
remove_column :contacts, :date_entered_string
end
end

Resources