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.
Related
I am new to ROR, and now doing a course project related to books.
I have a "book" table with string 'isbn' as its primary key, and now trying to add another "comment" with reference to the book table with a foreign key 'isbn' to refer to the "book" table .
So my "models/comment.rb" looks like this:
class Comment < ApplicationRecord
belongs_to :book, references: :isbn, type: :string
end
And "models/book.rb" is:
class Book < ApplicationRecord
has_many :comments, dependent: :destroy
end
My "books" table looks like:
I would expect that the "comments" table generated will contain a column "isbn" (string) after db migration, but in fact the "comments" table generated contained a "book_id" (integer) instead.
How can I generate a foreign key to reference to the string primary key "isbn" in "book" table?
Start by setting up the foreign key column to be the right type and set the foreign key constraint to point to the right column:
class CreateComments < ActiveRecord::Migration[6.0]
def change
create_table :comments do |t|
t.string :title
t.string :note
t.references :book, type: :string, column_name: :isbn
t.timestamps
end
end
end
I would expect that the "comments" table generated will contain a
column "isbn" (string) after db migration, but in fact the "comments"
table generated contained a "book_id" (integer) instead.
Migrations are actually a lot simpler and dumber than you would think. Its just a DSL that creates SQL statements. Its not actually in any way aware of the other table so it has no way of knowing that books.isbn is a string column. It just uses the default type for add_reference which is :integer. Rails is driven by convention - not artificial intelligence.
If you want to call the column something else like book_isbn you have to do it in two steps instead:
class CreateComments < ActiveRecord::Migration[6.0]
def change
create_table :comments do |t|
t.string :title
t.string :note
t.string :book_isbn
t.timestamps
end
add_foreign_key :comments, :books,
column: "book_isbn", primary_key: "isbn"
end
end
Then setup the books model to use your non-conventional primary key:
class Book < ApplicationRecord
self.primary_key = :isbn
has_many :comments,
foreign_key: :book_isbn # default is book_id
end
On the comment side you need to configure the belongs_to association so that it points to books.isbn instead of books.id.
class Comment < ApplicationRecord
belongs_to :book, primary_key: :isbn
end
references is not a valid option for belongs_to.
ArgumentError (Unknown key: :references. Valid keys are: :class_name, :anonymous_class, :foreign_key, :validate, :autosave, :foreign_type, :dependent, :primary_key, :inverse_of, :required, :polymorphic, :touch, :counter_cache, :optional, :default)
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
(Rails is version 5.0.0, Ruby 2.3.0p0)
I want to create an association between my Users table and Cards table. I've added belongs_to :user to the Cards model, and has_many :cards to the Users model, and created a migration with:
class AddUserIdToCard < ActiveRecord::Migration[5.0]
def change
add_foreign_key :cards, :users, column: :user_id
end
end
When I run rake db:migrate, I receive the error:
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column "user_id" referenced in foreign key constraint does not exist
: ALTER TABLE "cards" ADD CONSTRAINT "fk_rails_8ef7749967"
FOREIGN KEY ("user_id")
REFERENCES "users" ("id")
Now I initially worked around this problem simply by adding add_column :cards, :user_id, :integer to the migration, but that doesn't really seem very tidy, and I'm worried about problems coming up later. Is there a better way to accomplish this?
You're setting a foreign key for cards table with the column user_id. But you haven't created a reference yet. Create a reference and then add foreign key to maintain referential integrity. Rollback and modify your migration with
1 class AddUserIdToCard < ActiveRecord::Migration[5.0]
2 def change
3 add_reference :cards, :users, index:true
4 add_foreign_key :cards, :users
5 end
6 end
Line 3 will create, in the cards table, a reference to id in the users table (by creating a user_id column in cards).
Line 4 will add a foreign key constraint to user_id at the database level.
For more, read Add a reference column migration in Rails 4
The answer provided is not accurate for Rails 5. Scroll to the add_reference bit of the docs for more, but in the case of the above question, you would use:
class AddUserIdToCard < ActiveRecord::Migration[5.0]
def change
add_reference :cards, :users, foreign_key: true
end
end
In rails 6, I believe this is now
def change
add_column :cards, :user_id, :integer, index: true
add_foreign_key :cards, :users
end
class AddUserIdToCard < ActiveRecord::Migration[5.2]
def change
add_foreign_key :cards, :users, column: :user_id, primary_key: :"id", on_delete: :cascade
end
end
Try this migration. I was facing the same problem then I fixed it.
I have the following two Models:
class Store < ActiveRecord::Base
belongs_to :person
end
class Person < ActiveRecord::Base
has_one :store
end
Here is the issue: I am trying to create a migration to create the foreign key within the people table. However, the column referring to the foreign key of Store is not named store_id as would be rails convention but is instead named foo_bar_store_id.
If I was following the rails convention I would do the migration like this:
class AddReferencesToPeople < ActiveRecord::Migration
def change
add_reference :people, :store, index: true
end
end
However this will not work because the column name is not store_id but is foo_bar_store_id. So how do I specify that the foreign key name is just different, but still maintain index: true to maintain fast performance?
in rails 5.x you can add a foreign key to a table with a different name like this:
class AddFooBarStoreToPeople < ActiveRecord::Migration[5.0]
def change
add_reference :people, :foo_bar_store, foreign_key: { to_table: :stores }
end
end
Inside a create_table block
t.references :feature, foreign_key: {to_table: :product_features}
In Rails 4.2, you can also set up the model or migration with a custom foreign key name. In your example, the migration would be:
class AddReferencesToPeople < ActiveRecord::Migration
def change
add_column :people, :foo_bar_store_id, :integer, index: true
add_foreign_key :people, :stores, column: :foo_bar_store_id
end
end
Here is an interesting blog post on this topic. Here is the semi-cryptic section in the Rails Guides. The blog post definitely helped me.
As for associations, explicitly state the foreign key or class name like this (I think your original associations were switched as the 'belongs_to' goes in the class with the foreign key):
class Store < ActiveRecord::Base
has_one :person, foreign_key: :foo_bar_store_id
end
class Person < ActiveRecord::Base
belongs_to :foo_bar_store, class_name: 'Store'
end
Note that the class_name item must be a string. The foreign_key item can be either a string or symbol. This essentially allows you to access the nifty ActiveRecord shortcuts with your semantically-named associations, like so:
person = Person.first
person.foo_bar_store
# returns the instance of store equal to person's foo_bar_store_id
See more about the association options in the documentation for belongs_to and has_one.
To expand on schpet's answer, this works in a create_table Rails 5 migration directive like so:
create_table :chapter do |t|
t.references :novel, foreign_key: {to_table: :books}
t.timestamps
end
EDIT: For those that see the tick and don't continue reading!
While this answer achieves the goal of having an unconventional foreign key column name, with indexing, it does not add a fk constraint to the database. See the other answers for more appropriate solutions using add_foreign_key and/or 'add_reference'.
Note: ALWAYS look at the other answers, the accepted one is not always the best!
Original answer:
In your AddReferencesToPeople migration you can manually add the field and index using:
add_column :people, :foo_bar_store_id, :integer
add_index :people, :foo_bar_store_id
And then let your model know the foreign key like so:
class Person < ActiveRecord::Base
has_one :store, foreign_key: 'foo_bar_store_id'
end
# Migration
change_table :people do |t|
t.references :foo_bar_store, references: :store #-> foo_bar_store_id
end
# Model
# app/models/person.rb
class Person < ActiveRecord::Base
has_one :foo_bar_store, class_name: "Store"
end
Under the covers add_reference is just delegating to add_column and add_index so you just need to take care of it yourself:
add_column :people, :foo_bar_store_id, :integer
add_index :people, :foo_bar_store_id
foreign key with different column name
add_reference(:products, :supplier, foreign_key: { to_table: :firms })
refer the documentation Docs
I have the following Rails migration which works perfectly (irrelevant pieces removed):
create_table :comments do |t|
t.text :body
t.references :post
end
Now I'd like to add an author column to my comments table (which is the userid of a user), but I have no idea how to do it (I'm tempted to just write the MySql-specific syntax using an execute).
I've been looking at add_column here which doesn't mention references. I've actually found TableDefinition#references but I have no idea how to use it with an add_column statement.
Is this possible? Also, is it true that, for MySql, the "references" functionality does not actually establish relationships between the tables?
While it's too late to get any points out of this, I thought I'd post the best way for posterity :)
use change_table instead of create_table to add columns to a table that already exists, with all the TableDefinition goodness:
self.up do
change_table :comments do |t|
t.references :author
end
end
This might seem trivial, but other gems like Devise make heavy use of their own custom table definitions, and this way you can still use them.
add_reference :table_name, :reference, index: true
Finally got it
add_column :locations, :state_id , :integer, :references => "states"
First, do:
script/generate migration AddAuthorIdToComments
Open the generated file and add this line:
add_column :comments, :author_id, :integer
Then in your model files:
class User < ActiveRecord::Base
has_many :comments, :foreign_key => "author_id"
end
class Comment
belongs_to :author, :class_name => User
end
It's been a while since I've looked at this, but last I checked migrations don't support creating foreign keys. Fortunately, however, there is a plug-in for it. I've used this and it works well.
You could add the column by add_column(:table, :column_name, :type, :options) in a new Migration.