Rails One-to-Many "Products and Reviews" Lab

screenshot




A product has many reviews, a review belongs to a product

Back End

  • Make Rails server work with resource for Products
  • Make Reviews resource
  • Edit Reviews routes as nested resources
  • Make foreign keys and associations
  • Routing - use only and except to filter the routes
  • Change the controllers to reflect the changes we have made



Front End

  • Make a React frontend that consumes the API
  • The user should be able to see all the product images and click on them
  • Each product show page should display its associated reviews
  • A user of the app should be able to add a review to a product
  • A user of the app should be able to update and delete any reviews (chaos)



Start Here

MAKE RAILS API

Make a Rails API app called products_reviews_app_api.

Full Command
rails new products_reviews_app_api --api -d postgresql --skip-git



Scaffold Products

screenshot




Create and Migrate DB

Terminal Commands

screenshot screenshot




Seed DB

Put this seed data straight in to the seeds.rb file:

Product.create([
  { name: "Car", price: 20000, img: "http://www.gowithgo.net/wp-content/uploads/2011/07/Flintstone_Mobile-150x150.jpg"},
  { name: "Cat", price: 100, img: "http://animagehub.com/wp-content/uploads/2016/10/Pink-panther-vector-5-150x150.jpg"},
  { name: "Crab", price: 2, img: "http://scontent.cdninstagram.com/t51.2885-19/s150x150/13402342_1111471978911960_1380878568_a.jpg"},
  { name: "Crib", price: 200, img: "https://s-media-cache-ak0.pinimg.com/originals/99/29/ee/9929eef9086e07bd7e50102bc37ff3a8.jpg"},
  { name: "Coat", price: 200, img: "http://cooljunkyouneed.com/4548/uploads/2014/02/Workaholics-Bear-Coat-150x150.jpg"},
  { name: "Cake", price: 3, img: "http://scontent.cdninstagram.com/t51.2885-19/s150x150/11356601_447610668772561_439752401_a.jpg"},
  { name: "Concussion", price: 0, img: "http://youngmenshealthsite.org/wp-content/uploads/2015/05/concussion1-150x150.jpg"},
  { name: "Coal", price: 1, img: "http://www.whitecatpublications.com/wp-content/uploads/2010/12/lump-of-coal-150x150.jpg"},
  { name: "Cyclone", price: 70000, img: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cyclone_Mala.JPG/150px-Cyclone_Mala.JPG"},
  { name: "Career", price: 13500, img: "http://waterfordwhispersnews.com/wp-content/uploads/2014/10/happy-worker-e1412334561186-150x150.jpg"},
  { name: "Cillian Murphy", price: 400, img: "http://static.buzznet.com/uploads/2012/03/msg-133176055505-150x150.jpg"},
  { name: "Climate Change", price: 9, img: "http://scitechdaily.com/images/Detailed-Global-Climate-Change-Projections-150x150.jpg" }
]);
Run the seed file

screenshot


🔴  Commit your work
The commit message should read:
"Commit 1: Rails API for Products complete".




Scaffold Reviews

Unfortunately there is no official way to do a scaffold with nested resources and associations. There is a gem--Nested scaffold gem--which you may want to use for Project 4. But what we will do is scaffold as usual and then make tweaks to our code. That way we will be able to operate on our code a bit and see how it works.

screenshot

rails routes gives us the routes below: but we only want reviews to be accessed in relation to a product. That is, we first get a product by its id, then get the relevant review that belongs to that product. We don't really want free-floating CRUDdable reviews.

screenshot




Nested Resources

So let's change our resources. Nest reviews inside of products:

config/routes.rb

screenshot

When we run rails routes, we should see:

screenshot

We can see that, in our reviews controller, the first param will be called :product_id and the second param will be called :id. We can only ever create or alter a review in relation to a product. This is the restriction we have chosen! No free-floating reviews!



🔴  Commit your work
The commit message should read:
"Commit 2: Reviews nested routes complete".



Foreign Key

General good practice is to add the foreign key with a migration rather than up-front in the scaffold or generator or original migration or what-have-you, to prevent running things in the wrong order or getting one and many sides confused.

screenshot

screenshot

Open Rails console and test the association.

  • rails c
  • Product.find(1).reviews
  • 👾 Glitch!

Look at that horrible error. At the top is this:

screenshot

NoMethodError for 'reviews'. That means we must first add the relations to our models:

Model Code

screenshot

screenshot

Try again:

  • Product.find(1).reviews

Zig-a-zig ahhh:

screenshot

It works. There are no reviews yet, but it works.



🔴  Commit your work
The commit message should read:
"Commit 3: Products and Reviews associated".

Give Reviews to a Product

Product.find(1).reviews.create(title: "first review", content: "not a bad product", author: "Terminator 2")
Product.find(1).reviews.create(title: "second review", title: "middling product I'd say", author: "Chingy Right Thurr")
  • See Product 1's many Reviews: Product.find(1).reviews
  • See the Reviews that belong to Product 1: Review.where(product_id: 1)


🔴  Commit your work
The commit message should read:
"Commit 4: Products and Reviews associations tested and working".

Routes - Only & Accept

Products: Currently, we have more routes than we intend or need. We want the users of our API only to be able to see an index and show of our Products, and not to alter the db.

Routes: We will want all the routes for the reviews except for the show route. (We won't really ever need to see a review in isolation).




Product routes: only

Tell the router that for Products we only want index and show, and leave out the others:

screenshot

rails routes now has fewer routes:

screenshot

Review routes: except

Tell the router that for Reviews we want everything except show:

screenshot

rails routes now has even fewer routes:

screenshot



🔴  Commit your work
The commit message should read:
"Commit 5: Routes trimmed with only and except".



Controllers - Products

In the products controller, we'll need to make some changes given that we have changed the routing.

  • Delete the methods that alter data: create, update, destroy, and the product_params, and before action helper.
  • Change the show action to .find the Product
  • Add status codes (either render status codes or send them through as json)
Complete Products controller with rendered status codes

screenshot



🔴  Commit your work
The commit message should read:
"Commit 6: Products controller fixed".

Controllers - Reviews

  • Delete the show method, and the show argument to before_action.

Remember, since our Reviews routes are nested inside Products like below...

/products/:product_id/reviews

... we will have to adjust each of our methods to reflect the params hash.

If we run rails routes, remember, the URI for our reviews always has a param for a product id.

In each of our routes, we will have to query for our reviews by whatever number is stored in that param.




Reviews Index

We have a number coming through in :product_id, so we can do the following:

  • Since we are in the Reviews controller, let's find Reviews instead of Products
  • Since there is a param called :product_id let's match it to a column called product_id.

Code:

  def index
    reviews = Review.where(product_id: params[:product_id])

    render json: reviews
  end

Check out the reviews for Product 1 in the browser, /products/1/reviews:

screenshot


🔴  Commit your work
The commit message should read:
"Commit 7: Reviews index method fixed".



Reviews Create

Create the review as usual.

We can set its product_id column to the :product_id param from the params hash. Et voilà.

Remove location: @review because we don't need or want a URL redirect.

Create Method Code

screenshot

Test Review Create with Postman:

screenshot


🔴  Commit your work
The commit message should read:
"Commit 7: Reviews create method fixed".



Reviews Update and Destroy

The cool things is we don't need a product_id to reference which review to update or destroy. We can just update it or destroy it by id.

We would need the productid in the update route only if we were going to _change which product the review belongs to. We are not going to do that . . .

In our routes, we can remove update and destroy from the nested routes, and give them their own domain:

screenshot

This will remove :update and :destroy from nested resources.

Reviews resources gets :update and :destroy on its own.

rails routes gives us some modified routes:

screenshot

Since un-nested is actually the default behavior from a Rails scaffold, we don't need to alter either the update or delete methods in the Reviews controller.

Test your routes with Postman


🔴  Commit your work
The commit message should read:
"Commit 8: Routes changed for Reviews update and delete".



Hungry for More?

Configure CORS

Make it so a frontend app running on a local port can access your API


🔴  Commit your work
The commit message should read:
"Commit 9: Configured CORS".



Front End

Make it so an Express project using some kind of frontend framework or library, which could be...

  • jQuery
  • React
  • Angular, Vue, or a framework you're using for your lightning talk or final project

...can make an AJAX request to the Products index.


🔴  Commit your work
The commit message should read:
"Commit 10: AJAX request to Products Index".



Display Product images and reviews

Make it so your frontend app will display all the Product images.

When the user clicks on an image, they will be shown all the Reviews associated with that Product. You will need to make an AJAX request to the reviews index.


🔴  Commit your work
The commit message should read:
"Commit 11: AJAX request to Reviews Index".



Write a Review

Make it so a user of your frontend app can write a review of a particular Product


🔴  Commit your work
The commit message should read:
"Commit 12: AJAX request to Reviews Create".



Update a Review

Make it so a user of your frontend app can edit a review of a particular Product


🔴  Commit your work
The commit message should read:
"Commit 13: AJAX request to Reviews Update".



Remove a Review

Make it so a user of your frontend app can remove a review of a particular Product.


🔴  Commit your work
The commit message should read:
"Commit 14: AJAX request to Reviews Destroy".



Hungry for even more?

Make an 'admin' app

The admin app should be able to create, edit, and delete Products from the database. On the Rails server, you will need to:

  • Configure CORS to allow only the admin app db privileges for Products
  • Alter the routes and controller for Products

🔴  Commit your work
The commit message should read:
"Commit 15: The Void".