/toasty-toes-store

let's go shopping! - "Toasty Toes" e-commerce store app

Primary LanguageRuby

############################### #### CREATING THE STORE APP ### ###############################

#### INITIAL SETUP ### # (1) Create App

$ rails _3.2.6_ new depot

# (2) Create Scaffold for Products Controller/Product Model

$ rails generate scaffold Product title:string description:text image_url:string price:decimal

# (3) To refine the database model for Product, go to the migration file (edit this stuff with activerecord syntax) # (4) Apply changes to model

$ rake db:migrate 
# (the products table is added to the database defined by the development section of the database.yml file)

# (5) To test:

$ rake test

# (6) Import seed data to work with

# go to seeds.rb in db
# enter text data script
# To populate your products table with test data, run the following command:
$ rake db:seed

# (7) Style the data!

# go to stylesheet generated with products controller (products.css.scss in the directory app/assets/stylesheets.)
# add css
# define the products class used by this stylesheet (the application.html.erb file)
# edit the index.html.erb file in the views >products
  # to show alternating colors for rows: <tr class="<%= cycle('list_line_odd', 'list_line_even') %>">
  # confirm delete pop-up:
      # <td class="list_actions">
   #    <%= link_to 'Show', product %><br/>
   #    <%= link_to 'Edit', edit_product_path(product) %><br/>
   #    <%= link_to 'Destroy', product, method: :delete,
   #                data: { confirm: 'Are you sure?' } %>
   #    </td>

# (8) Config for Github

$ depot> git config --global --add user.name "SamanthaRadocchia"
$ depot> git config --global --add user.email samantha.radocchia@gmail.com
  # You can verify the configuration with the following command:
  $ depot> git config --global --list

#### VALIDATION #### # (1) Add validation to model layer (in app/models/product.rb)

# Go to model for Class Product (in app/models/product.rb)
# VERIFY THAT ALL TEXT FIELDS CONTAIN SOMETHING - use presence 
    validates :title, :description, :image_url, presence: true (add this to the class Product)
# VERIFY THAT PRICE IS VALID NUMBER - use numericality and :greater_than_or_equal_to option a value of 0.01
    validates :price, numericality: {greater_than_or_equal_to: 0.01}
# VERIFY THAT EACH PRODUCT HAS UNIQUE TITLE - use uniqueness 
    validates :title, uniqueness: true
# VERIFY THAT URL ENTERED FOR THE IMAGE IS VALID - use format 
    validates :image_url, allow_blank: true, format: { 
      with: %r{\.(gif|jpg|png)\Z}i,
      message: 'must be a URL for GIF, JPG or PNG image.'
    }

# (2) Set up Unit tests in test > functional > products_controller_test.rb and test >unit > product_test.rb

#### CATALOG DISPLAY #### # (1) Create consumer-facing controller

# (The Products controller is used by the seller to administer the Depot application.)
# (The Store controller is used by the paying customer to view products.)
# Create the store controller with the following command:
  $ rails generate controller Store index

# (2) Make Store the root URL for the site (so the customer arrives and sees the catalog)

# go to config/routes.rb and add the following: 
  root to: 'store#index', as: 'store'
# delete the public/index.html by entering:
  $ rm public/index.html

# (3) Display a Simple List of Products in the database

# go to store_controller.rb and add the following to index:
    class StoreController < ApplicationController 
      def index
        @products = Product.order(:title)
      end 
    end

# (4) Write the Store view template (found in index.html.erb in app/views/store) # (5) Add a stylesheet (found in app/assets/stylesheets/store.css.scss) # (6) Add a PAGE LAYOUT

# application.html.erb --> this file is the layout used for all views for all controllers that don’t otherwise provide a layout, we can change the look and feel of the entire site by editing just one file
  # Update application.html.erb (DEFAUL PAGE LAYOUT) to include a banner and a sidebar
  # About YIELD (When we invoke yield, Rails automatically substitutes in the page-specific content)
# to make this work, rename application.css to application.css.scss and add default css

