index name too long to rollback migration - ruby-on-rails

I'm on a git branch and trying to rollback two migrations, before destroying the branch. The most recent migration adds a column to a table that I want to keep (and which is part of master, not the branch to be dropped), so dropping the whole table is not a solution (unless I have to recreate it again). Anyways, I must have done something wrong, because when I tried to remove the apple_id column from the scores table, I got this abort error.
This is the migration that I'm trying to rollback
add_column :scores, :apple_id, :integer
However, the error message (see below) is referring to the indexes that I created with the original migration (part of master branch) that created the table
add_index :scores, [:user_id, :created_at, :funded, :started]
Can you suggest what I might do?
== AddAppleidColumnToScores: reverting =======================================
-- remove_column("scores", :apple_id)
rake aborted!
An error has occurred, this and all later migrations canceled:
Index name 'temp_index_altered_scores_on_user_id_and_created_at_and_funded_and_started' on table 'altered_scores' is too long; the limit is 64 characters
Update: reading this SO question How do I handle too long index names in a Ruby on Rails migration with MySQL?, I got some more information about the source of the problem but don't know how to solve it. Both sql and postgres have 64 character limits
Index name 'index_studies_on_user_id_and_university_id_and_subject_\
name_id_and_subject_type_id' on table 'studies' is too long; \
the limit is 64 characters
The accepted answer for the question I refer to says to give the index a name, although I'm not sure how I could do this now that I'm trying to rollback.
add_index :studies, ["user_id", "university_id", \
"subject_name_id", "subject_type_id"],
:unique => true, :name => 'my_index'
Update: in response to the comments, I'm using Rails 3.2.12. This is the migration that adds the column
class AddAppleidColumnToScores < ActiveRecord::Migration
def change
add_column :scores, :apple_id, :integer
end
end
Furthermore, the reason why I didn't want to drop the table was that I was unsure about what problems it might cause in recreating it since a) the main part was created on branch master, while a column added on a branch and b) I was unsure about what to do with the migration file for the dropped table? since it was the fourth (of about 10) tables I created, I don't know how to run it and only it again.

Can you copy/paste your migration?
Here's mine:
class AddAppleIdColumnToScores < ActiveRecord::Migration
def self.up
add_column :scores, :apple_id, :integer
add_index :scores, [:user_id, :created_at, :funded, :started]
end
def self.down
# delete index MySql way
# execute "DROP INDEX index_scores_on_user_id_and_created_at_and_funded_and_started ON scores"
# delete index Postgresql way
# execute "DROP INDEX index_scores_on_user_id_and_created_at_and_funded_and_started"
# delete index Rails way / not necessary if you remove the column
# remove_index :scores, [:user_id, :created_at, :funded, :started]
remove_column :scores, :apple_id
end
end

Related

Add primary key to schema_migrations table

I would like to run the following migration to my Rails(4.x) app but would like to get some feedback from the Rails community first.
class AddUniqueIndexToSchemaMigrations < ActiveRecord::Migration
def change
add_column :schema_migrations, :id, :primary_key
add_index :schema_migrations, ["version"], :name => "unique_versions", :unique => true
end
end
The database in use is MySQL (5.x). Adding this index allows me execute SQL such as:
INSERT INTO schema_migrations
(version)
VALUES ( '2014xxxxxx' )
ON DUPLICATE KEY
UPDATE foo=bar; // or some other bit of cleverness
Is there any reason why this is going off the rails? I don't want to create a situation where I'm fighting against Rails internals.
Thanks in advance!

reversible and revert in Active Record migrations

