Oops—we forgot a few fields when we created the movies database table! In addition to the existing fields, a movie could also use a textual description and the date it was released. So we need to modify the database, and that means it's time for a new migration!
Migrations are used not only to create database tables, but also to incrementally modify them. Indeed, almost any modification you can make to a database using an admin tool or the database's native language can be done through a migration. The benefit of using a migration is you end up with a more repeatable (and automated) way for everyone on the project to keep their database in step with the code. And since migrations make it easy to add or change database columns later, we can quickly adapt to new requirements. You'll end up creating many migrations while developing a full-featured Rails app, so it's good to get more practice with them.
Here's what we need to do:
Generate a new migration file that adds two fields to the movies database table
Update the existing movies in the database to have values for the new fields
Change the movie listing page to display the new movie fields
Notice that adding fields to the database has a small ripple effect. This is fairly typical, and it gives us an opportunity to learn how to realign things. It will also help reinforce a few things we learned in previous exercises.
Here are the changes we'll make (in red):
So let's jump right into it...
1. Add New Database Fields
First, we need to add the following columns and types to the movies database:
Note that we're using a date type for the released_on field rather than datetime as we did for events. It makes more sense that a movie is released on a date and an event starts at a date and time.
To add those columns to the database, we'll need a new migration file. Now, when we generated the Movie model we also got a migration file for creating the movies database table. This time we'd like to only generate a new migration file. Not surprisingly, this is so common that Rails has a migration generator.
Start by printing the usage information for the migration generator:
rails generate migration
At the top you'll see that the generator takes the migration name followed by a list of fields and types (and an optional index) separated by colons:
rails generate migration NAME [field[:type][:index] field[:type][:index]] [options]
Note that each field/type pair is specified using the format field:type without any spaces before or after the colon. If a type isn't specified, then the default type is string.
Armed with this helpful information, now use the generator to generate a migration named AddFieldsToMovies with the two fields and types listed above.
Running that command should generate a YYYYMMDDHHMMSS_add_fields_to_movies.rb file in the db/migrate directory. We now have our second migration file!
Open the generated migration file and you should see the following:
class AddFieldsToMovies < ActiveRecord::Migration[6.0] def change add_column :movies, :description, :text add_column :movies, :released_on, :date end end
This time instead of using create_table the change method is using the add_column method to add both columns to the movies table. The add_column method takes three parameters: the name of the table, the name of the column, and the column type.
How did the generator know that we wanted columns added to that specific table? That's another naming convention. If you name your migration using the format AddXXXToYYY, Rails assumes you want to add columns with the listed field names and types to the specified table (YYY). Pretty smart!
It's worth pointing out that you could have generated the migration like this:
rails g migration add_fields_to_movies
In this case, because the field names and types weren't listed, you would end up with an empty change method, like so:
class AddFieldsToMovies < ActiveRecord::Migration[6.0] def change end end
You'd then need to put the add_column lines in the change method.
So, if you follow the naming conventions, Rails is able to generate the complete migration for you. In cases where the naming convention doesn't make sense, you'll just need to edit the change method yourself.
Now that you have a new migration file, check the migration status by running:
This time you should see two migrations:
database: db/development.sqlite3 Status Migration ID Migration Name -------------------------------------------------- up 20190502122806 Create movies down 20190506213706 Add fields to movies
The second (new) migration has a "down" status because we haven't yet run it. Rails knows about the migration because it's in the db/migrate directory.
Go ahead and run the migration.
You should get the following output:
== AddFieldsToMovies: migrating ============================================== -- add_column(:movies, :released_on, :date) -> 0.0006s -- add_column(:movies, :description, :text) -> 0.0003s == AddFieldsToMovies: migrated (0.0010s) =====================================
Great—the migration successfully added both columns! It's important to note that only the second migration was run. The first migration wasn't re-run. Remember, when you run the db:migrate task, Rails looks at all the migration files in the db/migrate directory and only runs the migrations that haven't already been run. In other words, it only runs the migrations that have a status of "down".
Finally, re-check the migration status.
You should see both migrations marked as "up":
database: db/development.sqlite3 Status Migration ID Migration Name -------------------------------------------------- up 20190502122806 Create movies up 20190506213706 Add fields to movies
So now we've run two migrations. The first migration created the movies table and the second migration added new columns to that table.
2. Update the Movies
Next we need to update the movies in our database so that they have values for the new fields that we just added. To do that, we'll need to read each movie into the console and assign values to the new description and released_on attributes. And you already know how to do that!
Over in your console session, make sure to load the latest version of your application code by using the reload! command:
>> reload! => true
Then find the "Iron Man" movie and assign it to a movie variable.
You should get the following output:
=> #<Movie id: 1, title: "Iron Man", rating: "PG-13", total_gross: 0.585366247e9, created_at: "2019-05-02 12:46:57", updated_at: "2019-05-02 12:54:35", released_on: nil, description: nil>
Notice that the movie now has description and released_on attributes matching the new columns we added to the movies database table. We didn't have to change the Movie model. When the Movie class was declared in the console, ActiveRecord queried the movies table schema and automatically defined attributes for each column.
You probably noticed that the movie's description and released_on attributes have nil values, but go ahead and print them out just for practice.
Now assign values to the description and released_on attributes, and save the movie. For the date, you can assign a string with the format "YYYY-MM-DD" and it will get converted to a date.
Next, follow suit by updating the "Superman" movie.
Finally, update the "Spider-Man" movie. This time, update the attributes and save the movie in one fell swoop.
And with that, our database is all set!
3. Update the Movie Listing
The last step is to update the movie listing page so that it displays the new movie fields. You already know how to do this, too!
Refresh the index page and it should come as no surprise that the movies don't appear to have a description or release date.
Fix that by updating the index.html.erb template. Just to keep things simple for now, put each new field in a paragraph tag.
That completes this feature! We added missing columns to the database and now those changes are being reflected on the movie listing page. It's interesting to note that we didn't need to change the index action in the MoviesController. And that's exactly as it should be!
Remember, the controller is just a middleman between the model and the view. It doesn't concern itself with the details of the model or how the data is displayed in the view. So in this case, because we didn't have to change the controller, we're confident that we have the MVC responsibilities properly divided.
The full solution for this exercise is in the
migrations directory of the code bundle.
Rails is full of conventions that make apps easier to build, test, change, and ultimately, pass on to the next developer to test, change, and maintain. To practice using the Rails conventions you've learned so far, suppose you wanted your Rails app to be a listing of books for sale.
By convention, the book listing route would map a _______ request for the URL ________ .
The name of the controller would be _____________ and the name of the action would be ________.
If we don't explicitly tell the index action the name of the view template to render, what will Rails do by convention?
The model would be named _____________.
The corresponding database table would be named _____________.
If the model was named Person, the database table would be named _________.
After a migration has been run (or applied), its status changes from _______ to _____.
Any time you have to make a change to the database, think "new migration." Missing a column? Time for a new migration. Need to delete a column? That's another migration. Want to rename a table or column? Yup, migrations can do that, too. In fact, we'll talk even more about migrations a little later in this course.
Indeed, creating new migration files is really common. A typical Rails app will end up with tens, if not hundreds, of migration files where each migration represents an incremental database change. On team projects, all those migration files get checked in to a version control system as part of the Rails project. Then, whenever someone on the project checks out a version of the application, they can get their database schema in sync with the code simply by running rails db:migrate. That's the beauty of migrations. You end up with an automated, repeatable way to make modifications to the database.
OK, so now that we're showing more information on the movie listing page, it looks like we could use a bit of formatting. We'll tackle that using view helpers in the next section.
We'll write a couple more migrations a bit later in the course so you see them used in different situations. To learn more about migrations, refer to the Rails Guides: Migrations.