/rails-many-to-many

[rails, associations, relationships]

Primary LanguageRubyOtherNOASSERTION

Modeling Relationships in Rails

Objectives

After this lesson, students will be able to:

  • Build models with has_many, belongs_to, and has_many :through
  • Describe macros that match different relationship types

Preparation

Before this lesson, students should already be able to:

  • Create models that inherit from ActiveRecord
  • Explain and generate migrations
  • Describe a relational database

Why are relationships important?

A hefty part of designing a relational database is dividing the data elements into related tables. Once you're ready to start working with the data, you rely on relationships between the tables to pull the data together in meaningful ways. For instance, order information is useless unless you know which customer placed a particular order.

By now, you probably realize that you don't store customer and order information in the same table. Instead, you store order and customer data in two related tables and then use a relationship between the two tables to view each order and its corresponding customer information at the same time. If normalized tables are a relational database's foundation, then relationships are the cornerstone.

####Relationship types

An association, in this context, is a connection between two ActiveRecord models. Associations are implemented using macro-style calls, so that you can declaratively add features to your models. For example, by declaring that one model belongs_to another, you instruct your application to maintain Primary Key-Foreign Key information between instances of the two models, and you also get a number of utility methods added to your model.

  • has_many - Indicates a one-to-many connection with another model. This association indicates that each instance of the model has zero or more instances of another model.
  • belongs_to - 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.
  • has_many :through - A has_many :through association is often used to set up a many-to-many connection with another model. This association indicates that the declaring model can be matched with zero or more instances of another model by proceeding through a third model.

Associations: Relationships Between Models

Relationship Type Abbreviation Description Example
One-to-Many 1:N Parent model is associated with many children from another model One author can have many books.
Many-to-Many N:N Two models that can both be associated with many of the other. Libraries and books. One library can have many books, while one book can be in many libraries.

SQL JOINS

Joins

Each table in a relational database is considered a relation. All of the table's data is naturally related by single set of attributes defined for it. However, in order to be relational, we need to be able to make queries among relations or tables of data.

JOINs are our means of implementing queries that combine data and show results from multiple tables.

There are many kinds of joins, based on how you want to combine data.

Foreign Key

To implement a JOIN between two tables, one of our tables must have a foreign key. A foreign key is a field in one table that uniquely identifies a row of another table. We use the foreign key to establish and enforce a link between the data in two tables.

The foreign key always goes on the table with the data that belongs to data from another table. In the example below, a person has_many pets, and a pet belongs_to a person. The foreign key person_id goes on the pets table to indicate which person the pet belongs to.

Migration Workflow

Getting your models and tables synced up is a bit tricky. Pay close attention to the following workflow, especially the rake tasks.

# create a new rails app
rails new my_app -d postgresql
cd my_app

# create the database
rake db:create

# REPEAT THESE TASKS FOR EVERY CHANGE TO YOUR DATABASE
# <<< BEGIN WORKFLOW LOOP >>>

# -- IF YOU NEED A NEW MODEL --
# auto-generate a new model (AND automatically creates a new migration)
rails g model Pet name:string
rails g model Owner name:string

# --- OTHERWISE ---

# if you only need to change fields in an *existing* model,
# you can just generate a new migration
rails g migration AddAgeToOwner age:integer

# never try to create a migration file yourself through the file system! it's really hard to get the name right!

# -- EITHER WAY --
### whether we're creating a new model or updating an existing one, we can manually edit our models and migrations in sublime
# update associations in model --> this affects model interface
# update foreign keys in migrations --> this affects database tables

# generate schema for database tables
rake db:migrate

# <<< END LOOP >>>

# finally, we need some data to play with
# for now, we'll seed it manually, from the rails console...
rails c
> Pet.create(name: "Wowzer")
> Pet.create(name: "Rufus")

# --- OR ---

# but later we will run a seed task
rake db:seed

Many-to-Many Challenges

Our goal is to build the relationship between actors and movies. An actor can appear in many movies, and a movie can have many actors. How would you set up this relationship? Is there an additional data table we need besides actors and movies? Hint: A join table has two different foreign keys, one for each model it is associating.

Here's what our models' attributes might look like for actors and movies:

  • Actor: first_name, last_name
  • Movie: title, description, year

For these challenges, continue to work in your practice Rails app.

Your Task

  1. Create models and migrations for three tables: actors, movies, and a join table. Think about what you should name your join table and what columns it should have.
  2. Implement a many-to-many relationship between actors and movies.
  3. Use the Rails console to create at least three actors and two movies. Each movie should have at least one actor associated with it.

Stretch Challenges

  1. Add validations to your Actor and Movie models:
  • All attributes for actors and movies should be required (Hint: presence: true)
  • For movies, the year should not be in the future (Hint: Look at numericality)
  1. Test your validations in the Rails console:
a = Actor.create
a.errors.messages
# => What does this return?

Stretch Challenge: Self-Referencing Assocations

Lots of real-world apps create assocations between items that are the same type of resource. Read (or reread) the "self joins" section of the Associations Basics Rails Guide and try to create a self-referencing association in your practice_associations app. (Classic use cases are friends and following, where both related resources would be users.) No solution provided.

Helpful Hints

When you're creating associations in Rails ActiveRecord (or most any ORM, for that matter):

  • Define the relationships in your models (the blueprint for your objects)
    • Don't forget to define all sides of the relationship (e.g. has_many and belongs_to)
  • Remember to put the foreign key for a relationship in your migration
    • If you're not sure which side of the relationship has the foreign key, just use this simple rule: the model with belongs_to must include a foreign key.

Less Common Associations

These are for your references and are not used nearly as often as has_many and has_many through.

Useful Docs

Licensing

All content is licensed under a CC­BY­NC­SA 4.0 license. All software code is licensed under GNU GPLv3. For commercial use or alternative licensing, please contact legal@ga.co.