# (7) Format the Price using the helper method, number_to_currency()

number_to_currency(product.price)
# Go to app/views/store/index.html.erb
# <span class="price"><%= number_to_currency(product.price) %></span>
# this will add a dollar sign to the price

#### CART CREATION #### # Our application will need to keep track of all the items added to the cart by the buyer. # To do that, we’ll keep a cart in the database and store its unique identifier, cart.id, in the session. # Every time a request comes in, we can recover the identity from the session and use it to find the cart in the database. # (1) Create a Cart Controller

$ rails generate scaffold cart

# (2) Remember to run rake:db migrate

$ rake db:migrate

# (3) Store the id of the cart in the session by indexing it with the symbol :cart_id

# Go to app/controllers/application_controller.rb and do this:
    class ApplicationController < ActionController::Base
      protect_from_forgery

      private

        def current_cart 
          Cart.find(session[:cart_id])
        rescue ActiveRecord::RecordNotFound
          cart = Cart.create
          session[:cart_id] = cart.id
          cart
        end
    end

# (4) Generate Models and populate the migrations to create the corresponding tables such that a CART CONTAINS A SET OF PRODUCTS

# To create tables for product/cart run the following command
  $ rails generate scaffold line_item product_id:integer cart_id:integer
# Migrate
  $ rake db: migrate 
# The database now has a place to store the relationships between line items, carts, and products.

# (5) Specify that the cart can have many items

# Go to the models > cart.rb and add the following:
  has_many :line_items, dependent: :destroy # in place of the attr accessor

# (6) Create a reverse link from line_items.rb to cart.rb such that it belongs to a cart and a product

# Go to models > line_items.rb and add the following to class LineItem:
  belongs_to :product
  belongs_to :cart

# (7) Edit the model product.rb to include:

has_many :line_items
before_destroy :ensure_not_referenced_by_any_line_item

# (8) Add an “Add to Cart” button to each product

# To do this, you want to use the create action provided by the scaffold generator to CREATE a LINEITEM
# Essentially you are creating a new object, LineItem, when you add to the cart
# To do so, go here: app/controllers/line_items_controller.rb
# We will use the button_to() method
  button_to()
# Add the following to app/views/store/index.html.erb
  # <%= button_to 'Add to Cart', line_items_path(product_id: product) %>

# (9) Format the “Add to Cart” button such that it is displayed next to price, not below it (make the button inline)

# app/assets/stylesheets/store.css.scss

# (10) modify the create() method in the line items controller to expect a product id as a form parameter.

# app/controllers/line_items_controller.rb
    def create
      @cart = current_cart
      product = Product.find(params[:product_id])
      @line_item = @cart.line_items.build
      @line_item.product = product

      respond_to do |format|
        if @line_item.save
          format.html { redirect_to @line_item.cart,
            notice: 'Line item was successfully created.' }
          format.json { render json: @line_item,
            status: :created, location: @line_item }
        else
          format.html { render action: "new" }
          format.json { render json: @line_item.errors,
            status: :unprocessable_entity }
        end
      end
    end

#### CREATE A SMARTER CART #### # (1) Associating a count with each product in the cart

# Add a new column, "quantity" to the table line_items (# add_XXX_to_TABLE)
  $ rails generate migration add_quantity_to_line_items quantity:integer
# Add default value for this column in the migration file
  add_column :line_items, :quantity, :integer, default: 1
# Migrate
  $ rake db:migrate

# (2) Create method to check whether our list of items already includes the product we’re adding if it does, it bumps the quantity, and if it doesn’t, it builds a new LineItem:

# Go to app/models/cart.rb
# Create an add_product method for class Cart
    def add_product(product_id)
      # Active Record Method find_by_product_id() (start with find_by and end by column_name)
      current_item = line_items.find_by_product_id(product_id)
      if current_item
        current_item.quantity += 1
      else
        current_item = line_items.build(product_id: product_id)
      end
      current_item
    end