I have looked at Rails Guides and the Rails API and I can't understand the usage of reversible and revert.
So for example, see the example linked here http://guides.rubyonrails.org/migrations.html#using-reversible and included below:\
It says
Complex migrations may require processing that Active Record doesn't know how to reverse. You can use reversible to specify what to do when running a migration what else to do when reverting it. For example,
class ExampleMigration < ActiveRecord::Migration
def change
create_table :products do |t|
t.references :category
end
reversible do |dir|
dir.up do
#add a foreign key
execute <<-SQL
ALTER TABLE products
ADD CONSTRAINT fk_products_categories
FOREIGN KEY (category_id)
REFERENCES categories(id)
SQL
end
dir.down do
execute <<-SQL
ALTER TABLE products
DROP FOREIGN KEY fk_products_categories
SQL
end
end
add_column :users, :home_page_url, :string
rename_column :users, :email, :email_address
end
I get that the code in the down section is what will be run on rollback, but why include the code in the up block? I have also seen another example which had a reversible section with only an up block. What would the purpose of such code be?
Finally, I do not understand revert. Below is the example included in the Rails Guides, but it makes little sense to me.
`The revert method also accepts a block of instructions to reverse. This could be useful to revert selected parts of previous migrations. For example, let's imagine that ExampleMigration is committed and it is later decided it would be best to serialize the product list instead. One could write:
class SerializeProductListMigration < ActiveRecord::Migration
def change
add_column :categories, :product_list
reversible do |dir|
dir.up do
# transfer data from Products to Category#product_list
end
dir.down do
# create Products from Category#product_list
end
end
revert do
# copy-pasted code from ExampleMigration
create_table :products do |t|
t.references :category
end
reversible do |dir|
dir.up do
#add a foreign key
execute <<-SQL
ALTER TABLE products
ADD CONSTRAINT fk_products_categories
FOREIGN KEY (category_id)
REFERENCES categories(id)
SQL
end
dir.down do
execute <<-SQL
ALTER TABLE products
DROP FOREIGN KEY fk_products_categories
SQL
end
end
# The rest of the migration was ok
end
end
end`
I'm not an expert in this by any means, but my understanding from reading the guides is as follows:
The reversible call in the first example is expressing the second of four components of the change migration. (Note: The indentation you have is misleading in this regard and should probably be updated to match the guide.) It is related to but distinct from the other components, so it makes sense that it would have both an up and down section. I can't explain why you would have reversible with only one direction and not the other, as you indicated you'd seen.
The revert call is telling the system to revert a previous migration either by name or by providing a block describing the (forward) migration. The example you showed is the latter case and I think is best understood by careful reading of the paragraph that follows it in the guide, to wit:
The same migration could also have been written without using revert
but this would have involved a few more steps: reversing the order of
create_table and reversible, replacing create_table by drop_table, and
finally replacing up by down and vice-versa. This is all taken care of
by revert.

Rake aborted... table 'users' already exists

