Active Record is an object-relational mapping library, or ORM. The job of an ORM like Active Record is to map objects to database tables, allowing us to store and retrieve data from our databases with very little code.
To accomplish this, Active Record provides a base class. All of our database-backed classes will inherit from this class. The base class includes all of the CRUD methods we've previously written for each class such as
destroy, and so on. Now when we create a new class, we'll have it inherit from this Active Record base class and all of those methods will automatically be in place. That means we no longer need to write these methods from scratch!
Active Record uses Rake to make many tasks easier. From now on, we will no longer need to create databases and tables in psql. Instead, Active Record and Rake will do it for us.
Let's run our first task in the root directory of our new record store project. Note that
postgres must be running for this command to work.
$ rake db:create
This looks similar to the Rake tasks we wrote in the last section's Rake Tasks lesson. The one difference is that this tasks uses a
db namespace for database tasks.
The command above is exactly the same as running the following commands in psql:
CREATE DATABASE record_store_development; CREATE DATABASE record_store_test;
To drop the databases, run
$ rake db:drop.
We can type in
rails server (or
rails s for short) in the root directory of our project to run a local development server. We'll see something like this:
=> Booting Puma => Rails 5.2.3 application starting in development => Run `rails server -h` for more startup options Puma starting in single mode... * Version 3.12.1 (ruby 2.4.1-p111), codename: Llamas in Pajamas * Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://localhost:3000 Use Ctrl-C to stop
We can then navigate to localhost:3000 to see the Rails application. For now, there is only a welcome message, but soon we'll be navigating and debugging more complex websites.
Note that Windows users may notice warnings like
*** SIGUSR2 not implemented, signal based restart unavailable!. This means that your Windows system can't recognize certain signals that are meant for UNIX-based systems; these signals are used for ending ("killing") processes in a specific way. However, this should not cause issues in the normal functioning of our Sinatra apps, so you should ignore these warnings. If you want to explore resolving these warnings, you can try using Windows Subsystem for Linux (WSL), or try setting up a different web server for Rails.
With Active Record, whenever we want to make a change to our database, we do so with a migration. This will make it much easier to update our database and to communicate the changes with other developers. Before, we'd have to tell other developers working on the project what changes we'd made. They'd either have to recreate the changes themselves or make a copy of an updated database dump.
Let's add an
albums table to our database using a migration.
$ rails generate migration create_albums
Note that we're using the
rails command instead. We can also write this shorthand like this:
rails g migration create_albums.
Note that Rails has a number of built-in generators (see
$ rails g --help for a list). Other than the migration generator, please don't use them yet. They generate a lot of additional files, folders, and comments that we don't need right now.
When we generate a migration, Active Record automatically creates a migration file inside of db/migrate. Let's go to that directory now. The file will be named something like this: 20190611211910_create_albums. The number, which includes a timestamp, will differ in your application.
Let's take a look inside that file:
class CreateAlbums < ActiveRecord::Migration[5.2] def change create_table :albums do |t| end end end
Our migration created a new class called
CreateAlbums. This class name comes directly from what we named the migration. It's called
CreateAlbums to describe what this migration is doing: creating our new
albums database table where information about our
Album objects will be stored. Also, note that this new class inherits from
ActiveRecord::Migration. Active Record manages this line of code so we will never change it.
Now let's look at the block of code below that. We've added the comment to clarify what we'll change inside this file.
def change create_table :albums do |t| # Here we'll put code that specifies the changes we want to make to our database. end end
Every migration will come with a
change method. We will never change the name of the method itself. However, we will make changes inside the
change method because this is how we'll make updates to our database.
Now we can pass in the changes we want to make to our table:
class CreateAlbums < ActiveRecord::Migration[5.2] def change create_table(:albums) do |t| t.column(:name, :string) t.column(:year, :integer) t.timestamps() end end end
Make sure to save the file after the new code is added.
When we run this migration, it will create a table in the database called
albums with a column called
name of the type
varchar (which maps to
string) and a column called
year of the type
t.timestamps line generates columns called
updated_at that Active Record will automatically populate with dates a row was created and updated.
Though not evident in the code, Active Record automatically creates a primary key called
id of the type
Let's run the migration:
$ rake db:migrate
Now we can look at the updates to our database by looking at the file db/schema.rb. This is Active Record's snapshot of our current database schema. Notice how Active Record keeps track of which migrations have been run with the
version: 2019_06_11_211910 code.
Any time we make a migration, we need to mirror the changes in our test database. To do this, we run the following:
$ rake db:test:prepare
This is a rake task that ensures our test database is an exact mirror of our current development database.
Let's write a second migration. This time we'll add a
genre to our
$ rails g migration add_genre_to_albums
We'll add some code inside the
class AddGenreToAlbums < ActiveRecord::Migration[5.2] def change add_column(:albums, :genre, :string) end end
This migration adds a column to the
albums table called
genre with the type
When naming migrations, start with a verb to describe the change that the migration will make to the database. Remember that migration names should always be in lower snake case — all lowercase and separated by underscores.
If we mess up a migration and need to roll our database back to the previous state, simply run:
$ rake db:rollback
We should only rollback a migration if we make a mistake and we haven't shared that migration with other developers. Here's a good rule of thumb: if we haven't made a commit yet, it's okay to rollback and change a migration. If we've made a commit, we should probably make a new migration.
Let's say we've already committed our changes to our database only to realize we don't want a
genre column after all. We'd create a new migration where the
change method includes the following code. Note that this is just an example — we won't actually remove this column.
We need to specify the name of the table and the column but not the data type.
It's fine to have a lot of migrations. They are incremental changes to a database. A large project will have hundreds or even thousands of migrations.
Active Record provides a huge amount of functionality. The Ruby on Rails Guides are a great source of information on Rails and Active Record. The section on migrations provides more in-depth information on this topic. It's not necessary to read it all now but it's recommended to take a quick look.
Also check out the section on Active Record Basics. When we're working on a project and need a reference on how to write a migration (or anything else dealing with Active Record), the Rails Guide provide the best documentation.
We're moving from writing everything out explicitly ourselves to using pre-built tools that do a huge amount of work for us. Our job as programmers has just shifted: instead of figuring out a good solution to a problem with a relatively straightforward toolset (e.g. Ruby and SQL), we now need to figure out how to leverage existing very well-built but often poorly-documented tools to solve our problems for us. One of the most important skills we can improve at this point is our ability to decipher documentation and to use search engines to make targeted searches of the information we need.
Lesson 4 of 34
Last updated August 7, 2022