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
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
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
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
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
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
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
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
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
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
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
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
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
# -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
# -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
# -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
# -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
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.
Don't forget to modify our Food table to include category_id.
rails generate migration AddCategoryIdToFood category_id:integer
Then run "rails db:migrate".
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
Adding association with Category to Food model.
class Food < ApplicationRecord
belongs_to :category
# -cut-
end
Run your model specs and see it pass.
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
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
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
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
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
Run your controller specs and see it pass.
_category.json.builder
json.extract! category, :id, :name, :created_at, :updated_at
json.url food_url(category, format: :json)
_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 %>
_edit.html.erb
<h1>Editing Category</h1>
<%= render 'form', category: @category %>
<%= link_to 'Show', @category %> |
<%= link_to 'Back', categories_path %>
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 %>
index.json.builder
json.array! @categories, partial: 'categories/category', as: :category
new.html.erb
<h1>New Category</h1>
<%= render 'form', category: @category %>
<%= link_to 'Back', categories_path %>
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 %>
index.json.builder
json.partial! "categories/category", category: @category
Now you can play around with your Category feature.
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
_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 %>
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 -->
Now play around with your app and commit it afterward.