Keeping test and development databases in sync with rake - ruby-on-rails

Using Rails 4.2, I want to add an age field to my User ActiveRecord class in a Postgres database using this line in a migration:
add_column :users, :age, :integer
then I obviously ran bin/rake db:migrate. Later I decided to make this a string field so rolled the migration back and changed the migration like so:
add_column :users, :age, :string
and migrated again. All was fine in my development database, but my test database seemed to have retained the old datatype. This is what I got using psql and running select column_name, data_type from information_schema.columns where table_name = 'users';
In development:
age | character varying
In test:
age | integer
Is this intentional or a bug?

First it's seldom a good idea to change a migration after it has already been applied.
But since you're already there, there are two possible ways to go about this: either rollback and migrate the test database or just load the schema for the test database. I recommend the second solution: just RAILS_ENV=test rake db:schema:load and you're good to go.

Aha! The issue is that the ActiveRecord feature below does not support rolling back of migrations:
ActiveRecord::Migration.maintain_test_schema!
and is detailed in this issue https://github.com/rails/rails/issues/15787, it seems this will be fixed in Rails 5.

When you run a db:migrate, if you don't specify environment, it'll only run the migrations for your development database. If you do a db:migrate RAILS_ENV=test, it'll run the migrations against your test database.

Related

adding a new column in a rails database migration

So I've been going through a lot of rails tutorial, and I get that the default for adding a new column to a database is , for example,
rails generate migration add_reset_to_users reset_digest:string reset_sent_at:datetime
The above will add a reset_digest in the form of a string and reset_sent_at in the form of a date to the migration add_reset_to_users
My questions is what if I am clumsy one night at 4 AM and only call the following
rails generate migration add_reset_to_users reset_digest:string
I completely forgot about reset_sent_at but want to implement it the next morning. I made the mistake of adding the link directly to the db file, which was a huge mistake.
In this case what should I do? Do I simply call a new migration such as
rails generate migration add_reset_sent_to_users reset_sent_at:datetime
or is there an even better way?
first, if you have not run your migration, you can directly open the migration file, and add your column to the file, as
def change
add columns :table_name :column_name :column_type
end
In your case, you will modify the file as,
def change
add columns :users :reset_digest :string
add columns :users :reset_sent_at :datetime
end
and then run
rake db:migrate
if you have already ran your migration, and you have not run any other migration after that, you can undo it, by
rake db:rollback STEP=1
and then edit the migration file, and run your migration
Rule of thumb for migrations in Rails is that you always create a new migration file unless you have not already shared your code with others, i.e. pushed to remote repository, otherwise you can just change the old migration after running $ rake db:rollback and everything will be fine, and nobody will know about it, and won't affect other developers work(since it's still on your local repository).
So, I'd encourage you to create a new migration if you have already committed and pushed the code onto remote repository, and changing the old migration file again will hurt other developers productivity. In case of any confusion, always create a new migration:
rails generate migration add_reset_sent_to_users reset_sent_at:datetime
I think it depends what state your rails app is in.
If you were working on a production app then editing migrations is not advisable due to data loss and changes should be made with a new migration.
If your working locally in development then I would edit the migration directly and add the missing column and rerun your migration.
Don't be worried about editing you migrations, just remember to rake db:rollback the migration you are editing before making changes or you will encounter errors.
This is where changing your migration from:
def change
add_column('users', 'reset_digest', :string)
add_column('users', 'reset_sent_at', :datetime) # Would have to perform rollback before adding this line
end
to:
def up
add_column('users', 'reset_digest', :string)
add_column('users', 'reset_sent_at', :datetime) # Added after migration
**rake db:migrate
end
def down
remove_column('users', 'reset_digest', :string)
remove_column('users', 'reset_sent_at', :datetime) # Add this after rollback
**rake db:rollback
end
Allows you to make changes to your migrations before you rake db:rollback
This requires a bit more code but I find it easier when I'm building a new app and things are changing frequently.

How to keep overview of the migrations?

I have a question regarding my migrations in rails.
Normally when i want to add a colum to a model for i dont make extra migrations but instead i perform this steps:
rake db:rollback
next i change the migration file in db/migrations and rerune:
rake db:migrate
The biggest problem is that when i do this i loose my data.
Previous i wrote migrations from the command line with for example
rake g migration Add_Column_House_to_Users house:string
The problem with this approach is that my db/migrations folder afterwards get very large and not very clear! I mean at the end i dont know wich variables the object has! Im not an expert in rails and would like to ask you how to keep the overview over the migrations!Thanks
Just a minor thought - I just use the file db/migrate/schema.rb to determine whats in the database as opposed to tracking through the migrations
You definitely shouldn't use db:rollback with a table with existing data.
I have a few production RonR apps with a ton of data and there are 100+ entries in the migrations table and adding new migrations to tweak tables is the rails way to do things. Not sure what you mean by lucid, but your schema and data model are going to change over time and that is ok and expected.
One tip. The migrations are great, but they are just the beginning, you can include complex logic as needed to fix your existing data (like so)
Changing data in existing table:
def up
add_column :rsvps, :column_name_id, :integer
update_data
end
def update_data
rsvps = Rsvp.where("other_column is not null")
rsvps.each do |rsvp|
invite = Blah.find(rsvp.example_id)
...
rsvp.save
end
end
Another tip: backup your production database often (should do this anyway), but use it to test all of your migrations before deploying. I run scripts like this all the time for local testing:
mysql -u root -ppassword
drop database mydatabase_dev;
create database mydatabase_dev;
use mydatabase_dev;
source /var/www/bak/mydatabase_backup_2013-10-04-16.28.06.sql
exit
rake db:migrate

