- Understand how to make migrations that reference other models
- Set up relationships in models using
belongs_to
andhas_many
- Use ActiveRecord models to query and create related entities
- Learn how shallow routes support related models
Lets say we have an app where we are storing information about music artists and their work. We can start by generating a model for an artist with:
rails g model Artist name:string genre:string
This will create an Artist model and a migration to add the table to our database. If we want to then add a table where we keep track of each artist's albums, we'll want to generate a new model for Albums. In the SQL world we learned about foreign keys and how they keep track of a primary key that the entry relates to. For example an Album can have an artist_id
that references back to an artist. In rails we can add this reference when we generate the model:
rails g model Album name:string release_year:integer artist:references
This will create a migration that looks like:
class CreateAlbums < ActiveRecord::Migration[5.2]
def change
create_table :albums do |t|
t.string :name
t.integer :release_year
#this line will add our foreign key for artist_id
t.references :artist, foreign_key: true
t.timestamps
end
end
end
Generating that model will also create a model file that looks like:
class Album < ApplicationRecord
belongs_to :artist
end
A line is added that tells our model that there is a relationship to another table. This does a few things.
- It makes it so when we create an album, we need to specify an artist for it.
kesha = Artist.create({
name: "Ke$ha",
genre: "pop"
})
rainbow = Album.create({
name: "Rainbow",
release_year: 2017,
artist: kesha # we need to specify an artist here or there will be errors!
})
- It allows us to access our artist from our album
rainbow.artist # will give us kesha
At this point, we may also want to be able to access all of the albums from an artist. To add these methods to our Artist, we need to edit our artist model:
class Artist < ApplicationRecord
has_many :albums
end
Now we can access albums from any artist.
Artist.first.albums # will give us back all of the albums associated with the first artist
We can also create albums through our artist
kesha = Artist.find_by({ name: "Ke$ha" })
kesha.albums.create({
name: "Animal",
release_year: 2010,
artist: kesha
}) # will create the "Animal" album with the artist "Ke$ha"
Setting up routes can be challenging. There are a lot of ways to do so and there often isn't always a "right way". Its always good to think about your application and what you need from it and go through your options.
For example, for our music app we could:
Rails.application.routes.draw do
resources :artists
resources :albums
end
which would give us:
method | route | result |
---|---|---|
GET | /artists | get all artists |
POST | /artists | create an artist |
GET | /artists/:id | get an artist by id |
PUT | /artists/:id | update an artist |
DELETE | /artists/:id | delete an artist |
GET | /albums | get all albums |
POST | /albums | create an album |
GET | /albums/:id | get an album by id |
PUT | /albums/:id | update an album |
DELETE | /albums/:id | delete an album |
Rails.application.routes.draw do
resources :artists do
resources :albums
end
end
which would give us:
method | route | result |
---|---|---|
GET | /artists | get all artists |
POST | /artists | create an artist |
GET | /artists/:id | get an artist by id |
PUT | /artists/:id | update an artist |
DELETE | /artists/:id | delete an artist |
GET | /artists/:artist_id/albums | get albums by an artist |
POST | /artists/:artist_id/albums | create an album belonging to an artist |
GET | /artists/:artist_id/albums/:id | get an album by artist id and album id |
PUT | /artists/:artist_id/albums/:id | update an album by artist id and album id |
DELETE | /artists/:artist_id/albums/:id | delete an album by artist id and album id |
Rails.application.routes.draw do
resources :artists do
resources :albums, shallow: true
end
end
which would give us:
method | route | result |
---|---|---|
GET | /artists | get all artists |
POST | /artists | create an artist |
GET | /artists/:id | get an artist by id |
PUT | /artists/:id | update an artist |
DELETE | /artists/:id | delete an artist |
GET | /artists/:artist_id/albums | get albums by an artist |
POST | /artists/:artist_id/albums | create an album belonging to an artist |
GET | /albums/:id | get an album by id |
PUT | /albums/:id | update an album |
DELETE | /albums/:id | delete an album |
The last one here is good because it stops our routes from getting too long. I like to do a bit of a mixture where I use shallow routes, but also add in an index for albums not scoped to artists. This way I have a route to see all of the albums, not just by artist.
Rails.application.routes.draw do
resources :artists do
resources :albums, shallow: true
end
get "/albums", to: "albums#index"
end
which would give us:
method | route | result |
---|---|---|
GET | /artists | get all artists |
POST | /artists | create an artist |
GET | /artists/:id | get an artist by id |
PUT | /artists/:id | update an artist |
DELETE | /artists/:id | delete an artist |
GET | /artists/:artist_id/albums | get albums by an artist |
POST | /artists/:artist_id/albums | create an album belonging to an artist |
GET | /albums/:id | get an album by id |
PUT | /albums/:id | update an album |
DELETE | /albums/:id | delete an album |
GET | /albums | get all albums |