Reviewing a Book
Your bookstore is full of wonderful books. Although they have some great descriptions, your books deserve great advocates!
Your books need reviews. 😉
Before we can add reviews, we need a place to store them. Let’s add a new table to your database.
-
Open Terminal and go to your
bookstore
directory. -
Now, run
rails generate model review
.You might remember running this command when we created the books table.
rails generate model review
creates a bunch of files.invoke active_record create db/migrate/20170103220053_create_reviews.rb create app/models/review.rb invoke test_unit create test/models/review_test.rb create test/fixtures/reviews.yml
The first file it creates is a migration file. We’ve already created a few of these in your bookstore application.
$ ls -l db/migrate total 24 -rw-r--r-- 1 alimi staff 230 Dec 30 12:00 20161115030350_create_books.rb -rw-r--r-- 1 alimi staff 125 Dec 30 12:00 20161227020938_add_description_to_books.rb
Migration files always have file names that start with the timestamp of when they were created.
Let’s take a look at the migration that was generated by
rails generate model review
.
-
In your text editor, open the
CreateReviews
migration (db/migrations/TIMESTAMP_create_reviews.rb
). -
Take a look at the
change
method. It has acreate_table
block that will create thereviews
table.def change create_table :reviews do |t| t.timestamps end end
If you ran the migration as it is now, your
reviews
table wouldn’t have anything other than thecreated_at
andupdated_at
timestamps. We’ll need to change thecreate_table
block so your newreviews
table can store more review related data. 😄
1
2
3
4
5
6
7
8
class CreateReviews < ActiveRecord::Migration[5.0]
def change
create_table :reviews do |t|
t.timestamps
end
end
end
The reviews
table will need a column to store the text of a review. Let’s call that column body
, and let’s make it a text
column so you can leave long, inspiring reviews.
-
Using your other migrations as an example, update the
create_table
block of yourCreateReviews
migration to add atext
column calledbody
to yourreviews
table. -
When you’re done, save your changes, and run the migration by running
rake db:migrate
.If you make any mistakes, you can always undo the migration by running
rake db:rollback
. Then, you can make changes to yourCreateReviews
migration and re-run it withrake db:migrate
.
-
How did you update the migration?
You needed to add one line to the
create_table
block.t.text :body
-
If you didn’t already, add this line to your migration.
If you ran the migration without adding the
body
column, you’ll need to rollback and re-run the migration.
1
2
3
4
5
6
7
8
9
class CreateReviews < ActiveRecord::Migration[5.0]
def change
create_table :reviews do |t|
t.text :body
t.timestamps
end
end
end
Now that we have reviews, let’s play with them on the rails console
.
-
Go to Terminal and start the
rails console
. -
You now have a new object you can use:
Review
.Try running
Review.new
. It will return a new, emptyReview
.#<Review id: nil, body: nil, created_at: nil, updated_at: nil>
In addition to id, created_at, and updated_at, the new review also has a body.
-
Using the
Review
class, create two reviews.The same methods you used to create books can be used to create reviews.
When you’re done
Review.count
should return 2.
-
Did you create two reviews? How did you do it?
Maybe you created a review by using
create
.Review.create(body: "Wow, what a great book!")
Or maybe you started with a
new
review and calledsave
on it.a_new_review = Review.new a_new_review.body = "10/10 would recommend" a_new_review.save
You might’ve even come up with something totally different!
-
If you get 2 when you run
Review.count
, yay!If not, use either of the methods we described to create two reviews.
Associating Reviews to Books
Now you have a couple of reviews, but doesn’t it feel like we’re missing something?
How are these reviews related to books?
They’re not 😅
Fortunately, Rails gives us a few tools to associate reviews to books.
In this case, we can say a review should only belong to one book. This is called a belongs_to
relationship.
Let’s define the belongs_to
relationship.
-
In your text editor, open
app/models/review.rb
. -
Inside the
Review
class, add the following line.belongs_to :book
With this change, we’re setting up your application so a
Review
belongs_to
aBook
. -
Save your changes.
1
2
3
class Review < ApplicationRecord
belongs_to :book
end
-
Go back to the
rails console
and runreload!
to pick up your changes. -
Now, get your first review from the database and assign it to a variable called
my_first_review
.my_first_review = Review.first
-
Let’s try to get
my_first_review
’s book.Run
my_first_review.book
.That returned nil because
my_first_review
was never associated with a book. -
Let’s try assigning your first book to your first review.
Run the following:
my_first_review.book = Book.first
That returned an error, didn’t it? 🙃
-
It might be hard to see, but you actually got a pretty useful error…
ActiveModel::MissingAttributeError: can't write unknown attribute `book_id`
We’ve defined the
belongs_to
relationship between review and book, but we need a way to save that relationship in your database. To do that, we’ll need to add abook_id
column to yourreviews
table. -
Let’s make that change to the
reviews
table, but before you move on exit therails console
.
To add a book_id
column to the reviews
table, we’ll need a new migration.
Remember, editing an old migration would mean rolling back that migration and we don’t want to do that because that would delete the data in the database.
-
In Terminal, run
rails generate migration add_book_id_to_reviews
.This will generate a new timestamped migration called
AddBookIdToReviews
. -
Do you remember when you added
description
to thebooks
table?class AddDescriptionToBooks < ActiveRecord::Migration[5.0] def change add_column :books, :description, :text end end
We’ll want to do something similiar to add the
book_id
column to thereviews
table. -
Open the
AddBookIdToReviews
migration in your text editor (db/migrate/TIMESTAMP_add_book_id_to_reviews.rb
). -
Using the
AddDescriptionToBooks
migration as an example, update theAddBookIdToReviews
migration so abook_id
column is added to thereviews
table. Thebook_id
column should be aninteger
column.
-
What does your solution look like?
Inside the
change
method, you should have the following line:add_column :reviews, :book_id, :integer
This will add a
book_id
column to thereviews
table, and the new column will be an integer column. -
Update your solution to match this solution and save your changes.
1
2
3
4
5
class AddBookIdToReviews < ActiveRecord::Migration[5.0]
def change
add_column :reviews, :book_id, :integer
end
end
-
Now go back to Terminal and run your migration!
rake db:migrate
-
If you pay close attention to the output, you can see the
book_id
column getting added to thereviews
table.
Now that the reviews
table has a book_id
column, you can assign books to reviews. Let’s try it!
-
Open
rails console
and run the following code to assign your first book to your first review.my_first_review = Review.first my_first_review.book = Book.first my_first_review.save
-
Now, try running
my_first_review.book
.Yay! 🎉
Your first review is now associated with your first book.
If you look at
my_first_review
, you’ll notice itsbook_id
is 1 - your first book’s id. -
Go ahead and assign your second review to your first book. While you’re at it, also create another review for your first book.
When you’re done,
Review.count
should return 3.
A Book Has Many Reviews
Your first book now has multiple reviews.
We can find which review a book belongs to, but we can’t find all the reviews a book has.
Or can we???
Since a book can have many reviews, we can define a has_many
relationship between books and reviews.
-
In your text editor, open
app/models/book.rb
, and add the followling line inside the classhas_many :reviews
This will set up your application so a
Book
has_many
Review
s.In addition, we won’t have to make any database changes because the
reviews
table already has abook_id
column. The relationship is already defined in the database. -
Save your changes.
1
2
3
class Book < ApplicationRecord
has_many :reviews
end
-
Now, go back to the
rails console
and runreload!
to get you newest changes. -
Get your first book and assign it to
my_first_book
. -
Now, try running
my_first_book.reviews
.It might be hard to see, but all your book reviews are there!
-
Don’t believe me??
Try running
my_first_book.reviews.count
. It should return 3. -
Spend some time playing around with this relationship. You can do things like
my_first_book.reviews.first
ormy_first_book.reviews.pluck(:body)
.To find a complete list of all the things you can do, take a look at the documentation here.
-
When your done exploring, exit the
rails console
.
The magic that is a Relational Database!
Are you starting to see how we’re using Rails to build a relational database?
So far, you’ve built two tables: a books
table and a reviews
table. Those tables are connected to one another by the book_id
column on the Reviews table. That column is making it possible for books to have many reviews in our database.