Lesson Weekend

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 initialize, save, all, update, 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!

Rake Tasks with Active Record


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.

Running the Rails Server

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.

Migrations

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:

db/migrate/20190611211910_create_albums
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.

db/migrate/20190611211910_create_albums
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:

db/migrate/20190611211910_create_albums
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 integer.

The t.timestamps line generates columns called created_at and 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 integer.

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 albums table:

$ rails g migration add_genre_to_albums

We'll add some code inside the change method:

20121229023145_add_genre_to_albums.rb
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 string.

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.

remove_column(:albums, :genre)

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.

Further Resources

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.

Another important resource is the Ruby on Rails API documentation. It's huge and overwhelming but it's also comprehensive. Check out the API documentation for migrations.

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