Associations

We've talked about the objects behind our application: they represent the data-bearing elements of our application. Often, objects in the real world work together or, more correctly, are tied to each other.

One-to-Many Relationship

Let's examine two models in our sample application: Book and Author. Of course, an author is a person, but what we are interested in here is the fact that this person is an author of some book, nothing more.

So, the first step is to represent the models in Rails with the following code:


# app/models/book.rb
class Book < ActiveRecord::Base
end

# app/models/author.rb
class Author < ActiveRecord::Base
end

With this code we are simply saying that our application has two models: Book and Author. Both will have some attributes in the underlying database (eg: title, first name, email address, etc).

Now, it's time to tie those two models together. Capturing the association in natural language is easy: every book has one author and an author can write more than one book. Rewriting the code results in:


# app/models/book.rb
class Book < ActiveRecord::Base
  belongs_to :author
end

# app/models/author.rb
class Author < ActiveRecord::Base
  has_many :books
end

To tie rows in different tables together, id fields are used by default. If a model belongs to another model, the former must contain that reference. In our example, the Book model as represented in the books table contains an author_id field which correlates to the id field in the authors table.

The keyword has_many in the Author model will be useful to quickly references books that belong to some author:


andy = Author.find(:first)
andy_books = andy.books

One-to-One Relationship

If we want to capture the fact that an author can have a (potentially complex) address with data like city, ZIP code, county, country, GoogleMap reference, etc we should add an Address class like that:


class Address < ActiveRecord::Base
  belongs_to :author
end

class Author < ActiveRecord::Base
  has_many :books
  has_one :address
end

The Address model contains an author_id field, which corresponds to the Author model's id field and indicates who each address belongs to. Notice how easy it is to express associations in Rails, using natural language.

Many-to-Many Relationships

has_and_belongs_to_many

In our sample application each user can submit reviews for books, and a given book can have multiple reviews (from many users). This is termed a many-to-many relationship.

So we should refactor our models' code in this way:


# app/models/book.rb
class Book < ActiveRecord::Base
  belongs_to :author
  has_and_belongs_to_many :users   # as reviewers
end

# app/models/user.rb
class User < ActiveRecord::Base
  has_and_belongs_to_many :books   # as reviews
end

To express the fact that a bidirectional many-to-many relationship between two models exists we use the keyword has_and_belongs_to_many. Typically, you would then create a table to contain the relationship id fields. In our example, we would create a books_users table with a book_id field and a user_id field. The table name is derived by alphabetical joining of the plural of the model names.

has_many, :through

The problem with our approach in the previous section is that it limits us in what information we can store. Sure, we can know that a given book was reviewed by a particular user, but where do we store the actual wording of the review? When you need to store more information about a many-to-many relationship than that it exists, you should use a has_many, :through relationship.

We need another model between the User and the Book models: Review.


# app/models/book.rb
class Book < ActiveRecord::Base
  belongs_to :author
  has_many :reviews
end

# app/models/review.rb
class Review < ActiveRecord::Base
  belongs_to :book
  belongs_to :user
end

# app/models/user.rb
class User < ActiveRecord::Base
  has_many :reviews
end

To make all of this work, we would create a reviews table, with these fields: book_id, user_id, review_body, and any other data we wanted to store about the rating (like the number of stars).

With this approach:

  • Each book can have multiple reviews
  • Each review relates to a given book and is written by a particular user
  • Each user can submit reviews for multiple books

Additional Resources

Also available in: HTML TXT