Does change handle migration / rolling back of schema - ruby-on-rails

In the new preferred way, there is only 1 method which seems to add a column. Does that mean you don't need to have a method to remove columns?
# the old way
class AddNameToPerson < ActiveRecord::Migration
def up
add_column :persons, :name, :string
end
def down
remove_column :person, :name
end
end
# the new prefered way
class AddNameToPerson < ActiveRecord::Migration
def change
add_column :persons, :name, :string
end
end

That's the magic of Rails 3.1 and later. Rails knows how to migrate your database and reverse it when the migration is rolled back without the need to write a separate down method.
Rails keep track of migration. So it will have the implementation for reverse the changes added by the migration when you roll back. Earlier we use to have two methods up and down. On Rails 3.1 and later they changed migration to have change method.
In case of add_coulmn reverse method is remove_column and same for other methods. Using tracked detail it will call appropriate action. So you don't need two methods(up and down). You can see the methods supported by change method: here. If you have other then these method you need to use up and down.
If you are confused with using change method then I suggest you to use up and down. Once you thorough with migration you can start using change method.

You are right, You don't need a separate method to remove columns.
You can even try 'rake db:rollback' and then again 'rake db:migrate' if you don't have any data to lose.
It works perfectly. :)

Related

Is there any difference in using up/down vs change/reversible methods in Rails migrations?

In a Rails migration, does it make any difference, if I do this:
def up
foo
end
def down
bar
end
or this:
def change
reversible do |direction|
direction.up { foo }
direction.down { bar }
end
end
?
I think that it's better to use the change method if part of the migration includes reversible methods, such as create_table, add_column etc.. Other than that, is there any difference?
As you show it, there is no advantage. The main advantage is that a lot of the time you don't need to write the down method / block at all, eg
class SomeMigration < ActiveRecord::Migration
def change
create_table :articles do |t|
...
end
end
end
The reversible method is mostly used when there is a small part of a migration that activerecord doesn't know how to reverse (eg a raw SQL statement)
The change method works for many cases where the reversible logic can be derived easily and ActiveRecord migrations can easily handle it. However, in some cases you may need to do something specific inside of a migration to insure it will run without errors. There may also be something complex would be an irreversible migration.
See Rails: Is it bad to have an irreversible migration?
In such cases it is best to use the up down method but there is also away of checking if the current direction of the migration which is simply reverting?
Here's an example where you might use this in place of the 2 up/down methods. In this example, there is also a complex data method not show which caused a migration error without the conditional logic. I've ommited as it is not relevant to your question:
def change
add_column :languages, :iso_639_2t, :string
add_column :languages, :iso_639_2b, :string
add_column :languages, :iso_639_3, :string
add_index :languages, :iso_639_2t
unless reverting?
ActiveRecord::Base.connection
.execute("ALTER SEQUENCE languages_id_seq RESTART WITH #{Language.count+1}")
end
end
If complex data manipulation is used inside of a migration via migration_data gem for example, you might want to add a def check_data method and raise an exception if the data_check fails.
If the migration is really irreversible (destroys data), you may want to also use up/down for example:
def up
remove_column :signup, :date
end
def down
raise ActiveRecord::IrreversibleMigration, "Date field dropped in previous migration. Data unrecoverable"
end

How do I database level contraints to my rails application long after I've migrated everything?

1) I've created an entire Rails JSON API as my first project. It's fairly sizeable, and I put off database level constraints at the time. It's time to do it now.
I have 30+ or so migrations. For example
class CreateItems < ActiveRecord::Migration
def change
create_table :items do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
When I go in and edit
t.string :name
to be
t.string :name, null: false
and run a bundle exec db:reset db:migrate it doesn't work. What do I need to do to add db level constraints to my migrations files, and re migrate them?
2) Also, my 30+ migrations all have a "def change" and that's it. I believe somewhere a long time ago I saw something like def up, def down or something like that. It looks like it was used for rollbacks. Do I need to have "def down" for all of my migrations? Is that how I solve #1? Put "def down" in all of my 30 migrations, rollback the entire thing, then put in my db level constraints, and migrate the whole thing again?
You have two options: you can write new migrations to make changes to the existing columns. In console:
rails g migration add_null_to_name
In the migration file:
class ChangeNumbericFieldInMyTable < ActiveRecord::Migration
def self.up
change_column :my_table, :name, :string, :null => false
end
def self.down
change_column :my_table, :name, :string
end
Rinse and repeat for all the changes you need to make. Note that you can define them in the same migration file, but then if you had to rollback in the future it would roll back all of them.
Your second option is to roll everything back (or just start over), fix your migration methods and remigrate. This might be a cleaner approach, but that's not necessarily a good thing: it would also delete the existing data, if you have any.
I know I said you have two options, but you actually also have a third option, which is to to rely on model validations instead. These are more appropriate in most situations and can make testing much easier. Database validations are more reserved for complex scenarios where the database is used by other applications in addition to yours, and a few other edge cases.
As for your second question, every migration is a two-way street. When you migrate something, you should also be able to roll it back. In a lot of cases Rails can guess the reverse operation. For example, if you add_column in a migration, the opposite of that is obviously remove_column so you don't need to define it explicitly. But certain operations, such as change_column in the example above, don't have an obvious reverse. Rails can understand that something changed, but it can't tell what it changed from. So in those situations you need to explicitly define the down operation. In the case above, it's changing the column back to a state where it doesn't have a validation.