I have created a database with devise and the nifty generator. I'm trying to make a new database with the nifty generator (rails g nifty:scaffold Asset user_id:integer), but when I try to migrate the database (rake db:migrate), I get the following error:
charlotte-dator:showwwdown holgersindbaek$ rake db:migrate
== DeviseCreateUsers: migrating ==============================================
-- create_table(:users)
rake aborted!
An error has occurred, all later migrations canceled:
Mysql2::Error: Table 'users' already exists: CREATE TABLE `users` (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY, `email` varchar(255) DEFAULT '' NOT NULL, `encrypted_password` varchar(128) DEFAULT '' NOT NULL, `reset_password_token` varchar(255), `reset_password_sent_at` datetime, `remember_created_at` datetime, `sign_in_count` int(11) DEFAULT 0, `current_sign_in_at` datetime, `last_sign_in_at` datetime, `current_sign_in_ip` varchar(255), `last_sign_in_ip` varchar(255), `name` varchar(255), `created_at` datetime, `updated_at` datetime) ENGINE=InnoDB
Tasks: TOP => db:migrate
(See full trace by running task with --trace)
I'm following a tutorial and have quite a hard time understanding why this happens. Can anyone explain what is going on?
In your create_users migration (APP_ROOT/db/migrate/..), add drop_table :users right before create_table :users and run rake db:migrate. It will remove the users table before recreating it. You can remove that line of code after running this migration so it doesn't give you errors later on. Just a small fix if you dont have UI access to a database (on heroku, for example).
You need to drop that table from the sql lite console (You will lost all the data contained in it)
Access the sql lite console, type in terminal
mysql <DB NAME HERE>
Drop table (dont forget the last ; (semicolon))
drop table table_name;
run db:migrate again
bin/rake db:migrate
Hope it helps, it worked for me
If you wanna play safe and don't want to lose any data then you can check if the table exists in your database.
class DeviseCreateUsers < ActiveRecord::Migration
def up
if table_exists?(:users)
# update or modify columns of users table here accordingly.
else
# create table and dump the schema here
end
end
def down
# same approach goes here but in the reverse logic
end
end
The migration is trying to create a table that already exists in your database.
Try to remove the user table from your database. Something went wrong with you migration process. You should also compare your schema.rb version with your db/migrate/*.rb files.
Clarification:
It seems that many SO users don't agree with my reply, either because they consider it inaccurate or not recommended.
Removing a table is always destructive, and I think that everyone understands that.
I should have mentioned add_column, since the table was being created in another migration file.
If you know the database was created properly, you can just comment out the creation part of the migration code. For example:
Class ActsAsVotableMigration < ActiveRecord::Migration
def self.up
# create_table :votes do |t|
#
# t.references :votable, :polymorphic => true
# t.references :voter, :polymorphic => true
#
# t.boolean :vote_flag
#
# t.timestamps
# end
#
# add_index :votes, [:votable_id, :votable_type]
# add_index :votes, [:voter_id, :voter_type]
end
def self.down
drop_table :votes
end
end
If the table was created, but later commands weren't completed for some reason, you can just leave the later options for example:
Class ActsAsVotableMigration < ActiveRecord::Migration
def self.up
# create_table :votes do |t|
#
# t.references :votable, :polymorphic => true
# t.references :voter, :polymorphic => true
#
# t.boolean :vote_flag
#
# t.timestamps
# end
add_index :votes, [:votable_id, :votable_type]
add_index :votes, [:voter_id, :voter_type]
end
def self.down
drop_table :votes
end
end
If you don't have any significant data in your database to preserve however you can just have it drop the table and all the data and create it fresh. For example (notice the "drop_table :votes", in the self.up):
class ActsAsVotableMigration < ActiveRecord::Migration
def self.up
drop_table :votes
create_table :votes do |t|
t.references :votable, :polymorphic => true
t.references :voter, :polymorphic => true
t.boolean :vote_flag
t.timestamps
end
add_index :votes, [:votable_id, :votable_type]
add_index :votes, [:voter_id, :voter_type]
end
def self.down
drop_table :votes
end
end
Don't delete tables. Data > migrations!
The version of the database already reflects the changes the error-causing migration is trying to add. In other words, if the migration could be skipped, then everything would be fine. Check the db_schema_migrations table and try inserting the version of the erroneous migration (e.x, 20151004034808). In my case this caused subsequent migrations to execute perfectly and everything seems fine.
Still not sure what caused this problem.
If your app is new and you don't care about the data in your database, simply:
rake db:reset
I think this is an issue unique or more common to mysql in rails, possible having to do with the mysql2 gem itself.
I know this because I just switched from sqlite to mysql and just started having this problem systematically.
In my case, I simply commented out the code that had already run and ran the migration again (which I'm not adding more detail to because it looks like the guy above me did that).
I had a similar problem when trying to add Devise authentication to an existing Users table.
My solution: I found that I had two migrate files, both trying to create the Users table. So rather than deleting the table (probably not the best habit to form), I commented out the first (original) migrate file that created the Users table and then left the Devise migrate file as-is. Re-ran the migration and it worked fine.
As it turns out, the Devise file wasn't causing the problem; I can see that it is "changing" the table, not "creating it", which means that even without the devise installation, a db:migrate probably would have caused the same issue (though I haven't tested this).
If you want to keep your data, rename the table, but do it in the migration to save time, then remove it once the migration has ran.
Place at the top part of the up section of the migration file.
rename_table :users, :users2

Migration issue in Ruby-on-rails

Hey guys, when I first begin a rails project, the model user was designed and created. After all the migration part, it successful created the table "users" at postgres.
Well, then after doing some changes during the project, I realized that was missing an attribute/new column at the table.
So what I did was delete the table users from postgres and add a new column at my first migration ruby class:
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :name
t.string :password
t.string :email
t.string :authorization_token //this is the new attribute that I insert
t.datetime :created_at
t.datetime :updated_at
t.timestamps
end
end
def self.down
drop_table :users
end
end
So, when I run again the db:migrate hopping that a new user table will be created with the new attribute :authorization_token, it doesn't work, but with no errors.
(I know that I should not remove the table, there is another smart way to do it)
A tip for working with Rails -- do not hand modify your tables using SQL. When you saw the problem you should have written a new migration like #nruth showed. Running the rake:migrate command would have worked fine for you.
In this case since you've already deleted your 'users' table you now have the problem that your database schema is out of sync with what Rails thinks it is. To fix this problem you can either get the database schema to roughly match what Rails thinks it is by hand creating the 'users' table, running the down migration and then then the up migration. Or you can get Rails up to speed with the fact that the 'users' table no longer exists. Rails stores migration info in either a schema_info table (Rails < 2.1) or schema_migrations table (Rails >= 2.1). If you remove that table then Rails will think the schema does not exist and try to run all the up migrations and recreate the 'users' table for you again.
Lastly, over time you may accumulate a number of migrations that individually add a column or two that you forgot to include. If you haven't yet shipped or aren't in production yet, then you can write a migration that sort of baselines your table. It would look something like this:
class CreateBaselineUsers < ActiveRecord::Migration
def self.up
create_table :users, :force => true do |t|
t.string :name
...
This will forcibly drop the table and recreate it with all the attributes that you want.
Migrations are run once & stored in the database as having been used (take a look in the schema_migrations table). You could try using rake db:migrate:reset to re-run your initial migration, but it's better to just add new migrations (you won't want to blow away your database when it has data in it) as follows:
script/generate migration add_authorization_token_to_users authorization_token:string
which will generate something similar to the following:
class AddAuthorizationTokenToUsers < ActiveRecord::Migration
def self.up
change_table :users do |t|
t.string :authorization_token //this is the new attribute that I insert
end
end
def self.down
remove_column :users, :authorization_token
end
end
To see how add/remove column, change_table, etc work, take a look at ActiveRecord::ConnectionAdapters::SchemaStatements at http://api.rubyonrails.org or http://guides.rubyonrails.org/migrations.html

Cannot add a NOT NULL column with default value NULL in Sqlite3

I am getting the following error while trying to add a NOT NULL column to an existing table. Why is it happening ?. I tried rake db:reset thinking that the existing records are the problem, but even after resetting the DB, the problem persists. Can you please help me figure this out.
Migration File
class AddDivisionIdToProfile < ActiveRecord::Migration
def self.up
add_column :profiles, :division_id, :integer, :null => false
end
def self.down
remove_column :profiles, :division_id
end
end
Error Message
SQLite3::SQLException: Cannot add a NOT NULL column with default value NULL: ALTER TABLE "profiles" ADD "division_id" integer NOT NULL
This is (what I would consider) a glitch with SQLite. This error occurs whether there are any records in the table or not.
When adding a table from scratch, you can specify NOT NULL, which is what you're doing with the ":null => false" notation. However, you can't do this when adding a column. SQLite's specification says you have to have a default for this, which is a poor choice. Adding a default value is not an option because it defeats the purpose of having a NOT NULL foreign key - namely, data integrity.
Here's a way to get around this glitch, and you can do it all in the same migration. NOTE: this is for the case where you don't already have records in the database.
class AddDivisionIdToProfile < ActiveRecord::Migration
def self.up
add_column :profiles, :division_id, :integer
change_column :profiles, :division_id, :integer, :null => false
end
def self.down
remove_column :profiles, :division_id
end
end
We're adding the column without the NOT NULL constraint, then immediately altering the column to add the constraint. We can do this because while SQLite is apparently very concerned during a column add, it's not so picky with column changes. This is a clear design smell in my book.
It's definitely a hack, but it's shorter than multiple migrations and it will still work with more robust SQL databases in your production environment.
You already have rows in the table, and you're adding a new column division_id. It needs something in that new column in each of the existing rows.
SQLite would typically choose NULL, but you've specified it can't be NULL, so what should it be? It has no way of knowing.
See:
Adding a Non-null Column with no Default Value in a Rails Migration (2009, no longer available, so this is a snapshot at archive.org)
Adding a NOT NULL Column to an Existing Table (2014)
That blog's recommendation is to add the column without the not null constraint, and it'll be added with NULL in every row. Then you can fill in values in the division_id and then use change_column to add the not null constraint.
See the blogs I linked to for an description of a migration script that does this three-step process.
If you have a table with existing rows then you will need to update the existing rows before adding your null constraint. The Guide on migrations recommends using a local model, like so:
Rails 4 and up:
class AddDivisionIdToProfile < ActiveRecord::Migration
class Profile < ActiveRecord::Base
end
def change
add_column :profiles, :division_id, :integer
Profile.reset_column_information
reversible do |dir|
dir.up { Profile.update_all division_id: Division.first.id }
end
change_column :profiles, :division_id, :integer, :null => false
end
end
Rails 3
class AddDivisionIdToProfile < ActiveRecord::Migration
class Profile < ActiveRecord::Base
end
def change
add_column :profiles, :division_id, :integer
Profile.reset_column_information
Profile.all.each do |profile|
profile.update_attributes!(:division_id => Division.first.id)
end
change_column :profiles, :division_id, :integer, :null => false
end
end
You can add a column with a default value:
ALTER TABLE table1 ADD COLUMN userId INTEGER NOT NULL DEFAULT 1
The following migration worked for me in Rails 6:
class AddDivisionToProfile < ActiveRecord::Migration[6.0]
def change
add_reference :profiles, :division, foreign_key: true
change_column_null :profiles, :division_id, false
end
end
Note :division in the first line and :division_id in the second
API Doc for change_column_null
Not to forget that there is also something positive in requiring the default value with ALTER TABLE ADD COLUMN NOT NULL, at least when adding a column into a table with existing data. As documented in https://www.sqlite.org/lang_altertable.html#altertabaddcol:
The ALTER TABLE command works by modifying the SQL text of the schema
stored in the sqlite_schema table. No changes are made to table
content for renames or column addition. Because of this, the execution
time of such ALTER TABLE commands is independent of the amount of data
in the table. They run as quickly on a table with 10 million rows as
on a table with 1 row.
The file format itself has support for this https://www.sqlite.org/fileformat.html
A record might have fewer values than the number of columns in the
corresponding table. This can happen, for example, after an ALTER
TABLE ... ADD COLUMN SQL statement has increased the number of columns
in the table schema without modifying preexisting rows in the table.
Missing values at the end of the record are filled in using the
default value for the corresponding columns defined in the table
schema.
With this trick it is possible to add a new column by updating just the schema, operation that took 387 milliseconds with a test table having 6.7 million rows. The existing records in the data area are not touched at all and the time saving is huge. The missing values for the added column come on-the-fly from the schema and the default value is NULL if not otherwise stated. If the new column is NOT NULL then the default value must be set to something else.
I do not know why there is not a special path for ALTER TABLE ADD COLUMN NOT NULL when the table is empty. A good workaround is perhaps to create the table right from the beginning.

Resources