Creating the First Table in Your Database

  1. Open Terminal.

  2. If you still have Terminal open from the previous chapter, you should be in the bookstore directory.

    Do you remember how to check what directory you’re in?

    (pwd)

  3. If you’re not already in the bookstore directory, run the following commands to get there:

    cd Projects
    cd bookstore
    
  4. Now run

    rails generate model book
    

    This generated a few new files, but there are only a couple we’re interesed in.

  5. Run ls -l db/migrate to take a look at one of the new files. You should see a file named something like 20161115030350_create_books.rb.

    20161115030350 is a timestamp generated when the file is created. Your file will start with a more recent timestamp…I hope 😝

    Let’s take a look at this file in your text editor.

  $ pwd
  /Users/awesomesauce/Projects/bookstore

  $ rails generate model book
        invoke  active_record
        create    db/migrate/20161115030350_create_books.rb
        create    app/models/book.rb
        invoke    test_unit
        create      test/models/book_test.rb
        create      test/fixtures/books.yml

  $ ls -l db/migrate
  total 8
  -rw-r--r--  1 awesomesauce  staff  131 Nov 14 22:03 20161115030350_create_books.rb
  1. Open db/migrate/YOUR_TIMESTAMP_create_books.rb in your text editor.

    This is a migration file called CreateBooks. Migrations are used to make changes to your database.

    On line 3 of the CreateBooks migration, there’s a block called create_table. Inside this block, you’ll make changes to add columns to the new books table.

1
2
3
4
5
6
7
8
  class CreateBooks < ActiveRecord::Migration[5.0]
    def change
      create_table :books do |t|

        t.timestamps
      end
    end
  end

You might remember blocks from the Ruby in 100 Minutes tutorial.

Like most blocks, the create_table block starts with the do keyword and ends with the end keyword.

The books table will need a few columns.

It will need a string column to store book titles and another string column to store book authors.

The table will also need a column to store book prices. Since keeping track of money can be tricky, the column will need to be an integer column where book prices can be stored in cents. It sounds weird, but you’ll have to trust me on this one.

  1. Inside the create_table block, add the following lines:

    t.string :title
    t.string :author
    t.integer :price_cents
    
  2. Save your changes to the CreateBooks migration and go back to Terminal.

1
2
3
4
5
6
7
8
9
10
  class CreateBooks < ActiveRecord::Migration[5.0]
    def change
      create_table :books do |t|
        t.string :title
        t.string :author
        t.integer :price_cents
        t.timestamps
      end
    end
  end

What’s t.timestamps?

You added a few things to the create_table block, but you might’ve noticed that there was already some code in there:

create_table :books do |t|

  t.timestamps
end

What’s t.timestamps?

It’s a convinence method added by Rails that will add two more columns to the books table: created_at and updated_at. They’ll be used to store the times when books are created and updated.

  1. In Terminal, make sure you’re in the bookstore directory.

  2. Run rake db:migrate.

  3. Yay! You’ve just run your first migration!

  $ pwd
  /Users/awesomesauce/Projects/bookstore

  $ rake db:migrate
  == 20161115030350 CreateBooks: migrating ======================================
  -- create_table(:books)
     -> 0.0030s
  == 20161115030350 CreateBooks: migrated (0.0034s) =============================

What just happened?

This migration just added a table to your database!

One of the advantages of using a framework like Rails is that you can build and modify a database without having to use raw SQL.

The migration was just one of the files that was generated by rails generate model book. It also generated another file: app/models/book.rb. Let’s take a quick look at it.

  1. Open app/models/book.rb in your text editor.

    It might not look very exciting, but it’s actually pretty powerful. We now have a Book class. The Book class is used to represent individual rows in the books table.

    Still not impressed? Let’s see what you can do with this class.

1
2
  class Book < ApplicationRecord
  end

You might remember classes from the Ruby in 100 minutes tutorial.

Classes are used to describe things. For example, the Book class in your bookstore is used to describe books.

  1. Open Terminal and make sure you’re in the bookstore directory.

  2. Run rails console. This will open…the rails console.

    The rails console is available in all Rails applications. It let’s you play around with the different things in your applications including the data stored in your database.

    Now that you’re in the rails console, let’s see what we can do with the Book class.

  $ pwd
  /Users/awesomesauce/Projects/bookstore

  $ rails console
  Loading development environment (Rails 5.0.0.1)
  >>
  1. Let’s try creating my favorite book.

    In the rails console, run the following code:

    my_favorite_book = Book.new
    

    This assigns a new instance of Book to a variable so that we can refer to it as my_favorite_book.

  2. Now, to give this new instance of book a title run:

    my_favorite_book.title = "why's (poignant) Guide to Ruby"
    
  3. my_favorite_book now has a title.

    Don’t believe me?! Try running my_favorite_book.title. It should return “why’s (poignant) Guide to Ruby”.

  >> my_favorite_book = Book.new
  => #<Book id: nil, title: nil, author: nil, price_cents: nil, created_at: nil, updated_at: nil>

  >> my_favorite_book.title = "why's (poignant) Guide to Ruby"
  => "why's (poignant) Guide to Ruby"

  >> my_favorite_book.title
  => "why's (poignant) Guide to Ruby"