Removing timestamp fields from the schema

I had used default generator to create some tables and they all had that t.timestamp in their definition so the schema that was created also has created_at and updated_at fields.
Now I am told that I don't need those two fields in my schema so I went to the original create_table* files and took out t.timestamp line from them and ran the db:migrate and schema:load commands
But still when I go to my schema.rb file I can see they are still there.
Is there anything wrong I am doing here?
Run
rails g migration remove_timestamps_from_table created_at updated_at
with table being your model's name. Since this is following the pattern remove x from y, rails is smart enough to generate the appropriate migration for you.
Then run
rake db:migrate
to update your development database and
rake db:test:prepare
to prepare the test database, and you're all set!
Read more on migrations here. If you are still having trouble, consider restarting your rails server or database server.

How does the migration work in Rails?

This is a newbie question. Is the migration (rake db:migrate) feature something that one would using during development or it's strictly a production tool for bringing the database up to date?
Ideally you want to use migrations only during development, and then load schema and seed the database in production. In reality, they'll allow you to make some changes and then deploy to production without any harm done.
Migrations allow you to work in iterations even on your database. You don't have to worry about forgetting to add something. When you start, just create the table as you think it's right, and you can fix it with another migration later. That's basically the idea. It takes away the one db script rules them all kind of thing.
A little example, if you have a User model with username and password and you need to add an email field, simply do this
rails generate migration AddEmailToUser # this is a convention, but you can name it however you want
class AddEmailToUser < ActiveRecord::Migration
def change
add_column :users, :email, :string
end
end
the change method will work both ways, when you apply the migration, and also when you need to revert it. It's kind of a neat Rails 3.1 magic.
The old version of migrations would look like this
class AddEmailToUser < ActiveRecord::Migration
def up
add_column :users, :email, :string
end
def down
remove_column :users, :email
end
end
Once you've added the migration, just run rake db:migrate and everything should work just fine. A big advantage of migrations is that if you manually mess up your database, you can easily just do
rake db:drop
rake db:create
rake db:migrate
or
rake db:migrate:reset # this might not work if you messed up your migrations
And you have the correct version of the database created
Migrations keep track of changes in your database schema. All changes with it (renaming column, changing tables, adding indexes etc.) should be done via migration. Thanks to that is really easy to deploy changes across multiple production servers.

How do I add a new attribute to a migration in Rails 3?

Here's my story: I had something like this set up a while ago.
create_table(:users) do |t|
t.database_authenticatable :null => false
t.recoverable
t.rememberable
t.trackable
t.integer :total_pageviews
and "total_pageviews" worked fine. I could set it to zero, increment it, multiply it, call it, whatever. but let's say i want to add:
t.integer :total_votes
What do I have to do to configure total_votes as an attribute? Whenever I try to use "total_votes", I get undefined method error. I'm thinking that the answer is something like rake db:migrate or similar.
You should not ever alter existing migrations. If you have checked them in to your version control system and somebody else runs them and then you add a new field to them and commit that change, they will not know to get that change. Then you must tell them and it's a pain in the butt to fix.
If you've not committed it yet then rollback the migration (rake db:rollback), make the necessary modification and re-run it again (rake db:migrate).
But in the Real World, you would create a new migration which adds this field using rails g migration add_total_votes_to_users total_votes:integer.
Rails will interpret this migration name and know what to do with it, generating a migration that contains this line:
add_column :users, :total_votes, :integer
Then when you run this migration it will add this total_votes field to the users table which will make an attribute of the same name for all User objects.
Are you adding this after the migration has been run? So, the users table exists and you want to add a column to it? If so, you need go create a migration like
rails generate migration add_total_votes_to_users
Open it up (just to verify that the table and column are correct). Then run the migration.
If you app is in production, then you should create a new migration. If you app is in pre-production stage, and you have a small development team with good communication, then I recommend you to go ahead and modify the migration and then do
rake db:drop
rake db:create
rake db:migrate
then you can commit the modified migration file and let the rest of you team know that that you have changed a migration file.

Resources