Alter Schema in Rails 2 - ruby-on-rails

I need to add some columns to a table in my schema. Can someone tell me the best way to do this?
The following seems incomplete or wrong since the schema.rb file did not update to include the new column and all of the corresponding view files (edit,index,new,show) did not update to include the new column. Not to mention the bloat of all of those migration classes that get generated. Thanks
ruby script/generate migration RecordLabelToAlbums record_label:string
exists db/migrate
create db/migrate/20121130125859_record_label_to_albums.rb
Creates this:
class RecordLabelToAlbums < ActiveRecord::Migration
def self.up
end
def self.down
end
end
I then added this:
class RecordLabelToAlbums < ActiveRecord::Migration
def self.up
add_column :albums, :record_label, :text
end
def self.down
remove_column :albums, :record_label
end
end
The I ran:
rake db:migrate
Got This:
Mysql::Error: Table 'albums' already exists: CREATE TABLE albums (id int(11) DEFAULT NULL auto_increment PRIMARY KEY, created_at datetime, updated_at datetime)

The code you added is correct.
The error suggests that for some reason your system appears to think it has not yet run the original migration that created the albums table. The state of migrations (in Rails 2) is specified in a table in the database called schema_migrations -- if this gets confused then it will try to re-run migrations. I am not sure what might cause it to get confused, but I do recall this happened a couple times back in 2008 when I was using Rails 2.x.
The table is simple -- you can see what's in it from a SQL prompt -- just the names of migrations it thinks it has run, I think.
If you don't mind losing some data, you can try rake db:rollback or even rake db:reset to get back to the beginning. rake db:rollback STEP=2 will rollback the last 2 migrations.
If you need the data, correct the contents of the table by adding one or more new records referencing the migrations in app/db/migrations that may have been missed. The order is important, I think (the format changed a little in Rails 3, I don't recall how).
Any time you want to add or change the database schema, use rails to generate a migration, and then run rake db:migrate once it's ready to go.
And just asking: is there any way you can move to Rails 3. It's been out for years now, and Rails 4 is coming soon. You'll find yourself in a backwater of incompatibilities, deprecations, security and performance issues and so on if you don't take the hit and upgrade.

Related

Rails: How to modify data using migrations due to change in the schema

I have following two migrations:
One, Add column contextual_page_number to transcripts table:
class AddContextualPageNumberToTranscripts < ActiveRecord::Migration[5.2]
def change
add_column :transcripts, :contextual_page_number, :integer, default: 1
end
end
Second, changing the value of the previous added column contextual_page_number based on value of another column:
class ChangePageOffsetAndContextualPageNumberOfTranscripts < ActiveRecord::Migration[5.2]
def up
Firm.all.find_in_batches do |group|
group.each do |firm|
Apartment::Tenant.switch(firm.tenant) do
Transcript.where.not(page_offset: 0).each do |transcript|
transcript.update(
contextual_page_number: ((transcript.page_offset - 1) * -1),
page_offset: 1
)
end
end
end
end
end
def down
..
end
end
After running the migration, I am getting unknown attribute contextual_page_number error.
== 20211108132509 AddContextualPageNumberToTranscripts: migrating =============
-- add_column(:transcripts, :contextual_page_number, :integer, {:default=>1}) -> 0.0095s
== 20211108132509 AddContextualPageNumberToTranscripts: migrated (0.0096s) ====
== 20220113095658 ChangePageOffsetAndContextualPageNumberOfTranscripts: migrating rails
aborted! StandardError: An error has occurred, this and all later
migrations canceled:
unknown attribute 'contextual_page_number' for Transcript.
I have even tried reset_column_information, but no luck:
Apartment::Tenant.switch(firm.tenant) do
Transcript.connection.schema_cache.clear!
Transcript.reset_column_information
..
end
Any clue would be of great help, thanks.
As mentioned in one of the answer, I tried reset_column_information just right after the add_column, but that didn't worked. Finally, SQL to the rescue..
sql_cmd = "UPDATE transcripts
SET contextual_page_number = ((page_offset - 1) * -1),
page_offset = 1
WHERE page_offset != 0"
Transcript.connection.execute(sql_cmd)
You need two migration files.
First, try running the migration and check schema.rb for the table transcripts and verify that the newly added column contextual_page_number is being added or not.
Once you are sure that your new column is added, then again create a new migration like, eg: MigrateTransriptsCloningsData, and then add the desired changes in the up block, then execute db:migrate to update the required changes.
My choice would be To add a new rake task and executing it. like bundle exec rake migrate_transcripts_data:start instead of keeping that logic in the db/migrate/your_new_migration_file, choice is yours.
reset_column_information should be the correct way to resolve this sort of problem if you want to use models in a migration. This isn't without its problems though.
I suspect the issue is that you are calling it too late somehow. Put it first thing in the up method of the second migration or after the add_column in the first migration.
I may assume that the issue is in Apartment.tenant_names.
In the second migration, you are switching tenants by Apartment::Tenant.switch(firm.tenant), but I do not see similar in the first migrations. Probably tenant names are in DB, not in configs.
I am pretty sure that you may find samples of the appropriate add_column in your previous migrations.
Do not use structure migrations to modify data.
Use rake tasks, or data-migrate gem instead.
Also, do not use automatic data migrations, if you not ensure, that it working as expected on production server.
Always store data before modifications and write modification rollback code.

Rails | Migration missing | Works locally but not on Heroku

This is my first Stackoverflow post, so please let me know if I can specify my problem any better than I am: I've searched the site and haven't found anything that fixed my problem.
I have an app running locally with an Event model.
The model has a start_date column and I later added an end_date with a migration.
Everything works locally.
When I deploy it to Heroku I get an 500-error when trying to create an Event entry.
It first gave me an ActiveRecord::MultiparameterAssignmentErrors, saying that it couldn't assign anything to end_date=.
I looked through everything and made sure that it was a permitted parameter.
Then I looked through my migrations and found to my surprise that my AddEndDateToEvents migration didn't exist, but the end_date column is in my schema model.
So I ran a rails g migration AddEndDateToEvents where I wrote:
class AddEndDateToEvents < ActiveRecord::Migration[5.2]
def change
remove_column :events, :end_date
add_column :events, :end_date, :date
end
end
I ran rails db:migrate and everything works locally.
Now when I push it to Heroke and I run db:migrate there as well, it of course terminates and tells me that it cannot remove the column end_date because it doesn't exist in the model.
I'm stuck. What shall I do? :(
References of my source code
GitHub: https://github.com/Curting/mydanceplan
Heroku: https://mydanceplan.herokuapp.com/
Thank you in advance!
Oliver
I solved it with your help, Mark! :-)
I did the following:
1) Roll back migrations
I ran rails db:migrate:status and saw that I should roll back two migrations to get completely clean of my attempts to add/remove the end_date column.
When I tried to rollback, it gave me an error that my remove column migrations weren't reversible. I had to enter the type of column. So I added that as a third parameter. Example: remove_column :events, :end_date, :date
After that a rollback was possible. The easiest way was to run rails db:rollback STEP=2.
2) Delete down migrations and check schema
After rolling back my migrations, I deleted the unneeded migration-files from the folder. Also, I checked my schema file and manually removed the end_date row from the model.
3) Create conditional remove row migration
Using Mark's answer, I added a new migration with the following code:
class AddEndDateToEvents < ActiveRecord::Migration[5.2]
def change
remove_column(:events, :end_date) if column_exists?(:events, :end_date)
add_column :events, :end_date, :date
end
end
4) Reset database and run db:migrate
I then did my git push && git push heroku and then I ran heroku restart && heroku pg:reset DATABASE --confirm APP-NAME && heroku run rake db:migrate, where APP-NAME was the name of my app.
And now it works! :-)
Thank you very much. I'm quite blown away by the quick and helpful response, and the fact that some person out there on the interwebs took the time to edit my post and format it nicely.
Generous community. Nice!
Looks like your migrations are out of sync as you say. The best way to cover both bases is to add a conditional into the migration. Rails comes with a method called column_exists?
https://apidock.com/rails/ActiveRecord/ConnectionAdapters/SchemaStatements/column_exists%3F
Which we can use in our migration:
class AddEndDateToEvents < ActiveRecord::Migration[5.2]
def change
remove_column(:events, :end_date) if column_exists?(:events, :end_date)
add_column :events, :end_date, :date
end
end
That should now work whether or not the initial migration has been run
EDIT: It probably is best to keep both your databases in sync, so if your Heroku DB has no important data in it, you can always simply drop it, recreate it and run all migrations from the start. I think that's preferable if you can afford to lose whatever data is in your Heroku DB

