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.
Related
In Rails guide in here, it is wrote:
There is also a generator which will produce join tables if JoinTable is part of the name:
rails g migration CreateJoinTableCustomerProduct customer product
it generates:
class CreateJoinTableCustomerProduct < ActiveRecord::Migration[5.1]
def change
create_join_table :customers, :products do |t|
# t.index [:customer_id, :product_id]
# t.index [:product_id, :customer_id]
end
end
end
That simply creates customers_products table, both words are plural.
When I create a model CustomerProduct:
class CustomerProduct < ApplicationRecord
end
It always search for customer_products not customers_products
ActiveRecord::StatementInvalid: Mysql2::Error: Table 'customer_products' doesn't exist
Is there any way to make the model refer to the correct table without explicitly specifying table_name or naming it CustomersProduct :) ?
It is good to mention that I am looking to use it with :has_many :through and not :has_and_belongs_to_many
Also, Why in the generated migration, there are two lines of adding indices with different order ?
# t.index [:customer_id, :product_id]
# t.index [:product_id, :customer_id]
I believe you should name your "join model" and join table due to basic Rails naming conventions. The way I personally like is to name join table with its own word (not two other tables' words). Like not customers_products but something like selected_product or even customer_product will do the things. In these cases your model will search the table (selected_products or customer_products) correctly.
About indexes. For composite indexes the order of the columns matters. Basically if you have t.index [:customer_id, :product_id]:
CustomerProduct.where(customer_id: 5, product_id: 6) will use your index
CustomerProduct.where(customer_id: 5) will use your index, but
CustomerProduct.where(product_id: 5) won't use the index even though the index has product_id in it
So if your gonna query your table by both columns in all the combinations you need both of these composite indexes. You may see it by adding the indexes and trying EXPLAIN ANALYZE to learn how your queries would use the indexes.
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!
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.
In Rails 3,
I created a table with a migration, then added a column with a migration which creates a has_many, belongs_to relationship....
I then ran rake db:migrate
I'd like to now add an Index because I forgot to add it before I can migrate. can I add that to one of the existing migration files (the create table one) or do I need to create a new migration for adding an index to an existing table?
Thanks
I usually create a new migration if I forget something like that (especially if I have already migrated in production). But if you are still in development, then you can alter your last migration file and use the redo rake command:
rake db:migrate:redo
There is also:
rake db:rollback # Rolls the schema back to the previous version (specify steps w/ STEP=n).
Run this command to view all the different rake tasks:
rake -T db
Here is the section in the Rails Guide that talks about it:
http://guides.rubyonrails.org/migrations.html#rolling-back
If you would like to add your index without losing the data, you must create a new migration to add an index. Assuming your model is called Widget and the foreign model is called Zidget;
rails generate migration AddIndexToWidgets
and inside your new migration file at db/migrate/xxxxxxxxxx_add_index_to_widgets
class AddIndexToWidgets < ActiveRecord::Migration
def self.up
change_table :widgets do |t|
t.index :zidget_id # add ':unique => true' option if necessary
end
end
def self.down
change_table :widgets do |t|
t.remove_index :zidget_id
end
end
end
and then rake db:migrate as usual and voilà, you have your indexed column.
Update: If adding an index is all you're doing, there is a more concise way to write the same thing. There is no difference regarding the results. It's just that the former syntax is meant to DRY your code if you have more than one change for your table.
class AddIndexToWidgets < ActiveRecord::Migration
def self.up
add_index :widgets, :zidget_id # add ':unique => true' option if necessary
end
def self.down
remove_index :widgets, :column => :zidget_id
end
end
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.