I'm trying to perform the following up migration to change the column "number" in the "tweet" model's table
class ChangeDataTypeForTweetsNumber < ActiveRecord::Migration
def up
change_column :tweets do |t|
t.change :number, :integer
end
end
def down
change_table :tweets do |t|
t.change :number, :string
end
end
end
Upon performing the the following up migration to heroku....
heroku rake db:migrate:up VERSION=20120925211232
I get the following error
PG::Error: ERROR: column "number" cannot be cast to type integer
: ALTER TABLE "tweets" ALTER COLUMN "number" TYPE integer
Any thoughts you have would be very much appreciated.
Thanks everyone.
Same as above but a little bit more concise:
change_column :yourtable, :column_to_change, 'integer USING CAST("column_to_change" AS integer)'
From the fine manual:
[ALTER TABLE ... ALTER COLUMN ...]
The optional USING clause specifies how to compute the new column value from the old; if omitted, the default conversion is the same as an assignment cast from old data type to new. A USING clause must be provided if there is no implicit or assignment cast from old to new type.
There is no implicit conversion from varchar to int in PostgreSQL so it complains that column "number" cannot be cast to type integer and the ALTER TABLE fails. You need to tell PostgreSQL how to convert the old strings to numbers to match the new column type and that means that you need to get a USING clause into your ALTER TABLE. I don't know of any way to make Rails do that for you but you can do it by hand easily enough:
def up
connection.execute(%q{
alter table tweets
alter column number
type integer using cast(number as integer)
})
end
You'll want to watch out for values that can't be cast to integers, PostgreSQL will let you know if there are problems and you'll have to fix them before the migration will succeed.
Your existing down-migration should be fine, converting integer to varchar should be handled automatically.
Related
I'm trying to change gender column to integer in my rails app. It is said that when using postgresql, we have to write a little bit different way. i.e.
change_column :users, :gender, :integer, using: 'gender::integer'
or
change_column :users, :gender, 'integer USING CAST(gender AS integer)'
However, in my case, the aboves are still not working and got the below error.
PG::DatatypeMismatch: ERROR: default for column "gender" cannot be cast automatically to type integer
: ALTER TABLE "users" ALTER COLUMN "gender" TYPE integer USING gender::integer
Please tell me why it doesn't work. Or some weird things I've got? For example typo.
My environment
Ruby: 2.5.0
Rails: 5.1.6
Postgres: 11.1
Based on the error message you're seeing, Postgres does not know how to keep the default value you've set while changing the database type to integer. I'd suggest:
Dropping the default
Changing the datatype on the column
Adding the default in the new type
You may be able to do steps 2 and 3 in the same call. You'll want to use something like the below in the up migration:
def up
execute "ALTER TABLE users ALTER gender DROP DEFAULT;"
change_column :users, :gender, :integer, using: 'gender::integer', default: 0
end
If I've got a production database that has "types" stored as string, but I want to convert that column to an integer for enum.
I've googled/SO'd, and I see that I can CAST, but not sure what that does exactly.
If it's not hard, I'd love ot use rails enum, but otherwise, maybe I should stick with my string schema...
Please advise!
You can rename existing column, create a new one called "types" (integer), then write a script that stores appropriate integer value in the new column, and then drop the old column.
The migration will look like this:
class FixTypes < ActiveRecord::Migration
def change
rename_column :table_name, :types, :old_types
add_column :table_name, :types, :integer
end
end
Then write a script that sets the value of "types" based on "old_types":
Model.all.each do |entry|
entry.types = %w(status1 status2 status3 status4).index(entry.old_types)
entry.save!
end
And then drop the "old_types" column.
I need to index a table of users using an externally sourced id, which is a 64-bit integer. Rails is perfectly capable of storing such a number, unless it's the primary key it seems. I have the following migration:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users, :id => false do |t|
t.integer :id, limit: 8
t.string :name
t.timestamps null: false
end
end
end
The migration works fine, no errors reported, but when I attempt to seed it with a 64-bit integer, I'm told off by this:
RangeError: 76561198054432981 is out of range for ActiveRecord::Type::Integer with limit 4
Obviously Rails is ignoring the limit field, so long as it's the primary key/the :id field? How should I go about dealing with this?
For what it's worth I'm using sqlite3 (default), but to my knowledge, sqlite is perfectly capable of storing 64-bit integers.
Here's the table_info from sqlite:
0|id|integer(8)|0||0
1|name|varchar|0||0
2|created_at|datetime|1||0
3|updated_at|datetime|1||0
The limit value you gave is correct; it corresponds to BIGINT type
Make sure your migration is applied; open you database in some CLI or GUI software and verify the col-type
Addition:
Changing a column's length or datatype in a migration will invalidate the column as a primary key. Rather, creating an initializer that overrides the site's default primary key datatype should provide the behavior you're looking to implement:
# config/initializers/change_primary_key_datatype.rb
require 'active_record/connection_adapters/postgresql_adapter'
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:primary_key] = "bigserial primary key"
This is what we would do for PG database; This is possible because of
however in the code base of SQLite there is
I have a status column in a table that I want to be an enum. Originally I created that field as an integer, thinking that I would use the built in Rails enum functionality. Turns out that requires at least Rails 4.1, but I am using 4.0 and the process of upgrading is going to take some time.
But thinking about how this all works, I realized that I can have either an ActiveRecord enum or a postgres enum, not both. I thought that in the long term having a more explicit postgres enum would be best. So, I wrote a migration to convert the status column from an integer to an enum.
execute "CREATE TYPE status_options AS ENUM ('pending', 'declined', 'approved');"
change_column :site_applications, :status, "status_options USING status::status_options"
But, I get this error:
PG::CannotCoerce: ERROR: cannot cast type integer to status_options
ALTER TABLE "site_applications" ALTER COLUMN "status" TYPE status_options USING status::status_options
Everything that I have seen so far in my searchings tells me that should have worked, but it doesn't. I thought maybe the problem is that I just can't go from integer to enum. So be it. My solution was to first convert the column to a string and then try to convert it to enum.
change_column :site_applications, :status, :string
execute "CREATE TYPE status_options AS ENUM ('pending', 'declined', 'approved');"
change_column :site_applications, :status, "status_options USING status::status_options"
And that gives me the following error:
PG::DatatypeMismatch: ERROR: default for column "status" cannot be cast automatically to type status_options
ALTER TABLE "site_applications" ALTER COLUMN "status" TYPE status_options USING status::status_options
That led me to believe that this had something to do with the default value, so I tried specifying the default in the change_column declaration:
change_column :site_applications, :status, :string, default: "pending"
That successfully changes the column to a string with a default of "pending", but change_column fails with the same "default for column" error.
I realize that I could simply drop the column all together and then recreate it exactly how I want, but at this point it's a matter of posterity. Why the heck can't I convert a column from integer or string to enum? Anyone?
UPDATE WITH ACCEPTED ANSWER
Based on Gary's answer down there, this is the migration that worked.
def up
execute "ALTER TABLE site_applications ALTER status DROP DEFAULT;"
execute "CREATE TYPE status_options AS ENUM ('pending', 'declined', 'approved');"
change_column :site_applications, :status, "status_options USING status::status_options", default: "pending"
end
def down
change_column :site_applications, :status, :string, default: "pending"
execute "DROP TYPE status_options;"
end
You need to remove the default value from the column prior to the change as the default is set to a value that is valid for the old column type but incompatible with the new type.
alter table schema.site_applications alter status drop default
Then you can change the column type. Finally once the new column type is applied, you can add a new default against the table.
alter table schema.site_applications alter status set default 'pending'::status_options
I already google'd aroung a little bit and seems there's no satisfying answer for my problem.
I have a table with column of type string.
I'd like to run following migration:
class ChangeColumnToBoolean < ActiveRecord::Migration
def up
change_column :users, :smoking, :boolean
end
end
When I run this I get following error
PG::Error: ERROR: column "smoking" cannot be cast automatically to type boolean
HINT: Specify a USING expression to perform the conversion.
: ALTER TABLE "users" ALTER COLUMN "smoking" TYPE boolean
I know I can perform this migration using pure SQL but still it would be nicer if I could do it with Rails. I went through Rails code and seems theres no such possibility, but maybe someone knows a way?
I'm not interested in:
- pure SQL
- dropping the column
- creating another column, converting data, dropping original and then renaming
If your strings in smoking column are already valid boolean values, the following statement will change the column type without losing data:
change_column :users, :smoking, 'boolean USING CAST(smoking AS boolean)'
Similarly, you can use this statement to cast columns to integer:
change_column :table_name, :column_name, 'integer USING CAST(column_name AS integer)'
I am using Postgres. Not sure whether this solution works for other databases.
Not all databases allow changing of column type, the generally taken approach is to add a new column of the desired type, bring any data across, remove the old column and rename the new one.
add_column :users, :smoking_tmp, :boolean
User.reset_column_information # make the new column available to model methods
User.all.each do |user|
user.smoking_tmp = user.smoking == 1 ? true : false # If smoking was an int, for example
user.save
end
# OR as an update_all call, set a default of false on the new column then update all to true if appropriate.
User.where(smoking: 1).update_all(smoking_tmp: true)
remove_column :users, :smoking
rename_column :users, :smoking_tmp, :smoking
So right for boolean in postgres:
change_column :table_name, :field,'boolean USING (CASE field WHEN \'your any string as true\' THEN \'t\'::boolean ELSE \'f\'::boolean END)'
and you may add some more WHEN - THEN condition in your expression
For other database servers, the expression will be constructed based on the syntax for your database server, but the principle is the same. Only manual conversion algorithm, entirely without SQL there is not enough unfortunately.
The syntax change_column :table, :field, 'boolean USING CAST(field AS boolean)' is suitable only if the contents of the field something like: true / false / null
Since I'm using Postgres, I went with SQL solution for now.
Query used:
execute 'ALTER TABLE "users" ALTER COLUMN "smoking" TYPE boolean USING CASE WHEN "flatshare"=\'true\' THEN \'t\'::boolean ELSE \'f\'::boolean END'
It works only if one has a field filled with true/false strings (such as default radio button collection helper with forced boolean type would generate)