[Solutions]
The Simplified Go Food
Web App - Iteration 2
Fixing Cart Model Spec (1)
To ensure consistent test results, apply this fix to your Cart model spec:
describe Cart do
# -cut-
context "with existing line_item with the same food" do
before :each do
@cart = create(:cart)
@food = create(:food)
@line_item = create(:line_item, food: @food, cart: @cart)
end
it "does not save the new line_item in the database" do
expect { @cart.add_food(@food).save }.not_to change(LineItem, :count)
end
it "increments the quantity of line_item with the same food" do
expect { @cart.add_food(@food).save }.to change {
@cart.line_items.find_by(food_id: @food.id).quantity
}.by(1)
end
end
# -cut-
end
Fixing Cart Model Spec (2)
To ensure consistent test results, apply this fix to your Cart model spec:
describe Cart do
# -cut-
context "without existing line_item with the same food" do
it "saves the new line_item in the database" do
cart = create(:cart)
food = create(:food)
expect { cart.add_food(food).save }.to change(LineItem, :count).by(1)
end
end
# -cut-
end
Fixing LineItems Controller Spec (1)
To ensure consistent test results, apply this fix to your LineItems controller spec:
describe LineItemsController do
describe 'POST #create' do
# -cut
context "with existing line_item with the same food" do
before :each do
@cart = create(:cart)
session[:cart_id] = @cart.id
line_item = create(:line_item, food: @food, cart: @cart)
end
it "does not save the new line_item in the database" do
expect{
post :create, params: { food_id: @food.id }
}.not_to change(LineItem, :count)
end
it "increments the quantity of line_item with the same food" do
expect {
post :create, params: { food_id: @food.id }
}.to change {
@cart.line_items.find_by(food_id: @food.id).quantity
}.by(1)
end
end
# -cut-
end
end
Fixing LineItems Controller Spec (2)
To ensure consistent test results, apply this fix to your LineItems controller spec:
describe LineItemsController do
describe 'POST #create' do
# -cut
context "without existing line_item with the same food" do
it "saves the new line_item in the database" do
expect{
post :create, params: { food_id: @food.id }
}.to change(LineItem, :count).by(1)
end
end
# -cut-
end
end
Spec for LineItem's Total Price
This is how LineItem's total_price spec should look like.
describe LineItem do
# -cut-
it "can calculate total_price" do
cart = create(:cart)
food = create(:food, price: 10000.0)
line_item = create(:line_item, quantity: 3, food: food, cart: cart)
expect(line_item.total_price).to eq(30000.0)
end
end
Spec for Cart's Total Price
This is how Cart's total_price spec should look like.
describe Cart do
# -cut-
it "can calculate total_price" do
cart = create(:cart)
food1 = create(:food, name: "Food 1", price: 10000.0)
food2 = create(:food, name: "Food 2", price: 15000.0)
line_item1 = create(:line_item, quantity: 3, food: food1, cart: cart)
line_item2 = create(:line_item, quantity: 1, food: food2, cart: cart)
expect(cart.total_price).to eq(45000.0)
end
end
Spec for Category Model
This is how Category model spec should look like.
# -cut-
describe Category do
it "has a valid factory" do
expect(build(:category)).to be_valid
end
it "is valid with a name" do
expect(build(:category)).to be_valid
end
it "is invalid without a name" do
category = build(:category, name: nil)
category.valid?
expect(category.errors[:name]).to include("can't be blank")
end
it "is invalid with a duplicate name" do
category1 = create(:category, name: "Dessert")
category2 = build(:category, name: "Dessert")
category2.valid?
expect(category2.errors[:name]).to include("has already been taken")
end
end
Factory for Category
This is how Category factory should look like.
FactoryGirl.define do
factory :category do
name { Faker::Dessert.variety }
# This could be any faker, though
end
factory :invalid_category, parent: :category do
name nil
end
end
Factory for Food
This is how Food factory should look like.
FactoryGirl.define do
factory :food do
name { Faker::Food.dish }
description { Faker::Food.ingredient }
image_url "Food.jpg"
price 10000.0
association :category
end
# -cut-
end
Spec for Categories Controller (1)
This is how Categories controller spec should look like.
require 'rails_helper'
describe CategoriesController do
describe 'GET #index' do
it "populates an array of all categories" do
dessert = create(:category, name: "Dessert")
main_course = create(:category, name: "Main Course")
get :index
expect(assigns(:categories)).to match_array([dessert, main_course])
end
it "renders the :index template" do
get :index
expect(response).to render_template :index
end
end
# -cut-
end
Spec for Categories Controller (2)
This is how Categories controller spec should look like.
require 'rails_helper'
describe CategoriesController do
# -cut-
describe 'GET #show' do
it "assigns the requested category to @category" do
category = create(:category)
get :show, params: { id: category }
expect(assigns(:category)).to eq category
end
it "populates a list of all foods in the category" do
category = create(:category)
food1 = create(:food, category: category)
food2 = create(:food, category: category)
get :show, params: { id: category }
expect(assigns(:category).foods).to match_array([food1, food2])
end
it "renders the :show template" do
category = create(:category)
get :show, params: { id: category }
expect(response).to render_template :show
end
end
# -cut-
end
Spec for Categories Controller (3)
This is how Categories controller spec should look like.
require 'rails_helper'
describe CategoriesController do
# -cut-
describe 'GET #new' do
it "assigns a new Category to @category" do
get :new
expect(assigns(:category)).to be_a_new(Category)
end
it "renders the :new template" do
get :new
expect(:response).to render_template :new
end
end
# -cut-
end
Spec for Categories Controller (4)
This is how Categories controller spec should look like.
require 'rails_helper'
describe CategoriesController do
# -cut-
describe 'GET #edit' do
it "assigns the requested category to @category" do
category = create(:category)
get :edit, params: { id: category }
expect(assigns(:category)).to eq category
end
it "renders the :edit template" do
category = create(:category)
get :edit, params: { id: category }
expect(response).to render_template :edit
end
end
# -cut-
end
Spec for Categories Controller (5)
# -cut-
describe CategoriesController do
# -cut-
describe 'POST #create' do
context "with valid attributes" do
it "saves the new category in the database" do
expect{
post :create, params: { category: attributes_for(:category) }
}.to change(Category, :count).by(1)
end
it "redirects to categories#show" do
post :create, params: { category: attributes_for(:category) }
expect(response).to redirect_to(category_path(assigns[:category]))
end
end
context "with invalid attributes" do
it "does not save the new category in the database" do
expect{
post :create, params: { category: attributes_for(:invalid_category) }
}.not_to change(Category, :count)
end
it "re-renders the :new template" do
post :create, params: { category: attributes_for(:invalid_category) }
expect(response).to render_template :new
end
end
end
# -cut-
end
Spec for Categories Controller (6)
# -cut-
describe CategoriesController do
# -cut-
describe 'PATCH #update' do
before :each do
@category = create(:category)
end
context "with valid attributes" do
it "locates the requested @category" do
patch :update, params: { id: @category, category: attributes_for(:category) }
expect(assigns(:category)).to eq @category
end
it "changes @category's attributes" do
patch :update, params: { id: @category, category: attributes_for(:category, name: 'Dessert') }
@category.reload
expect(@category.name).to eq('Dessert')
end
it "redirects to the category" do
patch :update, params: { id: @category, category: attributes_for(:category) }
expect(response).to redirect_to @category
end
end
# -cut-
end
# -cut-
end
Spec for Categories Controller (7)
# -cut-
describe CategoriesController do
# -cut-
describe 'PATCH #update' do
# -cut-
context "with invalid attributes" do
it "does not update the category in the database" do
patch :update, params: { id: @category, category: attributes_for(:category, name: nil) }
@category.reload
expect(@category.name).not_to eq(nil)
end
it "re-renders the :edit template" do
patch :update, params: { id: @category, category: attributes_for(:invalid_category) }
expect(response).to render_template :edit
end
end
end
# -cut-
end
Spec for Categories Controller (8)
# -cut-
describe CategoriesController do
# -cut-
describe 'DELETE #destroy' do
before :each do
@category = create(:category)
end
context "with associated foods" do
it "does not delete the category from the database" do
food = create(:food, category: @category)
expect{
delete :destroy, params: { id: @category }
}.not_to change(Category, :count)
end
end
context "without associated foods" do
it "deletes the category from the database" do
expect{
delete :destroy, params: { id: @category }
}.to change(Category, :count).by(-1)
end
it "redirects to categories#index" do
delete :destroy, params: { id: @category }
expect(response).to redirect_to categories_url
end
end
end
# -cut-
end
Generating Model
You can use Rails to generate your Category model.
rails generate model Category name:string
Remember to not overwrite your Category model spec. And remember to delete categories spec generated by Rails.
Adding Column to Food
Don't forget to modify our Food table to include category_id.
rails generate migration AddCategoryIdToFood category_id:integer
Then run "rails db:migrate".
Filling Category Model
Adding association, validation, and callback to Category model.
class Category < ApplicationRecord
has_many :foods
validates :name, presence: true, uniqueness: true
before_destroy :ensure_not_referenced_by_any_food
private
def ensure_not_referenced_by_any_food
unless foods.empty?
errors.add(:base, 'Foods present')
throw :abort
end
end
end
Modifying Food Model
Adding association with Category to Food model.
class Food < ApplicationRecord
belongs_to :category
# -cut-
end
You Shall Pass
Run your model specs and see it pass.
Creating Categories Controller (1)
This is how your Categories controller should look like.
class CategoriesController < ApplicationController
before_action :set_category, only: [:show, :edit, :update, :destroy]
# GET /categories
# GET /categories.json
def index
@categories = Category.all
end
# GET /categories/1
# GET /categories/1.json
def show
end
# GET /categories/new
def new
@category = Category.new
end
# GET /categories/1/edit
def edit
end
# -cut-
end
Creating Categories Controller (2)
class CategoriesController < ApplicationController
# -cut-
# POST /categories
# POST /categories.json
def create
@category = Category.new(category_params)
respond_to do |format|
if @category.save
format.html { redirect_to @category, notice: 'Category was successfully created.' }
format.json { render :show, status: :created, location: @category }
else
format.html { render :new }
format.json { render json: @category.errors, status: :unprocessable_entity }
end
end
end
# -cut-
end
Creating Categories Controller (3)
This is how your Categories controller should look like.
class CategoriesController < ApplicationController
# -cut-
# PATCH/PUT /categories/1
# PATCH/PUT /categories/1.json
def update
respond_to do |format|
if @category.update(category_params)
format.html { redirect_to @category, notice: 'Category was successfully updated.' }
format.json { render :show, status: :ok, location: @category }
else
format.html { render :edit }
format.json { render json: @category.errors, status: :unprocessable_entity }
end
end
end
# -cut-
end
Creating Categories Controller (4)
This is how your Categories controller should look like.
class CategoriesController < ApplicationController
# -cut-
# DELETE /categories/1
# DELETE /categories/1.json
def destroy
@category.destroy
respond_to do |format|
format.html { redirect_to categories_url, notice: 'Category was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_category
@category = Category.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def category_params
params.require(:category).permit(:name)
end
end
Creating Categories Controller (4)
This is how your Categories controller should look like.
class CategoriesController < ApplicationController
# -cut-
# DELETE /categories/1
# DELETE /categories/1.json
def destroy
@category.destroy
respond_to do |format|
format.html { redirect_to categories_url, notice: 'Category was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_category
@category = Category.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def category_params
params.require(:category).permit(:name)
end
end
You Shall Pass
Run your controller specs and see it pass.
Creating Categories View (1)
_category.json.builder
json.extract! category, :id, :name, :created_at, :updated_at
json.url food_url(category, format: :json)
Creating Categories View (2)
_form.html.erb
<%= form_for(category) do |f| %>
<% if category.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(category.errors.count, "error") %> prohibited this category from being saved:</h2>
<ul>
<% category.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Creating Categories View (3)
_edit.html.erb
<h1>Editing Category</h1>
<%= render 'form', category: @category %>
<%= link_to 'Show', @category %> |
<%= link_to 'Back', categories_path %>
Creating Categories View (4)
index.html.erb
<p id="notice"><%= notice %></p>
<h1>Categories</h1>
<table>
<% @categories.each do |category| %>
<tr class="<%= cycle('list_line_odd', 'list_line_even') %>">
<td><%= category.name %></td>
<td>
<%= link_to 'Show', category %>
<%= link_to 'Edit', edit_category_path(category) %>
<%= link_to 'Destroy', category, method: :delete, data: { confirm: 'Are you sure?' } %>
</td>
</tr>
<% end %>
</table>
<br/>
<%= link_to 'New Category', new_category_path %>
Creating Categories View (5)
index.json.builder
json.array! @categories, partial: 'categories/category', as: :category
Creating Categories View (6)
new.html.erb
<h1>New Category</h1>
<%= render 'form', category: @category %>
<%= link_to 'Back', categories_path %>
Creating Categories View (7)
show.html.erb
<p id="notice"><%= notice %></p>
<p>
<strong>Name:</strong>
<%= @category.name %>
</p>
<p>
<strong>Foods:</strong>
<% @category.foods.each do |food| %>
<%= food.name %>
<% end %>
</p>
<%= link_to 'Edit', edit_category_path(@category) %> |
<%= link_to 'Back', categories_path %>
Creating Categories View (8)
index.json.builder
json.partial! "categories/category", category: @category
Play Around
Now you can play around with your Category feature.
Modifying Foods Controller
We need to modify our Foods controller.
class FoodsController < ApplicationController
# -cut-
private
# -cut-
# Never trust parameters from the scary internet, only allow the white list through.
def food_params
params.require(:food).permit(:name, :description, :image_url, :price, :category_id)
end
end
Modifying Foods View (1)
_form.html.erb
<%= form_for(food) do |f| %>
<!-- cut -->
<div class="field">
<%= f.label :category %>
<%= f.collection_select :category_id, Category.order(:name), :id, :name, include_blank: true %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Modifying Foods View (2)
index.html.erb
<!-- cut -->
<table>
<% @foods.each do |food| %>
<tr class="<%= cycle('list_line_odd', 'list_line_even') %>">
<!-- cut -->
<td class="list_description">
<dl>
<dt><%= food.name %></dt>
<dd><%= truncate(strip_tags(food.description), length: 80) %></dd>
<dd>Category: <%= food.category.try(:name) %></dd>
</dl>
</td>
<!-- cut -->
</tr>
<% end %>
</table>
<!-- cut -->
Commit!
Now play around with your app and commit it afterward.
Solutions - The Simplified Go Food Web App - Iteration 2
By qblfrb
Solutions - The Simplified Go Food Web App - Iteration 2
- 311