# Also need to edit line item controller to make use of this method
# Go to app/controllers/line_items_controller.rb and add the following:
    def create
      @cart = current_cart
      product = Product.find(params[:product_id])
      @line_item = @cart.add_product(product.id)
      @line_item.product = product

      respond_to do |format|
        if @line_item.save
          format.html { redirect_to @line_item.cart,
            notice: 'Line item was successfully created.' }
          format.json { render json: @line_item,
            status: :created, location: @line_item }
        else
          format.html { render action: "new" }
          format.json { render json: @line_item.errors,
            status: :unprocessable_entity }
        end
      end
    end

# (3) Update Cart View to display products added to it

# go here: app/views/carts/show.html.erb
# add the following erb: 
  <% if notice %>
  <p id="notice"><%= notice %></p>
  <% end %>

  <h2>Your Pragmatic Cart</h2>
  <ul>    
    <% @cart.line_items.each do |item| %>
      <li><%= item.product.title %></li>
    <% end %>
  </ul>

# (4) Combine items in cart

# run a migration
  $ rails generate migration combine_items_in_cart
# go to depot_g/db/migrate/20110711000005_combine_items_in_cart.rb and put crazy code in there 
    class CombineItemsInCart < ActiveRecord::Migration

      def up
        # replace multiple items for a single product in a cart with a single item
        Cart.all.each do |cart|
          # count the number of each product in the cart
          sums = cart.line_items.group(:product_id).sum(:quantity)

          sums.each do |product_id, quantity|
            if quantity > 1
              # remove individual items
              cart.line_items.where(product_id: product_id).delete_all

              # replace with a single item
              item = cart.line_items.build(product_id: product_id)
              item.quantity = quantity
              item.save!
            end
          end
        end
      end

      def down
        # split items with quantity>1 into multiple items
        LineItem.where("quantity>1").each do |line_item|
          # add individual items
          line_item.quantity.times do 
            LineItem.create cart_id: line_item.cart_id,
              product_id: line_item.product_id
          end

          # remove original item
          line_item.destroy
        end
      end
    end
# run rake db:migrate
  $ rake db:migrate 
# edit app/views/carts/show.html.erb to show the quantity 
  <li><%= item.quantity %> &times; <%= item.product.title %></li>

# (5) To empty cart items

# First add a button the cart view (app/views/carts/show.html.erb)
  # Add this code
    # <%= button_to 'Empty cart', @cart, method: :delete,
    # data: { confirm: 'Are you sure?' } %>
# Update Controller's destroy method (app/controllers/carts_controller.rb)
  # Add this code
    def destroy
      @cart = current_cart
      @cart.destroy
      session[:cart_id] = nil

      respond_to do |format|
        format.html { redirect_to store_url,
          notice: 'Your cart is currently empty' }
        format.json { head :no_content }
      end
    end

# (6) Tidy up the cart CSS (instead of li use a table) and CALCULATE TOTAL

# Calculate total price in cart
  # add total_price method to line_items model (app/models/line_item.rb)
    def total_price 
      product.price * quantity
    end
  # add new total_price method to cart model (app/models/cart.rb)
    def total_price
      line_items.to_a.sum { |item| item.total_price }
    end
# Tweak css to display total (app/assets/stylesheets/carts.css.scss)
    .carts {
      .item_price, .total_line {
        text-align: right;
      }

      .total_line .total_cell {
        font-weight: bold;
        border-top: 1px solid #595;
      }
    }

#### ERRORS AND ERROR REPORTING #### # (1) Intercept Bad Carts and Report on the Problem

# Go here: app/controllers/carts_controller.rb and add the following to the show method:
    def show
      begin
        @cart = Cart.find(params[:id])
      rescue ActiveRecord::RecordNotFound
        logger.error "Attempt to access invalid cart #{params[:id]}"
        redirect_to store_url, notice: 'Invalid cart'
      else
        respond_to do |format|
          format.html # show.html.erb
          format.json { render json: @cart }
        end
      end
    end
# Now the customer will just be redirected back to the catalog page and not see errors
# To see errors on the dev side go to development.log in the log directory