add_foreign_key vs add_reference in rails - ruby-on-rails

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

Related

Rails 5.1 & Postgres - adding unique constraint on two columns, when one is a foreign key

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.

Difference between add_references and add_column in rails

add_reference :books, :author
add_column :books, :author_id, :integer
Here add references will create user_id column and add column is also creating user_id column in books table. What is the difference between them. What is the advantage of using references instead of column?
TLDR
#add_column is meant for adding a column like the name suggests.
#add_reference is meant as a shortcut for creating a column, index and foreign key at the same time.
Explanation
In your example the only difference is the index on the column that will be created by #add_reference that defaults to true.
add_reference :books, :author
# equals
add_column :books, :author_id, :integer
add_index :books, :author_id
But if you would take the following line:
add_reference :books, :author, foreign_key: true
It would also create a foreign key constraint.
Furthermore if you would like to have every author be able to publish only one book you can set the unique constraint through #add_reference by doing the following:
add_reference :books, :author, null: false, index: {unique: true}, foreign_key: true
This requires every book to have an author and restraints the authors to have a maximum of one book.
The same can be done using #add_column by doing the following:
add_column :books, :author_id, :integer, null: false
add_index :books, :author_id, unique: true
add_foreign_key :books, :authors
Both will generate the same columns when you run the migration.
The first command adds a belongs_to :author relationship in your Book
model whereas the second does not. When this relationship is
specified, ActiveRecord will assume that the foreign key is kept in
the author_id column and it will use a model named Author to
instantiate the specific author.
The first command also adds an index on the new author_id column.

Rails Active Records : foreign_key vs references

This is my code
class CreatePosts < ActiveRecord::Migration[5.1]
def change
create_table :posts, id: :uuid do |t|
t.string :name
t.references :user, type: :uuid
t.references :user, type: :uuid, foreign_key: true
t.timestamps
end
end
end
I am confused what is the difference b/w these line.Both lines of code are working fine to accomplish reference between table .
t.references :user, type: :uuid
t.references :user, type: :uuid, foreign_key: true #what this line is doing
Can anybody explain me when to use foreign_key or not .
I find the similar things while searching these
t.references :makers, foreign_key: { to_table: :office }
In above code foreign_key is not true. It referencing to some table. why is it so.
You can check the document of references here, it uses same options with add_reference.
So, the different is:
t.references :user, type: :uuid - Add a column without adding constraint.
t.references :user, type: :uuid, foreign_key: true - Add a column and foreign key constraint. If you don't specify foreign_key, so it will be false.
foreign_key: { to_table: :table_name } - It's option to add a column with a custom name instead of convention name.
For example, in document:
add_reference(:products, :supplier, foreign_key: {to_table: :firms})
so, it will add a column name supplier_id to table products and add a foreign key to reference to firms table.
If you follow convention name, then you will want to add a column named firm_id instead of supplier_id.
foreign_key: true will create a foreign key constraint, and without it ONLY the foreign key will be created.
to understand the difference between foreign key and foreign key constraint please visit this link.
specifying foreign_key: { to_table: :office } will make the foreign key reference the office table.

What is belongs_to creates in rails migrations and in the database structure?

I'm confused about belongs_to and foreign key in rails. When we use belongs_to in rails migrations, it seems like it creates a foreign key on the child table that we can access from the parent table. However, in the rails documentation, there is a situation that uses both in one column.
create_table :accounts do |t|
t.belongs_to :supplier, index: { unique: true }, foreign_key: true
# ...
end
Can somebody explain this situation and explain the what belongs_to and foreign_key: true exactly does?
t.belongs_to :supplier adds supplier_id to accounts.
index: { unique: true } creates a database index for the column.
foreign_key: true creates a foreign key constraint for the column.
I recommend you to read Active Record Migrations — Ruby on Rails Guides.
Indexes speed up data retrieval operations.
Foreign keys help to maintain referential integrity.

A migration to add unique constraint to a combination of columns

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

Resources