Dive into this detailed tutorial on Ruby on Rails, where you'll learn to create models for cast members and genre, add objects to these models, update views, and navigate multiple exercises for a practical understanding of the framework.
This exercise is excerpted from Noble Desktop’s past web development training materials. Noble Desktop now teaches JavaScript and the MERN Stack in our Full Stack Development Certificate. To learn current skills in web development, check out our coding bootcamps in NYC and live online.
Topics covered in this Ruby on Rails tutorial:
Creating a model for cast members, Adding objects to the cast members model in Rails Console, Updating views to include the cast members model, Creating a genre model, Adding a genre field to the edit form
Exercise Preview
Exercise Overview
The Editorial Department at Flix has asked us to add two bits of information to every movie’s page: cast members and genre. What are the data requirements for this assignment? For cast members, we need to be able to support multiple cast members per film.
As for genre, the Editorial Department wants each genre to be clickable, leading to a separate page that lists all the films in a given genre. That’s a tall order, but with our extensive knowledge of models, we can handle it!
-
If you completed the previous exercises, you can skip the following sidebar. We recommend you finish the previous exercises before starting this one. If you haven’t finished them, do the following sidebar.
If You Did Not Do the Previous Exercises (3A–5A)
- Close any files you may have open.
- Open the Finder and navigate to Class Files > yourname-Rails Class
- Open Terminal.
- Type
cd
and a single space (do NOT press Return yet). - Drag the yourname-Rails Class folder from the Finder to the Terminal window and press ENTER.
- Run
rm -rf flix
to delete your copy of the Flix site. - Run
git clone https://bitbucket.org/noble-desktop/flix.git
to copy the Flix git repository. - Type
cd flix
to enter the new directory. - Type
git checkout 5A
to bring the site up to the end of the previous exercise. - Run
bundle
to install any necessary gems. - Run
yarn install --check-files
to install JavaScript dependencies.
Getting Started
Open the Finder and navigate to Class Files > yourname-Rails Class
Open Terminal.
Type
cd
and a single space (do NOT press Return yet).Drag the flix folder from the Finder to the Terminal window.
Make sure you’re in Terminal and hit Return to change into the new folder.
-
Type the following in Terminal:
rails server
Let’s check that everything is working on the site. Switch to a browser and navigate to localhost:3000 to make sure it’s up and running.
Back in Terminal hit Ctrl–C to shut down the server.
Creating a Model for Cast Members
How can we create a field for cast members that has an unknown number of values? There are several possible solutions, but one of the simplest is to just create a new model. To do that, switch to the Terminal.
-
Type the following, making sure to double-check the code before hitting Return:
rails generate model cast_member name:string movie:references
These are all of the fields that will be in the new
cast_member
model. Pretty straightforward! Thereferences
field is the only field type that we haven’t seen before. This will tell Rails that we want the new model to relate back to the movies model. We suggest opening the flix folder in your code editor if it allows you to (like Sublime Text does).
In your code editor, open flix > db > migrate > #
_
create_
cast_
members.rb (the # stands for the unique 14-digit timestamp in the filename).-
Take a look at line 5 in this automatically generated file:
t.references :movie, null: false, foreign_key: true
We typed the
references
bit ourselves, of course, but Rails addedforeign_key: true
all on its own. This line is crucial to establishing a relationship between the movies model and the cast_members model. Because the two will be associated, the two databases must have a consistent system to keep the records straight, especially if the databases start holding large numbers of records down the line.Also note the
null: false
bit. This means that each cast member must be attached to a movie. Close the file, as we’re done checking it out.
-
Switch to the Terminal and type the following to apply the migration:
rails db:migrate
Switch to your code editor.
-
Open app > models > cast_member.rb
When we created the movie model file earlier, it was completely empty—whereas this file has a line automatically added by Rails that reads:
belongs_to :movie
This is great, but to get all the Rails magic working and set up a properly functioning model relationship, we need to ensure that the previously-created movie model knows about the cast_member model, too! Open app > models > movie.rb
-
Add the following bold code around line 5, above the scope:
validates :runtime, numericality: true has_many :cast_members scope :with_placement, -> (placement) { where(placement: placement) }
This is telling the movie model that it has multiple cast members, thus satisfying the data requirement that each movie is able to have any number (zero or more) cast member objects associated with it.
Save the file.
Adding Records to the Cast Members Model in Rails Console
Switch to the Terminal.
-
Type the following to start up the Rails console:
rails console
We could create a form to add cast members, but for additional practice (and because it’s way faster) let’s use the Rails console to add cast members to the database. First, let’s look up a movie.
-
Type the following:
movie = Movie.find(1)
This will return
Text M for Murder
. Let’s add some cast members. -
Type:
movie.cast_members.new(name: "John Jones") movie.cast_members.new(name: "Susan Shine") movie.cast_members.new(name: "Ed Kovac") movie.save
Terminal will return a bit of SQL, letting us know that three things have been inserted into
cast_members
and that all of them are associated with"movie_id": 1
. Looking good so far. -
Type the following to see which cast members belong to Text M for Murder:
movie.cast_members
This will return a messy, dense bit of code; if you look carefully you’ll see that there are three cast member records. There’s an easier way to count them.
-
Type:
movie.cast_members.count
Terminal will print
3
. We can also start referring to these cast members directly. -
Type the following:
movie.cast_members.first.name
Terminal will print
John Jones
. (Note that it’s the first cast member’s full name, not the first name of a cast member.) We can also access cast members with arrays. -
Type:
movie.cast_members[1].name
Because the count starts with zero, this will return
Susan Shine
.NOTE: You may have noticed that Rails uses capitalized class names like
CastMember
when referring to the class by name, and a lowercase, underscored versioncast_members
in other contexts, such as referring to a method. This can be confusing until you get familiar with the convention. It’s just a weird idiosyncrasy that Rails seamlessly switches back and forth between the two. -
Try typing the following:
susan = CastMember.find_by(name: "Susan Shine")
Terminal will print all her information. This is a good way of finding her record without knowing her id number.
-
Type the following:
susan.movie.title
Terminal will print
Text M for Murder
. This helps elucidate why one model getshas_many
and another model getsbelongs_to
. -
In the diagram shown below, notice that one model has a reference (often called a foreign key) as rendered in italic text, and that model belongs to (
belongs_to
) the model thathas_many
. Therefore, cast_member with its foreign keymovie_id
belongs to movie. Meanwhile, movie has many (has_many
) cast members! We are going to get more familiar with this concept over time, so don’t worry if it’s a bit baffling at first.The foreign key refers to the field we created when we generated the cast_member model in this way:
rails generate model cast_member name:string movie:references
This created a field called
movie_id
which is the foreign key. It is called “foreign” because it refers to another table, and called a “key” because it refers to that table’s primary key (id). -
We can create multiple records at once by passing an array of hashes. Let’s add the cast to Planet of the Apps (which has an id of 2) by typing:
Movie.find(2).cast_members.create([{name: "Mark Wallburg"}, {name: "Hellova Carter"}, {name: "Tim Rath"}])
Remember that the
create
command performs thenew
andsave
actions in one fell swoop. Compared to the way we added cast members to Text M for Murder, usingcreate
and passing an array of hashes saved a lot of work. To save you even more work, we’ve typed up cast members for the rest of the movies. Switch to the Finder.
Navigate to and open the following file, which will probably open in TextEdit:
Class Files > yourname-Rails Class > flix snippets > cast_members.txtHit Cmd–A to select all the contents of the file and copy them (Cmd–C).
Close the file and switch to the Terminal.
-
Paste (Cmd–V) the text, then press Return once to apply the command.
Great, the cast members should all be added! Because they were added with
create()
, the records are saved automatically. -
Type the following to exit Rails console:
exit
Updating Views to Include the Cast Members Model
Let’s update the views to display cast members while using a model method to help keep the code DRY. Switch to your code editor.
Open app > models > movie.rb
-
Add the following bold code at the bottom of your code:
def cast cast_members.map { } end end
-
So far, the method simply takes cast members and collects them. Flesh out the method by adding the following bold code:
cast_members.map { |c| c.name }.join(", ")
This takes the cast member’s name and joins it with a comma so that lists of multiple cast members will be formatted properly.
-
Let’s make sure that in the case that the
cast_members
value isnil
, the page won’t throw an error. Add anunless
condition as shown in bold:cast_members.map { |c| c.name }.join(", ")
unless cast_members.nil?
Save the file.
Open app > views > movies > index.html.erb
-
Find the following code, which should start around line 21:
<h3><%= movie.title %></h3> <div><%= movie.mpaa_rating %>, <%= movie.runtime_hours %></div>
-
Add the following bold code:
<h3><%= movie.title %></h3> <div><%= movie.cast %></div> <div><%= movie.mpaa_rating %>, <%= movie.runtime_hours %></div>
Save the file.
Open app > views > movies > show.html.erb
-
Add the following code around line 7:
<h2><%= @movie.title %></h2> <p>Cast: <%= @movie.cast %></p> <p class="mpaa-rating">Rating: <%= @movie.mpaa_rating %></p>
Save the file.
-
Type the following in Terminal:
rails server
-
Switch to a browser and navigate to localhost:3000/movies
Notice that the cast members are now listed beneath the poster images! Click on any movie to view its detail page; cast members are displaying there, too.
Switch back to Terminal and hit Ctrl–C to shut down the server.
Creating a Genre Model
Creating a genre model is going to be a little more complicated. First we need to figure out where the foreign key should go. In this case, each genre (Horror, Action, Drama) only appears once, but it applies to many (has_many
) movies. And, each movie only has (belongs_to
) one genre. It sounds like movie needs the foreign key in this situation.
-
In Terminal, create the genre model by typing the following:
rails generate model genre name:string
This model only has a single field (
name
) because the foreign key is going to go in the movies model; in fact, let’s add it now. -
Generate a migration by typing:
rails generate migration add_genre_id_to_movies genre:references
-
There’s one more change we need to make before applying this migration. Open the newly-created migration file and amend this line:
add_reference :movies, :genre, null: false, foreign_key: true
Remove
null: false,
so it looks like this:add_reference :movies, :genre, foreign_key: true
Why did we have to do this? We already have several movies in the database, and we don’t want to assign a default genre. We will get an error if we try to apply this migration because those records will have a NULL value for genre_id by default.
Save the file.
-
Type the following to apply the migration:
rails db:migrate
We need to make sure that relationship information is added to both models.
Switch to your code editor.
Open app > models > genre.rb
-
Add the following bold code:
class Genre < ActiveRecord::Base has_many :movies end
Save the file.
Open app > models > movie.rb
-
Add the following code around line 7:
has_many :cast_members belongs_to :genre, optional: true
Note that we are adding
optional: true
here. This makes the genre field optional for movies. Otherwise, existing movies without a genre will throw an error.
You might be thinking that it sounds a little funny to say that movie belongs to genre. In terms of how we think about our data, movies are WAY more important than their genre! It’s just one of those things you have to get used to—belongs_to
, at the end of the day, exists to tell Rails which model has the foreign key.
Save the file.
Switch to Terminal.
-
Let’s use Rails console to add a few genres quickly. Type the following:
rails console
-
Type the following to create a few genres:
Genre.create(name: "Horror") Genre.create(name: "Drama") Genre.create(name: "Sci-Fi")
This range of genres is pretty simplistic, but it should encompass the handful of films that we have on Flix so far.
-
Type the following to get out of the Rails console:
exit
Re-purposing the Play Trailer Button as an Edit Button
Now that we have created the genre model, related it to movies and made a few genres, we’ll start assigning genres to movies. Let’s get some more practice adding additional models to browser-accessible forms by adding genres to the movie edit form.
Before we do that, there’s something we’ll have to address. It will be tiresome to go to localhost:3000/movies/1/edit and then localhost:3000/movies/2/edit and so on for all of the movie records. Instead, let’s re-purpose the Play Trailer button on the movie detail page and make it an Edit button.
Switch to your code editor.
Open app > views > movies > show.html.erb
-
Find the following code, around line 10:
<%= simple_format @movie.description %> <a href="#" class="button">Play Trailer</a> </div>
-
Replace the Play Trailer line of code with the following bold line of code:
<%= simple_format @movie.description %> <%= link_to "Edit This Movie", edit_movie_path(@movie) %> </div>
By passing
edit_movie_path
to the instance variable@movie
, Rails will know—based on the movie object itself—which movie theedit_movie_path
should lead to! Pretty cool, huh? -
Still editing the same line, add the following bold code to preserve the CSS class and styling of the button:
<%= simple_format @movie.description %> <%= link_to "Edit This Movie", edit_movie_path(@movie), class: "button" %> </div>
Save the file.
Switch to the browser, navigate to localhost:3000/movies and click on any of the detail pages to admire the handsome Edit This Movie button that is present on every page! The button should lead to the correct path, too.
Adding a Genre Field to the Edit Form
Switch to your code editor and open views > movies >
_
form.html.erb-
We need to add the genre dropdown menu to this form. Add the code as follows, around line 15:
<%= f.text_area :description, cols:60, rows:10 %> </p> <p> <%= f.label :genre_id, "Genre" %> </p> <p> <% f.label :has_subtitles %>
Instead of using
collect
, we are about to use a Rails method calledcollection_select
and let that do the heavy lifting for us.The
collection_select
method expects the following (in order):- The name of the field
- The collection to be used
- The value method (the value we are going to store in the database)
- The text method and any options
-
The
collection_select
method might sound confusing for the moment, but let’s see what it looks like. Add the following bold code:<p> <%= f.label :genre_id, "Genre" %> <%= f.collection_select :genre_id, Genre.all, :id, </p>
So far we have the name of the field (
:genre_id
), the collection itselfGenre.all
(that is, all instances of the genre class) followed by the name of the value we want to store in the database (the value method) which is:id
because we want to take the id of the selected genre and save that in the genre id field for this model. -
Add the following bold code to the
collection_select
method:<%= f.collection_select :genre_id, Genre.all, :id, :name, include_blank: true %>
We’ve just added
:name
(the text method) which is what we want to display in the menu. Finally, in the spot for any additional options, we’ve addedinclude_blank: true
, which ensures that no genre is selected by default and, in the case that users do not wish to select a genre, the field could be left blank. Save the file.
Open app > controllers > movies_controller.rb
-
Scroll down to the bottom of the file. Inside the private
movie_params
method in the long line of code, add the following bold code (keeping it on one line):movie_params
=
params.require(:movie).permit(:title,
:description,
:has_subtitles,
:placement,
:mpaa_rating,
:release_date,
:ticket_price,
:runtime,
:poster_image,
:director
, :genre_id
)
If you forgot to add this to
permit
, you could send the information through the form but it would not be saved! Save the file.
-
Type the following in Terminal:
rails server
Switch to a browser and navigate to localhost:3000/movies/1
Click on the Edit This Movie button.
Select Horror as the genre.
-
Click Update Movie.
You should now be back to the detail page for Text M for Murder. We assigned a genre, but notice it’s not appearing on the details page! Let’s fix that. Keep the page open in your browser so you can reload it in a moment.
Switch to show.html.erb in your code editor.
-
Add the following code around line 15:
<h4>Movie Details</h4> <div> <span>Genre:</span> <%= @movie.genre.name %> </div> <div> <span>Director:</span>
-
Add an
unless
statement to avoid errors in the case the genre value isnil
:<h4>Movie Details</h4> <div> <span>Genre:</span> <%= @movie.genre.name unless @movie.genre.nil? %> </div> <div> <span>Director:</span>
Save the file.
Switch back to the browser and reload the movie detail page for Text M for Murder. The Genre should be displaying properly in the Movie Details sidebar. Great!
-
Use the Edit This Movie button on each of the movie detail pages to set the following genres for the four remaining movies:
Planet of the Apps: Sci-Fi The Typographer’s Wife: Drama Gone with the Windows: Drama Will Code for Brains: Horror -
Back in Terminal hit Ctrl–C to shut down the server.
In the next exercise we will work on creating a genre view to display all the movies in a given genre on a special page.