Adding a column through the terminal

How do you add a column to a table using ActiveRecord through the terminal. I am trying to use add_column method but its not working. Any ideas please?
It is better to write a migration and a must if you are working with a team. When you make db changes, then every developer's environment has to be updated also. Otherwise, you will have some mad developers at you.
rails generate migration AddPartNumberToProducts part_number:string
will generate
class AddPartNumberToProducts < ActiveRecord::Migration
def change
add_column :products, :part_number, :string
end
end
Then you run the migration
rake db:migrate
http://guides.rubyonrails.org/migrations.html
Edit:
For a rails console command line check #tadman's answer or use what Bengala proposed like
ActiveRecord::Migration.add_column :products, :part_number, :string
You can run migrations directly in rails console rails c with ActiveRecord::Migration
For your purpose the next command will do what you ask:
> ActiveRecord::Migration.add_column :table_name, :field_name, :field_type
If you're just hacking around, it's usually easier to manipulate the database using a SQLite client of some sorts than through the Rails DB layer.
If you're doing this for a project, create a proper migration file and run it.
If you're determined to do this, the add_column method is available through the ActiveRecord::Base.connection driver object.

Renaming table in rails

I want to rename a table... (any table.)
I tried this line of code:
ActiveRecord::ConnectionAdapters::SchemaStatements.rename_table(old_name, new_name)
Here's the weird thing. I know I got it working the first time, but now I get this error: undefined method `rename_table' for ActiveRecord::ConnectionAdapters::SchemaStatements:Module
Was there something I need to set?
Remember that in Rails >= 3.1 you can use the change method.
class RenameOldTableToNewTable < ActiveRecord::Migration
def change
rename_table :old_table_name, :new_table_name
end
end
You would typically do this sort of thing in a migration:
class RenameFoo < ActiveRecord::Migration
def self.up
rename_table :foo, :bar
end
def self.down
rename_table :bar, :foo
end
end
.rename_table is an instance method, not a class method, so calling Class.method isn't going to work. Instead you'll have to create an instance of the class, and call the method on the instance, like this: Class.new.method.
[EDIT]
In this instance, ActiveRecord::ConnectionAdapters::SchemaStatements isn't even a class (as pointed out by cam), which means that you can't even create an instance of it as per what I said above. And even if you used cam's example of class Foo; include ActiveRecord::ConnectionAdapters::SchemaStatements; def bar; rename_table; end; end;, it still wouldn't work as rename_table raises an exception.
On the other hand, ActiveRecord::ConnectionAdapters::MysqlAdapter is a class, and it is likely this class you'd have to use to rename your table (or SQLite or PostgreSQL, depending on what database you're using). Now, as it happens, ActiveRecord::ConnectionAdapters::MysqlAdapter is already accessible through Model.connection, so you should be completely able to do Model.connection.rename_table, using any model in your application.
[/EDIT]
However, if you wish to permanently rename a table, I would suggest using a migration to do it. It's easy and the preferred way of manipulating your database structure with Rails. Here's how to do it:
# Commandline
rails generate migration rename_my_table
# In db/migrate/[timestamp]_rename_my_table.rb:
class RenameMyTable < ActiveRecord::Migration
def self.up
rename_table :my_table, :my_new_table
end
def self.down
rename_table :my_new_table, :my_table
end
end
Then, you can run your migration with rake db:migrate (which calls the self.up method), and use rake db:rollback (which calls self.down) to undo the migration.
ActiveRecord::Migration.rename_table(:old_table_name, :new_table_name)

Migrating Data in a Rails Migration

Let's say you have "lineitems" and you used to define a "make" off of a line_item.
Eventually you realize that a make should probably be on its own model, so you create a Make model.
You then want to remove the make column off of the line_items table but for every line_item with a make you want to find_or_create_by(line_item.make).
How would I effectively do this in a rails migration? I'm pretty sure I can just run some simple find_or_create_by for each line_item but I'm worried about fallback support so I was just posting this here for any tips/advice/right direction.
Thanks!
I guess you should check that the Make.count is equal to the total unique makes in lineitems before removing the column, and raise an error if it does not. As migrations are transactional, if it blows up, the schema isn't changed and the migration isn't marked as executed. Therefore, you could do something like this:
class CreateMakesAndMigrateFromLineItems < ActiveRecord::Migration
def self.up
create_table :makes do |t|
t.string :name
…
t.timestamps
end
makes = LineItem.all.collect(:&make).uniq
makes.each { |make| Make.find_or_create_by_name make }
Make.count == makes.length ? remove_column(:line_items, :make) : raise "Boom!"
end
def self.down
# You'll want to put logic here to take you back to how things were before. Just in case!
drop_table :makes
add_column :line_items, :make
end
end
You can put regular ruby code in your migration, so you can create the new table, run some code across the old model moving the data into the new model, and then delete the columns from the original model. This is even reversible so your migration will still work in both directions.
So for your situation, create the Make table and add a make_id to the lineitem. Then for each line item, find_or_create with the make column on lineitem, setting the returned id to the new make_id on lineitem. When you are done remove the old make column from the lineitem table.

Resources