Remember those other columns we added to the books table? We can set those on my_favorite_book.

  1. Try setting my_favorite_book’s author to “why the lucky stiff”.

  2. Although my_favorite_book is priceless, you can go ahead and give it a price. Remember, we named this column price_cents.

  3. We should probably keep track of the number of copies we have of my favorite book. Run the following code to set the quantity:

    my_favorite_book.quantity = 500
    

    Did that work? No?!

    Welcome to your first error!

    Not sure if happy or sad

    These errors can seem intimidating at first, but they can be suprisingly helpful as you work your way through building an application.

    So let’s embrace and explore these errors together!

  >> my_favorite_book.author = "why the lucky stiff"
  => "why the lucky stiff"

  >> my_favorite_book.price_cents = 100
  => 100

  >> my_favorite_book.quantity = 500
  NoMethodError: undefined method `quantity=' for #<Book:0x007fdc9453bf60>
      from /Users/awesomesauce/.rvm/gems/ruby-2.3.1/gems/activemodel-5.0.0.1/lib/active_model/attribute_methods.rb:433:in `method_missing'
      from (irb):11
      from /Users/awesomesauce/.rvm/gems/ruby-2.3.1/gems/railties-5.0.0.1/lib/rails/commands/console.rb:65:in `start'
      from /Users/awesomesauce/.rvm/gems/ruby-2.3.1/gems/railties-5.0.0.1/lib/rails/commands/console_helper.rb:9:in `start'
      from /Users/awesomesauce/.rvm/gems/ruby-2.3.1/gems/railties-5.0.0.1/lib/rails/commands/commands_tasks.rb:78:in `console'
      from /Users/awesomesauce/.rvm/gems/ruby-2.3.1/gems/railties-5.0.0.1/lib/rails/commands/commands_tasks.rb:49:in `run_command!'
      from /Users/awesomesauce/.rvm/gems/ruby-2.3.1/gems/railties-5.0.0.1/lib/rails/commands.rb:18:in `<top (required)>'
      from bin/rails:4:in `require'
      from bin/rails:4:in `<main>'

Your first error

We tried setting quantity on my_favorite_book, but we got an error:

>> my_favorite_book.quantity = 500
NoMethodError: undefined method `quantity=' for #<Book:0x007fdc9453bf60>

We get a NoMethodError for quantity= because quantity isn’t a attribute we can set on Book.

If you remember the CreateBooks migration, we added a few columns, but we never added a column for quantity.

class CreateBooks < ActiveRecord::Migration[5.0]
  def change
    create_table :books do |t|
      t.string :title
      t.string :author
      t.integer :price_cents
      t.timestamps
    end
  end
end

Your database is currently in a bad state. The books table needs a quantity column to store the number of available books.

Fortunately, we have a few ways to fix this.

Migrations are designed to run in two directions. So far, we’ve run the CreateBooks migration “up” to add the books table.

Now, we’re going to run the CreateBooks migration “down”. By running the migration “down”, the books table will be removed. With the table no longer in the database, we can make changes to the migration and re-run it so the books table has the quantity column.

  1. First, exit the rails console by running exit.

  2. Then, run the CreateBooks migration down by running rake db:rollback.

  >> exit

  $ rake db:rollback
  == 20161115030350 CreateBooks: reverting ======================================
  -- drop_table(:books)
     -> 0.0098s
  == 20161115030350 CreateBooks: reverted (0.0143s) =============================
  1. Go back to your text editor and open the CreateBooks migration.

  2. Inside the create_table block, add the following line:

    t.integer :quantity
    
  3. Save your changes to the CreateBooks migration.

1
2
3
4
5
6
7
8
9
10
11
  class CreateBooks < ActiveRecord::Migration[5.0]
    def change
      create_table :books do |t|
        t.string :title
        t.string :author
        t.integer :price_cents
        t.timestamps
        t.integer :quantity
      end
    end
  end
  1. Go back to Terminal.

  2. Re-run the migration by running rake db:migrate.

  $ rake db:migrate
  == 20161115030350 CreateBooks: migrating ======================================
  -- create_table(:books)
     -> 0.0415s
  == 20161115030350 CreateBooks: migrated (0.0416s) =============================

