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 and birth_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 the books 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:

  1. http://guides.rubyonrails.org/association_basics.html

results matching ""

    No results matching ""