Annoying issue with Rails migration - ruby-on-rails

Its not a problem but more like an annoying step which i have to repeat every time i made a mistake. I created a migration which like this:
class CreateSubmissions < ActiveRecord::Migration
def change
create_table :submissions do |t|
t.references :student, index: true, foreign_key: true
t.references :assignment, index: true, foreign_key: true
t.references :question, index: true, foreign_key: true
t.text :code
t.timestamps null: false
end
add_index :submissions, [:student_id, :assignment_id, :question_id], unique: true
end
end
try to rub rake db:migrate and it give me error that index name is too long and more, i change that line to this
add_index :submissions, [:student_id, :assignment_id, :question_id],
unique: true, name: 'index_submissions'
and again try to execute rake db:migrate at that time it give me error that submission_student_index already exists (because the first time it created the table but throw exception while adding index) i tried to rollback so that i can execute whole submissions table and index together but unfortunately it rollback my previous migration (in some cases a previous table or index or column).
For that i have to manually go to the database and drop that submissions table and then execute rake db:migrate to create submissions table along with the all the index and also that previous migration. Its annoying to go back to the database and manually drop the table, i don't know if i am doing something wrong or there is some better way to this.

Related

Ruby on Rails creating migration to reverse UUID primary keys

I'm currently in the process of writing code to convert our databases default ID columns to a UUID format.
One example of the code that I have for our migration is
def up
remove_reference :examples, :user, null: false, foreign_key: true
add_column :users, :uuid, :uuid, default: "gen_random_uuid()", null: false
change_table :users do |t|
t.remove :id, type: :id
t.rename :uuid, :id
end
execute "ALTER TABLE users ADD PRIMARY KEY (id);"
add_reference :examples, :user, null: false, foreign_key: true, type: :uuid
end
Essentially this allowed me to convert my ID column to a UUID format.
I created a down function so I would be able to rollback but it fails due to this error ERROR: column "user_id" of relation "examples" contains null values
I realize that there would be an issue because once there is data in the database it would be unable to rollback and create the correct references again. Does anyone have any ideas on how I should work on my down function?
def down
remove_reference :examples, :user, null: false, foreign_key: true, type: :uuid
execute 'ALTER TABLE users DROP CONSTRAINT users_pkey'
add_column :users, :new_id, :primary_key
change_table :users do |t|
t.remove :id, type: :uuid
t.rename :new_id, :id
end
add_reference :examples, :user, null: false, foreign_key: true
end
Does anyone have any suggestions on how I should proceed with this? The original migration was in one change function, but it would be unable to rollback due to the execute block.
Be careful doing this kind of database change in one-shot. I would suggest you to break into steps.
First step (new column uuid)
Create the new column for the uuid
def up
add_column :users, :uuid, :uuid, default: "gen_random_uuid()", null: false
add_column :examples, :user_uuid, :uuid
end
Adapt your code to populate the examples.user_uuid column with the recent created column; You can easily achieve this by creating a model callback, feeling the user_uuid automatically.
If your database has a GB of data, consider adding the uuid as nullable, and populate the column using queues or in batches. The new records will be already filled.
We have now two new columns, with new data comming and all synced
Second step (the renaming)
Once populated and working with new columns, is time to rename the columns and associate the new keys.
def up
change_table :examples do |t|
t.rename :user_id, type: :old_user_id
t.rename :user_uuid, :user_id
end
change_table :users do |t|
t.rename :id, type: :old_id
t.rename :uuid, :id
end
execute "ALTER TABLE users ADD PRIMARY KEY (id);"
add_reference :examples, :user, null: false, foreign_key: true, type: :uuid
end
def down
# ...
end
Rember to remove, or review the model code changed before, to support this new columns or ignore.
Now we have changed the columns, without loosing the old reference.
Be careful here, if your database is big, you may lock your operation. Perhaps you may need a maintenance window here.
Third step (removing the old columns)
Now we can remove the old columns and everything should work fine
note: Always be careful when making this kind of change into your database. It is very risk to perform something like this. If you want to go on, simulate several times the step of renaming. Make snapshot of your database before performing and inform your clients that might have a downtime in your service.
I don't know why you want to change your primary keys to be an uuid, this costs a lot to the database to query and join data. It's more complicated to compare an UUID than an integer. Consider just create a new indexed column uuid into your tables and let the database to join and based on this field.

ActiveModel::UnknownAttributeError in rails 5

I'm trying to make a reddit-like website, so I've created a Forum model which should contain posts, hence I also created a Post model which I want to be a Forum child. My code for this idea is:
forum.rb
class Forum < ApplicationRecord
has_many :posts
validates :name, presence: true,
length: { minimum: 2 }
end
post.rb
class Post < ApplicationRecord
validates :title, presence: true,
length: { minimum: 5 }
validates :text, presence: true,
length: { minimum: 5 }
belongs_to :forum
end
The corresponding migrations are:
create_forums.rb
class CreateForums < ActiveRecord::Migration[5.1]
def change
create_table :forums do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
create_posts.rb
class CreatePosts < ActiveRecord::Migration[5.1]
def change
create_table :posts do |t|
t.string :title
t.text :text
t.references :forum, index: true, foreign_key: true
t.timestamps
end
end
end
My problem arise when I try to create a new post given a certain forum. For example, if I run #forum.posts.create(title: "Title", text: "Body") I get ActiveModel::UnknownAttributeError in Posts#new with the description unknown attribute 'forum_id' for Post.
What is going on?
Have you run your migrations after generating them? ( rake db:migrate )
The only way I can get this error to occur in testing is to remove the forum reference/relationship field from the posts table and then try and create a post related to the forum. When the migration is run with the t.references :forum, index: true, foreign_key: true the command runs perfectly for me.
If you added the reference line after running the migration, you could reset your database by running rake db:drop db:create db:migrate and you should be good to go since you have it in the table creation migration file, but it is worth noting that if you want to add or remove columns or modify your database, you should be creating new migrations for this instead of running through the drop/create process.
Have you migrated your db? If no, then rails db:migrate
and then reset your db: rails db:setup
That should fix the issue.

Rails error: Index name '...' on table '...' already exists while running rails db:migrate

So I'm working with a colleague who added some additional migration files and per normal procedure once I pulled their version I ran rails db:migrate. I end up getting the following errors:
Index name 'index_authorizations_on_user_id' on table 'authorizations' already exists
ArgumentError: Index name 'index_authorizations_on_user_id' on table 'authorizations' already exists
So I went and checked the schema and the table is already present. So why is it failing? Typically in the past it only generates new table entries/updates when doing a migration so why isn't it just ignoring it?
I've tried doing a rollback and get:
This migration uses remove_columns, which is not automatically reversible.
I've tried doing bin/rails db:migrate RAILS_ENV=development and I get the same errors.
I've done a db:reset, db:drop, and it all comes back to an issue with pending migrations that I cannot get to run. What am I doing wrong?
They added the following migration: 20171024074328_create_authorizations.rb
class CreateAuthorizations < ActiveRecord::Migration[5.1]
def change
create_table :authorizations do |t|
t.string :provider
t.string :uid
t.references :user, foreign_key: true
t.timestamps
add_index :authorizations, :user_id
add_index :authorizations, [:provider, :uid], unique: true
end
end
end
This:
t.references :user, foreign_key: true
adds an index on authorizations.user_id for you. If you check the references documentation it will point you at add_reference and that says:
:index
Add an appropriate index. Defaults to true. [...]
So index: true is the default when you call t.references :user and that creates the same index that add_index :authorizations, :user_id creates.
So the only thing that I've found that "worked" was to actually delete the migration files then run rails db:migrate. Didn't catch on anything, no errors.
Not a fan of the fact that this worked.
Check if it's a table or a changed table, most times you need to drop the table or delete the column then run the migration again and it'll be good to go

Migration ERROR when adding unique constraint to model! "standard error"?

In my rails app, I want to add a unique constraint to my favourites model. (I'm remaking a basic twitter app, with users and tweets, and generated a third model for favourite). However when I try and add a unique constraint to my favourites model so that one user can favourite a tweet only once, then run the command
rake db:migrate, I get the following error:
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:
SQLite3::ConstraintException: UNIQUE constraint failed: favourites.user_id, favourites.tweet_id: CREATE UNIQUE INDEX "index_favourites_on_user_id_and_tweet_id" ON "favourites" ("user_id", "tweet_id")/Users/Tabish/.rvm/gems/ruby-2.2.0/gems/sqlite3-1.3.10/lib/sqlite3/statement.rb:108:in `step'
Here is how my migration file that I have created looks:
class AddUniqueConstraintToTweets < ActiveRecord::Migration
def change
add_index :favourites, [:user_id, :tweet_id], :unique => true
end
end
Also here is my favourites table migration file:
class CreateFavourites < ActiveRecord::Migration
def change
create_table :favourites do |t|
t.references :user, index: true
t.references :tweet, index: true
t.timestamps null: false
end
add_foreign_key :favourites, :users
add_foreign_key :favourites, :tweets
end
end
I am using Rails 4.2.0 and SQLite3
As #mu mentioned, this means that you can't apply this index, because in your current database state you have duplicated user_id, tweet_id pairs. So you should remove them before running a migration.
To find them, open console and fire this command, which will show you those duplicates:
Favourite.select('user_id, tweet_id').group(:user_id, :tweet_id).having('count(*) > 1')

ActiveRecord, what does "index: true" mean?

I'm writing a migration that involves a foreign key. Looking at my colleagues code, I see that he has added the line: t.reference :tablename, index: true
The t.reference part makes sense, but I don't know what index: true means. Can anyone tell me? I haven't been able to find that in the docs.
Note: This is not a duplicate of: Rails ActiveRecord::Migration what is the difference between index: true and add_index? Which only diffs the two, but doesn't explain what they do.
index: true adds a database index to the referenced column. For example, if creating a :products table:
create_table :products do |t|
t.references :user, index: true
end
That will create a non-unique index on the user_id column in the products table named index_products_on_user_id.

Resources