Fixing migration error

I tried to create a migration to add roles to my user tables but i accidentally typed AddRolesToUsers instead of AddRoleToUser. So i tried creating a new migration with the correct AddRoleToUsers but when i tried to run rake db:migrate i got an error :
SQLite3::SQLException: duplicate column name: role: ALTER TABLE "users" ADD "role" integer/Users/miguel/.rvm/gems/ruby-2.2.1/gems/sqlite3-1.3.11/lib/sqlite3/database.rb:91:in `initialize'
I tried rake db:migrate:down VERSION= do delete the one I had to type on but i keep getting the same error . PS: i deleted the migration file manually after running rake db:migrate:down VERSION=
rails g migration AddRoleToUsers role:integer
migration file :
class AddRoleToUsers < ActiveRecord::Migration
def change
add_column :users, :role, :integer
end
end
When you ran the first migration, the role column was added to the Users table. The error on the second migration tells you that much.
To clear the migration pending error, you need to comment out the add_column statement in the new migration.
i.e,
class AddRoleToUsers < ActiveRecord::Migration
def change
# add_column :users, :role, :integer
end
end
Then run the migration. This way, the new migration should run successfully.
You can now uncomment it and delete the previous migration, so that when you deploy, only the newer migration is run and the role column is added successfully.
In that case right click and delete both migration files and start again. The error exists because it thinks you want to add two columns both named role to your users table
UPDATE
In that case, if role already exists in your users table, a migration must've been successfully run and there is no need to run another. As long as the role column is there, trying to add another column called role will always give you an error. If you wanted to test that you are still able to add new columns you can always check by creating a test migration AddSomethingToUsers something:string and rake db:migrate to test, then rake db:rollback to undo..but it all seems like it's worked so I probably my wouldn't mess with it too much.

How do you skip failed migrations? (rake db:migrate)

I can't seem to find an option or anything that allows me to skip migrations.
I know what you're thinking: "you should never have to do that..."
I need to skip a migration that makes changes to specific user records that don't exist in my development database. I don't want to change the migration because it is not part of the source I am supposed to be working with. Is there a way to skip a migration or skip failed migrations?
Thanks in advance!
I think you should fix the offending migrations to be less fragile, I'd guess that a couple of if statements and perhaps a rescue would be sufficient.
But, if fixing the migrations really isn't an option, you can fake it in various ways. First of all, you could just comment out the migration methods, run rake db:migrate, and then uncomment (or revert) the offending migration.
You can also fake it inside the database but this sort of chicanery is not recommended unless you know what you're doing and you don't mind manually patching things up when you (inevitably) make a mistake. There is a table in your database called schema_migrations that has a single varchar(255) column called version; this table is used by db:migrate to keep track of which migrations have been applied. All you need to do is INSERT the appropriate version value and rake db:migrate will think that the migration has been done. Find the offending migration file:
db/migrate/99999999999999_XXXX.rb
then go into your database and say:
insert into schema_migrations (version) values ('99999999999999');
where 99999999999999 is, of course, the number from the migration's file name. Then running rake db:migrate should skip that migration.
I'd go with the second option before the third, I'm only including the "hack schema_versions" option for completeness.
I had an issue where I had a migration to add a table that already existed, so in my case I had to skip this migration as well, because I was getting the error
SQLite3::SQLException: table "posts" already exists: CREATE TABLE "posts"
I simply commented out the content of the create table method, ran the migration, and then uncommented it out. It's kind of a manual way to get around it, but it worked. See below:
class CreatePosts < ActiveRecord::Migration
def change
# create_table :posts do |t|
# t.string :title
# t.text :message
# t.string :attachment
# t.integer :user_id
# t.boolean :comment
# t.integer :phase_id
# t.timestamps
# end
end
end
This is a good way to do it for one-off errors.
db:migrate:up VERSION=my_version
This will run one specific migration's "up" actions. (There is also the opposite if you need it, just replace "up" with "down".) So this way you can either run the future migration that makes the older one (that you need to skip) work, or just run each migration ahead of it selectively.
I also believe that you can redo migrations this way:
rake db:migrate:redo VERSION=my_version
I have not tried that method personally, so YMMV.
If you have to do that, your app's migrations are messed up!
Inserts all missing migrations:
def insert(xxx)
ActiveRecord::Base.connection.execute("insert into schema_migrations (version) values (#{xxx})") rescue nil
end
files = Dir.glob("db/migrate/*")
files.collect { |f| f.split("/").last.split("_").first }.map { |n| insert(n) }
To skip all pending migrations, run this in your terminal:
echo "a = [" $(rails db:migrate:status | grep "down" | grep -o '[0-9]\{1,\}' | tr '\n' ', ') "];def insert(b);ActiveRecord::Base.connection.execute(\"insert into schema_migrations (version) values (#{b})\") rescue nil;end;a.map { |b| insert(b)}" | xclip
(For macOS use pbcopy instead of xclip)
Then CTRL-V the result inside rails console:
a = [ 20180927120600,20180927120700 ];def insert(b);ActiveRecord::Base.connection.execute("insert into schema_migrations (version) values (#{b})") rescue nil;end;a.map { |b| insert(b)}
And hit ENTER.
You can change the list of migrations you want to skip by removing them from array a before executing the line.
Instead of skip the migration you could make your migration smart, adding some IF to it, so you can check "specific users"
sometimes, it is necessary to re-fill schema_migrations table with definitely correct migrations...
ONLY FOR THIS PURPOSE i have created this method
def self.insert_missing_migrations(stop_migration=nil)
files = Dir.glob("db/migrate/*")
timestamps = files.collect{|f| f.split("/").last.split("_").first}
only_n_first_migrations = timestamps.split(stop_migration).first
only_n_first_migrations.each do |version|
sql = "insert into `schema_migrations` (`version`) values (#{version})"
ActiveRecord::Base.connection.execute(sql) rescue nil
end
end
you can copy-paste it into any model you want and use it from console
YourModel.insert_missing_migrations("xxxxxxxxxxxxxx")
(or somehow else)
where "xxxxxxxxxxxxxx" - is timestamp of migration before which you want to stop insertion (you can leave it empty)
!!! use it only if you absolutely understand what result will you get !!!

Temporary index name too long in Rails migration

I've got a problem trying to rollback one of my migration. It seems as if Rails is generating a temporary table for the migration, with temporary indices. My actual index on this table is less than 64 characters, but whenever Rails tries to create a temporary index for it, it turns into a name longer than 64 characters, and throws an error.
Here's my simple migration:
class AddColumnNameToPrices < ActiveRecord::Migration
def self.up
add_column :prices, :column_name, :decimal
end
def self.down
remove_column :prices, :column_name
end
end
Here's the error I'm getting:
== AddColumnNameToPrices: reverting ============================================
-- remove_column(:prices, :column_name)
rake aborted!
An error has occurred, this and all later migrations canceled:
Index name 'temp_index_altered_prices_on_column_and_other_column_and_third_column' on table 'altered_prices' is too long; the limit is 64 characters
I've changed the column names, but the example is still there. I can just make my change in a second migration, but that still means I can't rollback migrations on this table. I can rename the index in a new migration, but that still locks me out of this single migration.
Does anyone have ideas on how to get around this problem?
It looks like your database schema actually has index called prices_on_column_and_other_column_and_third_column. You have probably defined the index in your previous play with migrations. But than just removed index definition from migrations.
If it is true you have 2 options:
The simplier one (works if you code is not in production). You can
recreate database from scratch using migrations (not from
db/schema.rb) by calling rake db:drop db:create db:migrate. Make sure that you do not create this index with long name in other migration files. If you do, add :name => 'short_index_name' options to add_index call to make rails generate shorter name for the index.
If you experience this problem on a production database it is a bit more complicated. You might need to manually drop the index from the database console.
Had this problem today and fixed it by changing the migration to include the dropping and adding of the index causing the long name issue. This way the alteration is not tracked while I am altering the column type (that is where the really long name is caused)
I added the following:
class FixBadColumnTypeInNotifications < ActiveRecord::Migration
def change
# replace string ID with integer so it is Postgres friendly
remove_index :notifications, ["notifiable_id","notifiable_type"]
change_column :notifications, :notifiable_id, :integer
# shortened index name
add_index "notifications", ["notifiable_id","notifiable_type"], :name => "notifs_on_poly_id_and_type"
end
end

Resources