Cast old string values to datetime with migration in Rails PostgreSQL - ruby-on-rails

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

Related

Rails 7 Is it possible to remove data before migration?

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

How change column type works in rails migration

We know, rails ActiveRecord::Migration now have a new method
def change
add_column :accounts, :name, :string
add_index :accounts, :name
change_column :my_table, :some_id, :string
end
But my question is for
change_column :my_table, :some_id, :string
rails do not need to know :some_id's previous type is integer or not.
For example assume :some_id was an integer, after this migration it is converted to string.
when I revert this migration :some_id type should be integer again. Am i right ?? but how can rails understand :some_id previous type was integer.
in previous mehtod self.up and self.down it is written in migration file. so it was not problem. rails can easily find that. but in change method how it is recollected?? Does it check the migration for this table in previous migration files where last data type was definded for :some_id or anything else ??
The change_column is an irreversible migration method. So you cannot reverse this migration using change method. To do this you need to write up and down methods. If you just write this in change method, when you run
rake db:rollback
it will throw this exception
ActiveRecord::IrreversibleMigration
You can read more more on:
http://edgeguides.rubyonrails.org/active_record_migrations.html#changing-columns

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 date_select

Right now I am saving my dates as a string in the format of mm/dd/yyyy, but want to convert to date_select but I keep getting errors for some reason.
Here is the code that I am using
the form
<%= f.date_select :start_date %>
the model
validates :start_date, :presence => true
but I get an error from my controller saying that it doesnt fit the params.
That's because of the way Rails automatically looks at a database column to figure out what type of object is going to be stored there. In this case, Rails is looking for a Datetime column to be used in conjunction with the date_select helper, but instead it's finding a varchar column.
I would run a migration to drop the start_date column, and re-add it as a datetime column, like so
To generate a new migration:
rails generate migration [name of your migration]
In your case something like:
rails generate migration change_start_date_column_to_timestamp
This will generate a file in your RAILS_ROOT/db/migrations folder, which will look something like:
class ChangeStartDateColumnToTimestamp < ActiveRecord::Migration
def self.up
end
def self.down
end
end
And you need to modify it to look like:
class ChangeStartDateColumnToTimestamp < ActiveRecord::Migration
def self.up
remove_column :table_name, :start_date
add_column :table_name, :start_date, :timestamp
end
def self.down
remove_column :table_name, :start_date
add_column :table_name, :start_date, :string
end
end
Then, when rails pulls the data from the database, it'll automatically convert them to Ruby Time objects.
A word of caution... this will destroy the data in the start_date field. So if you have pre-existing information that needs to be preserved, you need to do something more complicated.

Ruby/Rails - Active Record Db Migration to MySQL - timestamp type?

I am using Redmine and when I run a rake db migration the database gets created in mysql ok. My problem is that the date field is wrong. I want it to be a mysql timestamp type, but instead it is a DATETIME type in MySQL.
class CreateChats < ActiveRecord::Migration
def self.up
create_table :chats do |t|
t.column :message, :string
t.column :user, :integer
t.column :sendDate, :timestamp
end
end
def self.down
drop_table :chats
end
end
In addition if I make a change to this migration how do I get it to remake the table (deleting it doesn't work)?
You can always insert a column with a custom type if you want. The symbol names are automatically converted into whatever ActiveRecord defines, but if you use a plain string it goes in as-is:
t.column :ar_timestamp, :timestamp
t.column :mysql_timestamp, 'timestamp'
What you get is ar_timestamp being the usual DATETIME type where mysql_timestamp is defined as TIMESTAMP.

Resources