I'm writing a migration that involves a foreign key. Looking at my colleagues code, I see that he has added the line: t.reference :tablename, index: true
The t.reference part makes sense, but I don't know what index: true means. Can anyone tell me? I haven't been able to find that in the docs.
Note: This is not a duplicate of: Rails ActiveRecord::Migration what is the difference between index: true and add_index? Which only diffs the two, but doesn't explain what they do.
index: true adds a database index to the referenced column. For example, if creating a :products table:
create_table :products do |t|
t.references :user, index: true
end
That will create a non-unique index on the user_id column in the products table named index_products_on_user_id.
Related
What is the difference between "add_foreign_key" and "add_reference" methods in rails?
According to rails official guide all I understand is that they both are used to create foreign key constraint between two tables.
add_foreign_key - adds a new foreign key. from_table is the table with the key column, to_table contains the referenced primary key.
add_reference - is meant as a shortcut for creating a column, index and foreign key at the same time.
What is foreign key - a foreign key is a field or group of fields in a table that uniquely identifies a row in another table.
(Note: This answer is based on Rails 6.0.)
In a word, add_reference (Ref) is kind of a short-form of a combined set of add_column, add_index, and add_foreign_key (Ref) without adding a DB-level foreign key in default. So, when you want to achieve something simple enough or (conversely?) a polymorphic reference, add_reference is handy. If not, use add_foreign_key, maybe combined with explicit add_index.
As a simple example, these two are (I think) equivalent to each other:
add_reference :articles, :author, foreign_key: true
add_column :articles, :author_id, :bigint, null: true
add_foreign_key :articles, :authors
add_index :articles, :author_id
Here are more detailed differences:
The second argument of add_reference is a reference (column name without _id, hence usually singular), whereas that of add_foreign_key is a table name (hence usually plural).
In add_reference,
DB-level foreign key is not created in default, unless foreign_key option is specified non-nil.
index: true is default, whereas the index is irrelevant in add_foreign_key
null: true is default (allowing nulls for the column), which is irrelevant in add_foreign_key
polymorphic: true is available only with add_reference in Rails (which will create 2 columns in one action; see Ref).
The formats of the accepted options between the two are totally different, though add_reference is largely more inclusive, accepting a wider range of options.
Two example realistic use-cases
For the has_one association, where null is forbidden:
add_reference :products, :merchant, null: false, index: {unique: true}, foreign_key: {on_delete: :cascade}
When a table has 2 foreign-key columns to an identical table:
add_foreign_key :products, :merchants, column: :seller_id
add_foreign_key :products, :merchants, column: :buyer_id
add_index :products, [:seller_id, :buyer_id], unique: true, name: 'index_my_name_shorter_than_64chars'
There is a limitation to add_reference compared to add_foreign_key.
As I am curious if there is a way to do the exact following thing with add_reference. Afaik the standard foreign_key to primary_key/reference_key mapping can not be diverged with add_reference.
Migration snippet
add_foreign_key :foos, :bars, column: :foo_key, primary_key: :foo_key, type: :string
add_index :foos
Usecase is when trying to map the foreign_key to the primary_key in a non Standard way. Let's say using a table with STI to hold multiple references
So I read this question, answer and the comments, but it doesn't answer my case, which is what to do when of the columns is a foreign key?
Here is my original migration to create the table in question:
class CreateTemplates < ActiveRecord::Migration[5.1]
def change
create_table :templates, id: :uuid do |t|
t.references :account, type: :uuid, foreign_key: true
t.string :name
t.text :info
t.string :title
t.timestamps
end
end
end
Since account_id is a foreign_key (and identifies the customer) it will appear in almost all (99%) of queries on this table.
Now it has been decided that name should be unique to account, so the model has been updated:
validates_uniqueness_of :name, scope: [:account]
So once I add the joint index:
add_index :templates, [:name, :account_id], unique: true
should I delete the index on account_id?
I ask because in SQLLite (see this), it seems the answer would be that I don't need the single index on account_id and to create my new index with account_id in the first position:
add_index :templates, [:account_id, :name], unique: true
I'm using postgres, so does the same idea apply?
You have to add extra index if it's not the first index.
So if you have this:
add_index :templates, [:name, :account_id], unique: true
then you should not delete the original :account_id foreign key index, since it is second index.
I recommend you to read about index implementations. It's pretty interesting and you can learn a lot from it.
Here is a migration to create a table:
class CreateTemplates < ActiveRecord::Migration[5.1]
def change
create_table :templates, id: :uuid do |t|
t.references :account, type: :uuid, foreign_key: true
t.string :name
t.text :info
t.string :title
t.timestamps
end
end
end
Since account_id is a foreign_key (and identifies the customer) it will appear in almost all (99%) of queries on this table - there is not much point in retrieving a template that belongs to another customer.
So should I drop the index the above migration created for the account_id foreign_key and create this one instead?
add_index :templates, [:id, :account_id], unique: false
Or should I keep the original and also add this?
EDIT
To clarify the 99% use case - I think I was mistaken there. When creating a template, the account_id is always inserted so that the index method of the tempaltes_controller will always return all templates using the account_id, so that a user only sees a list of templates belonging to their account. For edits, updates, deletes, those actions only need the template_id. So my 99% guess is wrong! Most queries won't actually need a composite key it seems to me.
If most of your queries are going to filter on a combination of [:id, :account_id](which is unlikely) then creating a composite index will improve the performance of your queries.
However, it sounds like that most of your queries will only require :account_id, If that is the case then you do not need to add a composite index.
Its not a problem but more like an annoying step which i have to repeat every time i made a mistake. I created a migration which like this:
class CreateSubmissions < ActiveRecord::Migration
def change
create_table :submissions do |t|
t.references :student, index: true, foreign_key: true
t.references :assignment, index: true, foreign_key: true
t.references :question, index: true, foreign_key: true
t.text :code
t.timestamps null: false
end
add_index :submissions, [:student_id, :assignment_id, :question_id], unique: true
end
end
try to rub rake db:migrate and it give me error that index name is too long and more, i change that line to this
add_index :submissions, [:student_id, :assignment_id, :question_id],
unique: true, name: 'index_submissions'
and again try to execute rake db:migrate at that time it give me error that submission_student_index already exists (because the first time it created the table but throw exception while adding index) i tried to rollback so that i can execute whole submissions table and index together but unfortunately it rollback my previous migration (in some cases a previous table or index or column).
For that i have to manually go to the database and drop that submissions table and then execute rake db:migrate to create submissions table along with the all the index and also that previous migration. Its annoying to go back to the database and manually drop the table, i don't know if i am doing something wrong or there is some better way to this.
What I need is a migration to apply unique constraint to a combination of columns. i.e. for a people table, a combination of first_name, last_Name and Dob should be unique.
add_index :people, [:firstname, :lastname, :dob], unique: true
According to howmanyofme.com, "There are 46,427 people named John Smith" in the United States alone. That's about 127 years of days. As this is well over the average lifespan of a human being, this means that a DOB clash is mathematically certain.
All I'm saying is that that particular combination of unique fields could lead to extreme user/customer frustration in future.
Consider something that's actually unique, like a national identification number, if appropriate.
(I realise I'm very late to the party with this one, but it could help future readers.)
You may want to add a constraint without an index. This will depend on what database you're using. Below is sample migration code for Postgres. (tracking_number, carrier) is a list of the columns you want to use for the constraint.
class AddUniqeConstraintToShipments < ActiveRecord::Migration
def up
execute <<-SQL
alter table shipments
add constraint shipment_tracking_number unique (tracking_number, carrier);
SQL
end
def down
execute <<-SQL
alter table shipments
drop constraint if exists shipment_tracking_number;
SQL
end
end
There are different constraints you can add. Read the docs
For completeness sake, and to avoid confusion here are 3 ways of doing the same thing:
Adding a named unique constraint to a combination of columns in Rails 5.2+
Let's say we have Locations table that belongs to an advertiser and has column reference_code and you only want 1 reference code per advertiser. so you want to add a unique constraint to a combination of columns and name it.
Do:
rails g migration AddUniquenessConstraintToLocations
And make your migration look either something like this one liner:
class AddUniquenessConstraintToLocations < ActiveRecord::Migration[5.2]
def change
add_index :locations, [:reference_code, :advertiser_id], unique: true, name: 'uniq_reference_code_per_advertiser'
end
end
OR this block version.
class AddUniquenessConstraintToLocations < ActiveRecord::Migration[5.2]
def change
change_table :locations do |t|
t.index ['reference_code', 'advertiser_id'], name: 'uniq_reference_code_per_advertiser', unique: true
end
end
end
OR this raw SQL version
class AddUniquenessConstraintToLocations < ActiveRecord::Migration[5.2]
def change
execute <<-SQL
ALTER TABLE locations
ADD CONSTRAINT uniq_reference_code_per_advertiser UNIQUE (reference_code, advertiser_id);
SQL
end
end
Any of these will have the same result, check your schema.rb
Hi You may add unique index in your migration to the columns for example
add_index(:accounts, [:branch_id, :party_id], :unique => true)
or separate unique indexes for each column
In the typical example of a join table between users and posts:
create_table :users
create_table :posts
create_table :ownerships do |t|
t.belongs_to :user, foreign_key: true, null: false
t.belongs_to :post, foreign_key: true, null: false
end
add_index :ownerships, [:user_id, :post_id], unique: true
Trying to create two similar records will throw a database error (Postgres in my case):
ActiveRecord::RecordNotUnique: PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "index_ownerships_on_user_id_and_post_id"
DETAIL: Key (user_id, post_id)=(1, 1) already exists.
: INSERT INTO "ownerships" ("user_id", "post_id") VALUES ($1, $2) RETURNING "id"
e.g. doing that:
Ownership.create!(user_id: user_id, post_id: post_id)
Ownership.create!(user_id: user_id, post_id: post_id)
Fully runnable example: https://gist.github.com/Dorian/9d641ca78dad8eb64736173614d97ced
db/schema.rb generated: https://gist.github.com/Dorian/a8449287fa62b88463f48da986c1744a
If you are creating a new table just add unique: true
class CreatePosts < ActiveRecord::Migration[6.0]
def change
create_table :posts do |t|
t.string :title, unique: true
t.text :body
t.references :user, foreign_key: true
t.timestamps
end
add_index :posts, :user_id, unique: true
end
end