Now that your books table has the quantity column, you can go back to adding my favorite book 😉

  1. Enter the rails console by running…rails console.

  2. You already forgot what my favorite book was, didn’t you?

    No worries. You can run each of the following lines of code in your console to refresh your memory:

    my_favorite_book = Book.new
    my_favorite_book.title = "why's (poignant) Guide to Ruby"
    my_favorite_book.author = "why the lucky stiff"
    my_favorite_book.price_cents = 100
    
  3. Ok, now that you have my favorite book again try adding the quantity:

    my_favorite_book.quantity = 500
    
  4. Alright, my favorite book is coming along nicely. I think we’re ready to save it to your database.

    Run my_favorite_book.save to save my favorite book to your database.

    If the last thing you see says true, my favorite books has been saved to your database. Yay!

  >> my_favorite_book = Book.new
  => #<Book id: nil, title: nil, author: nil, price_cents: nil, created_at: nil, updated_at: nil, quantity: nil>

  >> my_favorite_book.title = "why's (poignant) Guide to Ruby"
  => "why's (poignant) Guide to Ruby"

  >> my_favorite_book.author = "why the lucky stiff"
  => "why the lucky stiff"

  >> my_favorite_book.price_cents = 100
  => 100

  >> my_favorite_book.quantity = 500
  => 500

  >> my_favorite_book.save
     (0.1ms)  begin transaction
    SQL (0.8ms)  INSERT INTO "books" ("title", "author", "price_cents", "created_at", "updated_at", "quantity") VALUES (?, ?, ?, ?, ?, ?)  [["title", "why's (poignant) Guide to Ruby"], ["author", "why the lucky stiff"], ["price_cents", 100], ["created_at", 2016-11-24 03:30:53 UTC], ["updated_at", 2016-11-24 03:30:53 UTC], ["quantity", 500]]
     (2.3ms)  commit transaction
  => true

What if I told you there was more than one way to add a book to your database?

O rly?!

  1. Let’s try using the create method to add my second favorite book to your database.

    Run the following code on the rails console:

    Book.create(title: "Oh, the Places You'll Go!", author: "Dr. Seuss", price_cents: 500, quantity: 200)
    

    The create method lets us save data in a single command. Instead of setitng attributes one at a time, we can set them all at once.

  >> Book.create(title: "Oh, the Places You'll Go!", author: "Dr. Seuss", price_cents: 500, quantity: 200)
     (0.1ms)  begin transaction
    SQL (0.8ms)  INSERT INTO "books" ("title", "author", "price_cents", "created_at", "updated_at", "quantity") VALUES (?, ?, ?, ?, ?, ?)  [["title", "Oh, the Places You'll Go!"], ["author", "Dr. Seuss"], ["price_cents", 500], ["created_at", 2016-11-24 03:33:22 UTC], ["updated_at", 2016-11-24 03:33:22 UTC], ["quantity", 200]]
     (3.7ms)  commit transaction
  => #<Book id: 2, title: "Oh, the Places You'll Go!", author: "Dr. Seuss", price_cents: 500, created_at: "2016-11-24 03:33:22", updated_at: "2016-11-24 03:33:22", quantity: 200>

Now that we’ve added some books to your database, let’s try pulling them out of the database.

  1. My second favorite book was the last book you added to your database. To get it, run Book.last on the rails console.

  2. My favorite book was the first book you added. Can you guess how we would get it?

    Run Book.first to get the first book in your database.

  3. What if you wanted to see all the books in your database? How would you do that??

    Run Book.all to get all the books in your database.

    Programming can be really confusing, but every once in a while it kinda makes sense 😊

  >> Book.last
    Book Load (0.2ms)  SELECT  "books".* FROM "books" ORDER BY "books"."id" DESC LIMIT ?  [["LIMIT", 1]]
  => #<Book id: 2, title: "Oh, the Places You'll Go!", author: "Dr. Seuss", price_cents: 500, created_at: "2016-11-24 03:33:22", updated_at: "2016-11-24 03:33:22", quantity: 200>

  >> Book.first
    Book Load (0.2ms)  SELECT  "books".* FROM "books" ORDER BY "books"."id" ASC LIMIT ?  [["LIMIT", 1]]
  => #<Book id: 1, title: "why's (poignant) Guide to Ruby", author: "why the lucky stiff", price_cents: 100, created_at: "2016-11-24 03:30:53", updated_at: "2016-11-24 03:30:53", quantity: 500>

  >> Book.all
    Book Load (0.2ms)  SELECT "books".* FROM "books"
  => #<ActiveRecord::Relation [#<Book id: 1, title: "why's (poignant) Guide to Ruby", author: "why the lucky stiff", price_cents: 100, created_at: "2016-11-24 03:30:53", updated_at: "2016-11-24 03:30:53", quantity: 500>, #<Book id: 2, title: "Oh, the Places You'll Go!", author: "Dr. Seuss", price_cents: 500, created_at: "2016-11-24 03:33:22", updated_at: "2016-11-24 03:33:22", quantity: 200>]>

As happy as I am to see some of my favorite books in your bookstore, I’m sure you have some you’d like to add as well.

  1. Add five more books to your bookstore.

  2. When you’re done, run Book.count to get the total number of books in your database. It should return 7.

  3. After you’ve added five more books and verified that they are saved to your database, exit the rails console by running exit.