Associations
Basic Associations
So far, we have created the books
table. Now, we are going to create the author
table and try to associate these two tables.
Let's create the author
table first. How do we do that?
$ rails g model author
invoke active_record
create db/migrate/20161213095813_create_authors.rb
create app/models/author.rb
...
The most important files generated are the model file app/models/author.rb
and the migration file db/migrate/xxxx_create_authors.rb
.
In db/migrate/xxxx_create_authors.rb
,
class CreateAuthors < ActiveRecord::Migration[5.0]
def change
create_table :authors do |t|
t.string :name
t.string :location
t.string :birth_year
t.timestamps
end
end
end
What did we do here? Why do we say that
birth_year
should have string format? Why not integer?Here, we added 3 attributes:
name
,location
andbirth_year
.birth_year
should be string because we don't need to do any arithmetic operations on it.
To run the migration:
$ rails db:migrate
== 20161213095813 CreateAuthors: migrating ====================================
-- create_table(:authors)
-> 0.0072s
== 20161213095813 CreateAuthors: migrated (0.0073s) ===========================
To double check the migration status (again):
$ rails db:migrate:status
Status Migration ID Migration Name
--------------------------------------------------
up 20161212101030 Create books
up 20161213095813 Create authors
Now, both the book
table and the author
table have been created, but there's no association between them yet. Let's recall their association we've defined earlier on.
Here, we are going to associate each book with its author through the author_id
. Why do we want to connect these two tables? Normalization. Normalization is a systematic approach of decomposing tables to eliminate data redundancy. Instead of having duplicated and redundant data, we connect them in a way that makes sense.
To relate the book
and author
table through author_id
, we have to add the author_id
column to the book
table. This time, we can't use $ rails g model
because we are not creating a new model. We need to create a new migration to modify the book
table.
To generate a new migration file, you would usually run $ rails generate migration NameOfTheMigrationFile
.
There is a convention to naming our migration files. It is generalized into two types: 1) add columns to a table or 2) remove columns from a table. For adding, use the format "AddXXXToYYY" (i.e. AddAuthorIdToBooks). For removing, use the format "RemoveXXXFromYYY" (i.e. RemoveNameFromAuthors). Read more at http://guides.rubyonrails.org/active_record_migrations.html#creating-a-standalone-migration.
In our case, we can say
$ rails g migration AddAuthorIdToBooks
invoke active_record
create db/migrate/20161214031618_add_author_id_to_books.rb
In db/migrate/20161214031618_add_author_id_to_books.rb
,
class AddAuthorIdToBooks < ActiveRecord::Migration[5.0]
def change
end
end
And, we will add a new column / attribute author_id
,
class AddAuthorIdToBooks < ActiveRecord::Migration[5.0]
def change
# add_column :<table name in plural>, :<column name>, :<data type>
add_column :books, :author_id, :integer
end
end
Afterwards, we can run the migration,
$ rails db:migrate
== 20161214031618 AddAuthorIdToBooks: migrating ===============================
-- add_column(:books, :author_id, :integer)
-> 0.0102s
== 20161214031618 AddAuthorIdToBooks: migrated (0.0104s) ======================
Done, we've added the author_id
to the database schema. Speaking of database schema, is there a place where we can visually see the schema in Rails? Yes. Go to db/schema.rb
ActiveRecord::Schema.define(version: 20161214031618) do
create_table "authors", force: :cascade do |t|
t.string "name"
t.string "location"
t.string "birth_year"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "books", force: :cascade do |t|
t.string "name"
t.string "publishing_year"
t.integer "num_of_pages"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "author_id"
end
end
You can see the
author_id
is in thebooks
table.Remember. You shouldn't modify
schema.rb
directly. All changes should be done by migrations via migration files.
So, are we done associating books
table with authors
table? Not yet. We still have one last step. These database tables are interconnected with Rails models. So, we have to add the associations in the Rails models. The association / relationship is that an author can have many books and a book belongs to an author.
In app/models/book.rb
,
class Book < ApplicationRecord
validates :name, presence: true
belongs_to :author, required: false # this allows a record to be created even if the author attribute doesn't exist
end
In app/models/author.rb
,
class Author < ApplicationRecord
has_many :books
end
Great! Let's test it in Rails console ($ rails c
).
author = Author.create(name: "J.K. Rowling")
book = Book.create(name: "Harry Potter", author: author)
puts author.to_json
# => {"id":4,"name":"J.K. Rowling","location":null,"birth_year":null,"created_at":"2016-12-14T03:40:53.699Z","updated_at":"2016-12-14T03:40:53.699Z"}
puts book.to_json
# => {"id":11,"name":"Harry Potter","publishing_year":null,"num_of_pages":null,"created_at":"2016-12-14T03:40:53.707Z","updated_at":"2016-12-14T03:40:53.707Z","author_id":4}
puts author.id
# => 4
puts book.author_id
# => 4
puts book.author == author
# => true
There we go. Rails made it extremely easy to assign and retrieve association records. If you want to get the book's author? Just do book.author
. Since the author has an id
of 4, the author_id
stored inside the book would be 4.
Why Associations?
In Rails, an association is a connection between two Active Record models. By associating and interconnecting different models, we can query the database for complex relationships with simple commands. Essentially, we can talk to the database in an easy way and still get difficult things done. For example, a book has an author. The author owns five dogs. What's the birth_year of the first dog? By associating models, we can query this by simply saying book.author.dogs.first.birth_year
.
The types of associations you can make are:
- belongs_to
- has_one
- has_many
- has_many :through
- has_one :through
- has_and_belongs_to_many
The belongs_to Association
A belongs_to
association sets up a one-to-one connection with another model, such that each instance of the declaring model "belongs to" one instance of the other model. For example, if your application includes authors and books, and each book can be assigned to exactly one author, you'd declare the book model this way:
class Book < ApplicationRecord
belongs_to :author
end
The has_many Association
A has_many association indicates a one-to-many connection with another model. You'll often find this association on the "other side" of a belongs_to
association. This association indicates that each instance of the model has zero or more instances of another model. For example, an author can publish many books, so the author model could be declared like this:
class Author < ApplicationRecord
has_many :books
end
References: