I created a table and added an index to it.
On a second migration I renamed the table.
Will the index keep on working?
Rails 3
No, you'll need to take care of the indexes yourself since the index is based on the table name. For example:
remove_index :old_table_name, :column_name
rename_table :old_table_name, :new_table_name
add_index :new_table_name, :column_name
Rails 4+
From the Rails 4 upgrade guide:
In Rails 4.0 when a column or a table is renamed the related indexes are also renamed. If you have migrations which rename the indexes, they are no longer needed.
Related
with what migration can I add a column to every model at once? This would be handy almost every time you forgot to add a field anyway because most of the time a field you want to add is not limited to one model only. Like "email" In this case I forgot to add an "order" field.
Thanks
You can do as follows -
def change
tables = [:users, :products, :addresses]
tables.each do |table_name|
add_column table_name, :order, :integer
end
end
It's called a group migration
Answering your first question "With what migration can I add a column to every model at once?". Answer: None. Rails migrations are a way to alter database schemas over time in a consistent way.
Rails migrations are Ruby classes using Rails methods as instructions to modify your database as needed. So your question could be better formulated as "How can I create a migration to add a column to every model at once?"
IMHO I don't think there's going to be a specific method to do this, as the requeriment is pretty custom, but, depending in your Rails version you can get all the ApplicationRecord.descendants:
Zeitwerk::Loader.eager_load_all
ApplicationRecord.descendants.map { |table| table.name.downcase.pluralize }.each do |table|
add_column table, :logdate, :datetime
end
Or all those tables from the database that can be safe_constantized:
ActiveRecord::Base.connection.tables.map do |table|
table.classify.safe_constantize
end.reject(&:nil?).each do |table|
add_column table, :logdate, :datetime
end
That way you get the name of each table/model and use it as the first argument for add_column.
The difference is that in Rails 6 the default code loader is Zeitwerk, so you can eager load all the project dependencies. In other versions you could do the same but using Rails.application.eager_load!.
The second version would work without having to load the models as dependencies as it makes a query asking for their tables and then maps them as constants.
I have a rails 4 app.
I have two tables, one for 'scope' and one for 'data'. Data belongs to scope. I forgot to add a foreign key when I set up data and I'm trying to write a migration to add one now.
I have created a change table, but the migration I've written isn't working.
I can't follow the rails guides example because it isn't consistent with the experience I'm having in my setup (not sure why).
The migration I have is:
class AddFKeyToData < ActiveRecord::Migration
def change
add_foreign_key :data, :scopes
end
end
Please can you help me identify the problem.
Thank you
Rollback this migration by:
rake db:rollback
Then go into your migration and edit add_foreign....
to:
add_column :data, :scope_id, :integer
Should work!
rails g migration CreateJoinTable zombie:index role:index
This creates this migration:
class CreateJoinTable < ActiveRecord::Migration
def change
create_join_table :zombies, :roles do |t|
t.index [:zombie_id, :role_id]
t.index [:role_id, :zombie_id] # I'd be happy if it didn't have this!
end
end
end
That migration is nearly there, but why do I have four indexes rather than two? Where in my generate command does it specify to create an extra two sets of indexes for indexes that already exist?
Try this instead:
rails g migration CreateJoinTableRolesZombies roles zombies
The migration comments out the indexes, presumably to show that the create_join_table handles this for you.
Note that in rails 4 the table names must be in sort order. Also, the migration name has been expanded in this example just to make it clear. CreateJoinTable appears in it, which is sufficient.
You've only two indexes, though it might index more than it should. See Index on multiple columns in RoR to explain the array syntax and how that changes t.index.
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
Is it possible to rename a column using a command like:
script/generate migration AddColumnToTable column:type
? Thanks.
Rails does have a migration command on the ActiveRecord ConnectionAdapter called rename_column. You can generate a migration and then write the code yourself. example (MySQL):
script/generate migration rename_my_column_by_hand
Then edit the file it creates:
class RenameMyColumnByHand < ActiveRecord::Migration
def self.up
rename_column :my_table, :old_name, :new_name
end
def self.down
rename_column :my_table, :new_name, :old_name
end
end
It executes SQL like:
ALTER TABLE my_table CHANGE old_name new_name BIGINT;
Note This only renames the column, it won't rename any references you have to it on other tables.
Great question. The answer is, unfortunately, no. See Rails 2.3.5 source code:
lib/rails_generator/generators/components/migration/migration_generator.rb
The only keywords that are recognized by the migration generator are add, remove, and to/from.
I use a bit of trickery here. Say I want to change column foo to bar.
Create a migration with the following steps
Add a temporary column temp_foo
Update all records, saving foo's value in temp_foo
Add a bar column
Update all records, saving temp_foo's value in bar
Drop column foo
Drop column temp_foo
This is ex-tre-me-ly brittle. If one step fails, you might loose data..