Skip to content

Latest commit

 

History

History
323 lines (219 loc) · 10.7 KB

File metadata and controls

323 lines (219 loc) · 10.7 KB

Know Your Relationships!

Objective
Understand how to create one-to-many and many-to-many relationships in rails.
Understand how to modify migrations to add foreign keys to tables
Understand how to create a join table
Understand how to create model instances when they have associations.

Associations: Relationships between Models

Relationship type Abbreviation Description Example
One-to-One 1:1 Parent model is associated with one child One driver has one driver's license.
One-to-Many 1:N Parent model is associated with many children from another model One owner has_many pets.
Many-to-Many N:N Two models. Both can be associated with many of the other. Libraries and books. One library can have many books, while one book can be in many libraries.

Active Learning:

Before we move into the actual syntax, let's work together for 10 minutes to come up with some examples of both one-to-one and one-to-many relationships.

One to many (1:N) Relationship

Example: One owner has_many pets and... A pet belongs_to one owner (this model will have a foreign key [FK] owner_id)

Always remember! : Whenever there is a belongs_to in the model, there should be a FK in the matching migration!


### So to set this up, we'll need two models:
rails generate model Pet name:string
rails generate model Owner name:string

Then, we'll need to add the following to our models:

class Owner < ActiveRecord::Base
    has_many :pets
end

class Pet < ActiveRecord::Base
    belongs_to :owner
end

Note that in this case, belongs_to uses the singular form of the class name (:owner), while has_many uses the pluralized form (:pets).

But if you think about it, this is exactly how you'd want to say this in plain English. For example, if we were just discussing the relationship between pets and owners, we'd say:

  • "One owner has many pets"
  • "A pet belongs to an owner"

Now, as mentioned, we have to add a foreign key in our migration, so in our pets migration file we should add:

t.integer :owner_id

But wait, we could also do something more rail-sy and say

t.references :owner

This makes the association a bit more semantic and readable and has a few bonuses:

  1. It defines the name of the foreign key column (in this case, owner_id) for us.
  2. It adds a foreign key constraint which ensures referential data integrity in our Postgresql database.

But wait, there's more...

We can actually get even more semantic and rail-sy and say:

t.belongs_to :owner

This will do the same thing as t.references, but it has the added benefit of being super semantic for anyone reading your migrations later on.


Wading in Deeper: Using our Associations

First things first, we need to create our models, run our migrations, do all things necessary to setup our database.

Let's all run:

rake db:create
rake db:migrate

Now, let's jump into our rails console by typing rails c at a command prompt, and check out how these new associations can help us define relationships between models:

Pet.count
Owner.count
fido = Pet.create(name: "Fido")
lassie = Pet.create(name: "Lassie")
nathan = Owner.create(name: "nathan")
nathan.pets
fido.owner
nathan.pets << fido # Makes "fido" one of my pets
nathan.pets << lassie # Makes "lassie" another one of my pets
nathan.pets.size
nathan.pets.map(&:name)
nathan.pets.each {|x| puts "My pet is named #{x.name}!"}
fido.owner

# What's going to be returned when we do this?
fido.owner.name

Remember: We just saw that in Rails, we can associate two model instances together using the << operator.


Wait!!!! What if I forget to add a foreign key before I first run rake db:migrate?

If you were to make the mistake of running rake db:migrate before adding a foreign key to the table's migration, it's ok. There's no need to panic. You can always fix this by creating a new migration...

rails generate migration NameOfMigrationHere

...and then modifying it to include the following:

change_table :pets do |t|
  # You ONLY need to add ONE OF THESE THREE to your new migration
  t.integer :owner_id
  # OR...
  t.references :owner
  # OR...
  t.belongs_to :owner
end

Class Exercise 1

Let's jump over to in-class exercise #1 where you'll all work in groups on a solution.


Many to Many (N:N) with 'through'

Let's think about the example of students and courses. A student can take many courses and a course will have many students. If you think back to our SQL discussions, you'll recall that we used a join table to create this kind of association.


A Typical 'Join' Table

Remember that a join table has two different FKs, one for each model it is associating. In the example below, 3 students have been associated with 4 different courses.

student_id course_id
1 1
1 2
1 3
2 1
2 4
3 2
3 3

Let's make sure we understand how this join table works before moving on.


So how do we create N:N associations in rails?

We use the has_many :relatedModel, :through => :joinTableName method.

We'll start by creating 3 models

rails generate model Student name:string
rails generate model Course name:string
rails generate model Enrollment enrollment_date:date 

Note that "Enrollment" is our join table.


After generating our models, we need to open them in sublime and edit them so they include the proper associations.

Your models should look as follows once you've finished making the necessary changes:

# models/course.rb
class Course < ActiveRecord::Base
    has_many :enrollments
    has_many :students, :through => :enrollments
end
# models/student.rb
class Student < ActiveRecord::Base
    has_many :enrollments
    has_many :courses, :through => :enrollments
end
# models/enrollment.rb
class Enrollment < ActiveRecord::Base
    belongs_to :course
    belongs_to :student
end

We'll also want to modify the migration for the enrollment model before we run db:migrate. Make your enrollment model look like this:

class CreateEnrollments < ActiveRecord::Migration
  def change
    create_table :enrollments do |t|
      t.date :enrollment_date
      t.timestamps null: false

      # The foreign keys for the associations are defined below
      t.references :student
      t.references :course

      # or we can do this instead
      t.belongs_to :student
      t.belongs_to :course
    end
  end
end

Make sure your save the file before running rake db:migrate.

(Also, just a reminder, if you're using postgresql, be sure to run rake db:create first!)


Let's test out our models!

Open the rails console by running rails c in terminal.

At the command prompt, let's create some students:

nathan = Student.create(name: "Nathan")
travis = Student.create(name: "Travis")
justin = Student.create(name: "Justin")

Now we can create some courses too:

algebra = Course.create(name: "Algebra")
science = Course.create(name: "Science")
english = Course.create(name: "English")
french = Course.create(name: "French")

Associate our model instances

Now, because we've used :through, we can create our associations in the same way we do for a 1:N association. If you're the curious type, you can try just using has_many without through (outside of class time please!!) and then using the console to experiment and figure out how you'd associate, and then access various instances together.

nathan.courses << algebra
nathan.courses << french

travis.courses << science
travis.courses << english
travis.courses << french

# Here's a little trick: Use an array to associate multiple courses with a student in just one line of code.
justin.courses << [english, algebra]

Let's see if it worked

Once you've done all of this, try the following and see if your output matches the below in irb.

nathan.courses.map(&:name)
# Outputs: => ["Algebra", "French"]

travis.courses.map(&:name)
# Outputs: => ["Science", "English", "French"]

justin.courses.map(&:name)
# Outputs: => ["Algebra", "English"]

Many-to-Many Exercise

To do this exercise, let's head over to our many-to-many exercise file and work together in groups of two.


Less Common Associations

For your reference. If you'd like to experiment with these, go nuts! (after class please...)


Other Useful Notes