Lesson Weekend

The Rails console is a powerful IRB shell loaded with the Rails development environment. We can use it to try out commands, query the database, and debug our application. Most Rails developers rarely use psql to look at the database data. Instead, we should use the Rails console.

In this lesson, we'll practice using the rails console and try out some of the methods that Active Record provides us.

The Rails Console


We can open the Rails console from the terminal like this:

$ rails console

We can also use a shorter command:

$ rails c

Because our Album model inherits from the ActiveRecord::Base class, we can use Active Record CRUD methods to explore and manipulate data in the database.

Creating Database Entries Through the Console

We'll start by creating a few new Albums and Songs in the database. First, we'll use the #new and #save methods.

New and Save

2.2.0 :001 > album = Album.new
=> #<Album id: nil, name: nil, year: nil, created_at: nil, updated_at: nil, genre: nil>

You'll see after the => that an empty instance of Album is returned with no values in any of its properties.

Now we can assign values to this Album:

2.2.0 :002 > album.name="In Rainbows"
 => "In Rainbows"
2.2.0 :003 > album
=> #<Album id: nil, name: "In Rainbows", year: nil, created_at: nil, updated_at: nil, genre: nil>

Finally, we can save it with album.save():

2.2.0 :004 > album.save
(0.2ms)  BEGIN
Album Create (0.4ms)  INSERT INTO "albums" ("name", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id"  [["name", "In Rainbows"], ["created_at", "2019-06-17 15:46:54.422926"], ["updated_at", "2019-06-17 15:46:54.422926"]]
(0.8ms)  COMMIT
=> true

The #save method triggers a SQL INSERT that creates a new record on the albums table for our album object as seen between the BEGIN and COMMIT. The database returns the value true to indicate that the save was successful.

Now, we can look at our album object and see the other properties that have been set by the database when we saved.

2.2.0 :005 > album
=> #<Album id: 1, name: "In Rainbows", year: nil, created_at: "2019-06-17 15:46:54", updated_at: "2019-06-17 15:46:54", genre: nil>

Note that the album now has two timestamps. One shows when it was created and the other shows when it was last updated. Currently, those two timestamps are the same because we haven't updated the album.

Create

We can perform the new and save events in a single action with the #create method:

  Album.create(name: "Giant Steps")

   (0.2ms)  BEGIN
  Album Create (0.3ms)  INSERT INTO "albums" ("name", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id"  [["name", "Giant Steps"], ["created_at", "2019-06-17 15:58:20.395727"], ["updated_at", "2019-06-17 15:58:20.395727"]]
   (0.7ms)  COMMIT
=> #<Album id: 2, name: "Giant Steps", year: nil, created_at: "2019-06-17 15:58:20", updated_at: "2019-06-17 15:58:20", genre: nil>

Notice that after the #create method, the new record is returned instead of a boolean value as we saw with #save. If we want the method to return a boolean, then we'll favor using the #save method. If we want to return the new row from the database, we'll use #create instead.

Using Bang Methods for Debugging

Let's try creating a new Song now. We'll just add a name property for now.

011:0> Song.create(name: "Reckoner")
   (0.2ms)  BEGIN
   (0.2ms)  ROLLBACK
=> #<Song id: nil, lyrics: nil, album_id: nil, created_at: nil, updated_at: nil, name: "Reckoner">

This time, something goes wrong. Instead of getting a COMMIT message, we get a ROLLBACK message instead. Our new Song hasn't been saved to the database.

Fortunately, the #create method can also be a bang method like this: #create!. We've learned that bang methods are usually destructive, but not in this case. #create! and #save! cause our application to throw an exception if the record isn't committed to the database.

Let's try again:

012:0> Song.create!(name: "Reckoner")
   (0.2ms)  BEGIN
   (0.2ms)  ROLLBACK
ActiveRecord::RecordInvalid: Validation failed: Album must exist
    from (irb):12

Now we have a clear error message: Validation failed: Album must exist. We'll cover validations more in a future lesson, including how to create custom validations. However, Rails 5 has a built-in validation for one-to-many relationships. By default, the "many" must always have an association with the "one." In this case, that means that an instance of Song must have an album_id. Let's try to create the record one more time:

013:0> album
=> #<Album id: 1, name: "In Rainbows", year: nil, created_at: "2019-06-17 15:46:54", updated_at: "2019-06-17 15:46:54", genre: nil>
014:0> Song.create!(name: "Reckoner", album_id: album.id)
   (0.2ms)  BEGIN
  Album Load (0.2ms)  SELECT  "albums".* FROM "albums" WHERE "albums"."id" = $1 LIMIT $2  [["id", 2], ["LIMIT", 1]]
  Song Create (32.3ms)  INSERT INTO "songs" ("album_id", "created_at", "updated_at", "name") VALUES ($1, $2, $3, $4) RETURNING "id"  [["album_id", 1], ["created_at", "2019-06-17 16:10:28.982136"], ["updated_at", "2019-06-17 16:10:28.982136"], ["name", "Reckoner"]]
   (0.6ms)  COMMIT
=> #<Song id: 1, lyrics: nil, album_id: 1, created_at: "2019-06-17 16:10:28", updated_at: "2019-06-17 16:10:28", name: "Reckoner">

First, we verify that album exists. (As long as you've been following along with the lesson and haven't exited the console, it should.) Then we #create! the Song yet again. We don't have to use the bang method, but it's useful here just in case we get another error. We specify that the value of album_id should be equal to album.id.

This time, the Song is successfully created.

These bang methods are great for debugging but should never be used in a production application. We don't want our application to throw an exception if a record isn't saved to the database. In a production environment, any rollback is probably due to user error and the attempt to commit the record should fail silently, as it does with the standard #create and #save methods.

Read

To see all of the records on the albums table, we can use the .all method:

015:0> Album.all
  Album Load (0.3ms)  SELECT  "albums".* FROM "albums" LIMIT $1  [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Album id: 1, name: "In Rainbows", year: nil, created_at: "2019-06-17 15:35:35", updated_at: "2019-06-17 15:35:35", genre: nil>, #<Album id: 2, name: "Giant Steps", year: nil, created_at: "2019-06-17 15:58:20", updated_at: "2019-06-17 15:58:20", genre: nil>]>

Once again, we see the actual SQL statement first: SELECT "lists".* FROM "lists". The query returns an <ActiveRecord::Relation> object. This is our ORM translating the result of the query to Ruby. This object is always an array, even if the query returns no results.

Other Active Record methods for reading records include: .where, .find, .first, and .last. Let's try them out:

020:0> Song.where(name: "Reckoner")
  Song Load (0.3ms)  SELECT  "songs".* FROM "songs" WHERE "songs"."name" = $1 LIMIT $2  [["name", "Reckoner"], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Song id: 1, lyrics: nil, album_id: 2, created_at: "2019-06-17 16:10:28", updated_at: "2019-06-17 16:10:28", name: "Reckoner">]>

021:0> Album.find(1)
  Album Load (0.3ms)  SELECT  "albums".* FROM "albums" WHERE "albums"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
=> #<Album id: 1, name: "In Rainbows", year: nil, created_at: "2019-06-17 15:35:35", updated_at: "2019-06-17 15:35:35", genre: nil>

022:0> Album.first
  Album Load (0.4ms)  SELECT  "albums".* FROM "albums" ORDER BY "albums"."id" ASC LIMIT $1  [["LIMIT", 1]]
=> #<Album id: 1, name: "In Rainbows", year: nil, created_at: "2019-06-17 15:35:35", updated_at: "2019-06-17 15:35:35", genre: nil>

023:0> Album.last
  Album Load (0.2ms)  SELECT  "albums".* FROM "albums" ORDER BY "albums"."id" DESC LIMIT $1  [["LIMIT", 1]]
=> #<Album id: 2, name: "Giant Steps", year: nil, created_at: "2019-06-17 15:58:20", updated_at: "2019-06-17 15:58:20", genre: nil>

All of these methods are useful and .find is probably the most commonly used. However, .where is the most powerful because it allows us to create more targeted queries. We'll cover advanced queries in a future lesson. All of the methods above return an instance of Album except for the .where method, which returns an <ActiveRecord::Relation> object.

Note that Active Record automatically creates methods for Album.songs and Song.album as well. For example, we could do the following:

Album.first.songs

Song.where(name: "Reckoner").first.album

Try them out in the console. For other querying options for reading records, check out the Rails Guides Active Record Query Interface documention.

Update and Destroy

The #update and #destroy methods can also be used in the Rails console to change and delete records. Let's update our first instance of Album to add a genre:

017:0> Album.first.update(genre: "Rock")
   (0.2ms)  BEGIN
  Album Update (0.4ms)  UPDATE "albums" SET "updated_at" = $1, "genre" = $2 WHERE "albums"."id" = $3  [["updated_at", "2019-06-17 16:55:25.323868"], ["genre", "Rock"], ["id", 1]]
   (0.7ms)  COMMIT
=> true

Note that we pass in key-value pairs for the properties we want to update. The method returns a boolean.

Now let's destroy this Album:

007:0> album = Album.first
  Album Load (0.3ms)  SELECT  "albums".* FROM "albums" ORDER BY "albums"."id" ASC LIMIT $1  [["LIMIT", 1]]
=> #<Album id: 1, name: "In Rainbows", year: nil, created_at: "2019-06-17 16:37:24", updated_at: "2019-06-17 16:37:24", genre: nil>

008:0> album.destroy
   (0.2ms)  BEGIN
  Song Load (0.3ms)  SELECT "songs".* FROM "songs" WHERE "songs"."album_id" = $1  [["album_id", 4]]
  Album Destroy (0.5ms)  DELETE FROM "albums" WHERE "albums"."id" = $1  [["id", 4]]
   (1.3ms)  COMMIT
=> #<Album id: 1, name: "In Rainbows", year: nil, created_at: "2019-06-17 16:37:24", updated_at: "2019-06-17 16:37:24", genre: nil>

We can also verify that the dependent instances of Song have also been destroyed: Song.where(name: "Reckoner").

Finally, there is a .destroy_all method that destroys all instances of a class. Use this method with caution!

To exit the console, type exit at the prompt.

In this lesson, we've practiced using Active Record's functionality in the Rails console. Think of the console as like a sandbox environment where we can try out different queries and debug. It's an extremely powerful too and can help us out at all stages of our application — from trying out associations to testing queries to debugging issues in our application.

Lesson 7 of 34
Last updated August 7, 2022