商城迁移

08 管理平台开发
09 管理平台:分类管理开发
10 管理平台 商品管理
11 管理平台 商品图片管理
12 分类和商品页面开发
13 购物车功能开发

08 管理平台开发
去除默认的routes的设定

#config/application.rb
 config.generators do |generator|
   generator.skip_routes true
 end

routes.rb中建立独立的admin路由

namespace :admin do
  root "sessions#new"

  resources :sessions
  resources :categories
end

终端创建controller

rails g controller admin::sessions new #会自动生成routes,但是前面已经skip routes了
rails g controller admin::categories index new

针对controller层,为了代码看起来更有逻辑性,可以生成一个Admin::BaseController继承于ApplicationController,Admin::SessionsController和Admin::CategoriesController同时继承于这个类。
针对view层,建立views/admin文件夹,其中建立layouts目录,这是针对管理平台页面的布局,其中建立头文件_menu.html.erb文件和布局文件admin.html.erb文件。
_menu.html.erb代码如下:


admin.html.erb代码如下:



  
    管理平台 - 蛋人商城
    
    <%= csrf_meta_tags %>
    
    <%= stylesheet_link_tag 'admin', media: 'all' %>
    <%= yield :stylesheets %>
  

  
    <%= render 'admin/layouts/menu' %>
    
<% unless flash[:notice].blank? %>
<%= flash[:notice] %>
<% end -%> <%= yield %>
<%= javascript_include_tag 'admin' %> <%= yield :javascripts %>

对父模板进行引用

#base_controller.rb
class Admin::BaseController < ActionController::Base
  layout "admin/layouts/admin"
end

因为为后台添加了样式,所以针对这个新的样式添加新的css和js文件。

#app/assets/javascripts/admin.js
//= require jquery
//= require jquery_ujs
//= require bootstrap-sprockets
//= require_tree .  #这句代码需删除,因为这句代码会把javascript中所有的js代码都加载进去,application.js中也删除该代码

##app/assets/javascripts/admin.scss
@import "bootstrap-sprockets";
@import "bootstrap";
@import "font-awesome";

因为会独立引用css和js文件,所以需要对预编译文件进行配置

#config/initializers/assets.rb
Rails.application.config.assets.precompile += %w(
  admin.js
  admin.css
)

09 管理平台:分类管理开发
category的model,controller,view层的开发,这里有一级分类和二级分类。
product.rb

class Category < ApplicationRecord
  validates :title, presence: { message: "名称不能为空" }
  validates :title, uniqueness: { message: "名称不能重复" }

  has_ancestry orphan_strategy: :destroy
  has_many :products, dependent: :destroy

  before_validation :correct_ancestry

  private
  def correct_ancestry
    self.ancestry = nil if self.ancestry.blank?
  end
end

categories_controller.rb

class Admin::CategoriesController < Admin::BaseController

  before_action :find_root_categories, only: [:new, :create, :edit, :update]
  before_action :find_category, only: [:edit, :update, :destroy]
  
  def index
    if params[:id].blank?
      @categories = Category.roots
    else
      @category = Category.find(params[:id])
      @categories = @category.children
    end

    @categories = @categories.page(params[:page] || 1).per_page(params[:per_page] || 10)
      .order(id: "desc")
  end

  def new
    @category = Category.new
  end

  def create
    @category = Category.new(params.require(:category).permit!)

    if @category.save
      flash[:notice] = "保存成功"
      redirect_to admin_categories_path
    else
      render action: :new
    end
  end

  def edit
    render action: :new
  end

  def update
    @category.attributes = params.require(:category).permit!

    if @category.save
      flash[:notice] = "修改成功"
      redirect_to admin_categories_path
    else
      render action: :new
    end
  end

  def destroy
    if @category.destroy
      flash[:notice] = "删除成功"
      redirect_to admin_categories_path
    else
      flash[:notice] = "删除失败"
      redirect_to :back
    end
  end

  private
  def find_root_categories
    @root_categories = Category.roots.order(id: "desc")
  end

  def find_category
    @category = Category.find(params[:id])
  end
end

views/admin/categories/new.html.erb和index.html.erb

#new.html.erb

<%= @category.new_record? ? "新建分类" : "修改分类 ##{params[:id]}" %>

<%= form_for @category, url: (@category.new_record? ? admin_categories_path : admin_category_path(@category)), method: (@category.new_record? ? 'post' : 'put'), html: { class: 'form-horizontal' } do |f| %> <% unless @category.errors.blank? %>
    <% @category.errors.messages.values.flatten.each do |error| %>
  • <%= error %>
  • <% end -%>
<% end -%>
为空为一级分类
<%= f.text_field :title, class: "form-control" %>
<%= f.text_field :weight, class: "form-control" %> 数值越大越靠前
<%= f.submit (@category.new_record? ? "新建分类" : "编辑分类"), class: "btn btn-default" %>
<% end -%>
#index.html.erb
<%= link_to "新建分类", new_admin_category_path, class: "btn btn-primary" %>

<% if @category %> 分类:<%= @category.title %>(<%= @categories.total_entries %>) <% else %> 分类(<%= @categories.total_entries %>) <% end -%>

<% @categories.each do |category| %> <% end -%>
ID 名称 Weight
<%= category.id %> <%= category.title %> <%= category.weight %> <%= link_to "编辑", edit_admin_category_path(category) %> <%= link_to "删除", admin_category_path(category), method: :delete, 'data-confirm': "确认删除吗?" %> <% if category.root? %> <%= link_to "查看子分类", admin_categories_path(id: category) %> <% end -%>
<%= will_paginate @categories %>

10 管理平台 商品管理
product.rb模型描述

#product.rb
class Product < ApplicationRecord

  validates :category_id, presence: { message: "分类不能为空" }
  validates :title, presence: { message: "名称不能为空" }
  validates :status, inclusion: { in: %w[on off], 
    message: "商品状态必须为on | off" }
  validates :amount, numericality: { only_integer: true,
    message: "库存必须为整数" },
    if: proc { |product| !product.amount.blank? }
  validates :amount, presence: { message: "库存不能为空" }
  validates :msrp, presence: { message: "MSRP不能为空" }
  validates :msrp, numericality: { message: "MSRP必须为数字" },
    if: proc { |product| !product.msrp.blank? }
  validates :price, numericality: { message: "价格必须为数字" },
    if: proc { |product| !product.price.blank? }
  validates :price, presence: { message: "价格不能为空" }
  validates :description, presence: { message: "描述不能为空" }

  belongs_to :category

  before_create :set_default_attrs

  module Status
    On = 'on'
    Off = 'off'
  end

  private
  def set_default_attrs
    self.uuid = RandomCode.generate_product_uuid
  end
end

products_controller.rb描述

class Admin::ProductsController < Admin::BaseController
  before_action :find_product, only: [:edit, :update, :destroy]

  def index
    @products = Product.page(params[:page] || 1).per_page(params[:per_page] || 10)
      .order("id desc")
  end

  def new
    @product = Product.new
    @root_categories = Category.roots
  end

  def create
    @product = Product.new(params.require(:product).permit!)
    @root_categories = Category.roots

    if @product.save
      flash[:notice] = "创建成功"
      redirect_to admin_products_path
    else
      render action: :new
    end
  end

  def edit
    @root_categories = Category.roots
    render action: :new
  end

  def update
    @product.attributes = params.require(:product).permit!
    @root_categories = Category.roots
    if @product.save
      flash[:notice] = "修改成功"
      redirect_to admin_products_path
    else
      render action: :new
    end
  end

  def destroy
    if @product.destroy
      flash[:notice] = "删除成功"
      redirect_to admin_products_path
    else
      flash[:notice] = "删除失败"
      redirect_to :back
    end
  end

  private
  def find_product
    @product = Product.find(params[:id])
  end
end

设置常量的值

#product.rb
class Product < ApplicationRecord
  module Status
    On = 'on'
    Off = 'off'
  end
end

设置new.html.erb和index.html.erb

#index.html.erb
<%= link_to "新建商品", new_admin_product_path, class: "btn btn-primary" %>

商品(<%= @products.total_entries %>)

<% @products.each do |product| %> <% end -%>
ID 名称 UUID/SKU MSRP Price 库存 状态
<%= product.id %> <%= product.title %> <%= product.uuid %> <%= product.msrp %> <%= product.price %> <%= product.amount %> <%= product.status %> <%= link_to "编辑", edit_admin_product_path(product) %> <%= link_to "删除", admin_product_path(product), method: :delete, 'data-confirm': "确认删除吗?" %>
<%= will_paginate @products %>
#new.html.erb

<%= @product.new_record? ? "新建商品" : "修改商品 ##{params[:id]}" %>

<%= form_for @product, url: (@product.new_record? ? admin_products_path : admin_product_path(@product)), method: (@product.new_record? ? 'post' : 'put'), html: { class: 'form-horizontal' } do |f| %> <% unless @product.errors.blank? %>
    <% @product.errors.messages.values.flatten.each do |error| %>
  • <%= error %>
  • <% end -%>
<% end -%>
<%= f.text_field :title, class: "form-control" %>
<%= f.text_field :amount, class: "form-control" %> 必须为整数
<%= f.text_field :price, class: "form-control" %>
<%= f.text_field :msrp, class: "form-control" %>
<%= f.text_area :description, class: "form-control" %>
<%= f.submit (@product.new_record? ? "新建商品" : "编辑商品"), class: "btn btn-default" %>
<% end -%>

11 管理平台 商品图片管理
参考peperclip这个gem进行代码填充,和这个gem代码唯一的不同是,这个gem中的代码没有添加product_image与product的关联关系。如果要应用到这个项目中,需要添加这两个model的关联关系,同时修改controller和view中的代码。

12 分类和商品页面开发
这个章节需要处理
1、左边标签栏的创建
2、右边面包屑以及图片成列
3、N+1问题的处理
对于标签的左边栏显示参考ancestry的生成一二级标签栏页面的代码。
利用scope查询只有上架的商品被显示

#routes.rb
resources :categories, only: [:show]
resources :products, only: [:show]

#product.rb
class Product < ApplicationRecord
  scope :onshelf, ->{ where(status: Status::On) }
end

#welcome_controller.rb
class WelcomeController < ApplicationController
  
  def index
    fetch_home_data #已经在ApplicationController类中被定义
    
    @products = Product.onshelf.page(params[:page] || 1).per_page(params[:per_page] || 12)
      .order("id desc").includes(:main_product_image)
  end

end

#app/views/welcome/index.html.erb,使用两个嵌套模板
<%= render 'shared/categories' %>
<%= render 'shared/products' %>
#app/views/shared/categories.html.erb
    <% @categories.each do |group| %>
  • <%= group.first.title %>
  • <% group.last.each do |sub_category| %>
  • <%= sub_category.title %>
  • <% end -%> <% end -%>
#app/views/shared/products.html.erb
<% @products.each do |product| %>
<%= link_to image_tag(product.main_product_image.image.url(:middle), alt: product.title), product_path(product) %>

<%= link_to product.title, product_path(product), class: 'title' %>

¥<%= product.price %> ¥<%= product.msrp %>

加入购物车

<% end -%>
<%= will_paginate @products %>

通过product.main_product和includes解决N+1问题

#product.rb
class Product < ApplicationRecord
  has_one :main_product_image, -> { order(weight: 'desc') },
    class_name: :ProductImage
end

#welcome_controller.rb
class WelcomeController < ApplicationController
  
  def index
    fetch_home_data #已经在ApplicationController类中被定义
    
    @products = Product.onshelf.page(params[:page] || 1).per_page(params[:per_page] || 12)
      .order("id desc").includes(:main_product_image)
  end
end

添加相关样式#stylesheets/home.css,需要在application.scss文件中进行import

.list-group-item a {
  display: block;
}
.msrp {
  text-decoration: line-through;
}
.thumbnail {
  height: 290px;
  &.detail {
    height: auto;
  }
  a.title {
    color: #333;
  }
}

点击左边标签,显示该标签下面的商品

#categories_controller.rb
class CategoriesController < ApplicationController
  def show
    @category = Category.find(params[:id])
    @products = @category.products.onshelf.page(params[:page] || 1).per_page(params[:per_page] || 12)
      .order("id desc").includes(:main_product_image)
  end
end
<%= render 'shared/categories' %>

<%= @category.title %>

<%= render 'shared/products' %>

点击商品,查看商品的详细信息

class ProductsController < ApplicationController
  def show
    fetch_home_data    
    @product = Product.find(params[:id])
  end
end
<%= render 'shared/categories' %>

<%= @product.title %>

<% @product.product_images.each do |product_image| %> <% end -%>
  • 商品编号: <%= @product.uuid %>
  • 库存: <%= @product.amount %>

¥<%= @product.price %> ¥<%= @product.msrp %>

<%= link_to "加入购物车", "#", class: "btn btn-danger" %>




<%= @product.description.html_safe %>

13 购物车功能开发
场景描述和解释:
因为在不同的电脑上都可以查看到用户的购物车,因此这个购物车到存储到数据库中。正常情况下,这个购物车需要指定属于哪个用户,所以需要user_id这个字段(允许为空),但是也可以在用户不登录的情况下创建购物车,所以需要user_uuid这个字段,用来追踪这个购物车属于哪个用户,[这里采取的策略是,任何一个用户打开这个网站的时候,都在浏览器的cookies中设置一个user_uuid(唯一字符串),当用户添加购物车的时候,将使用这个user_uuid来追踪当前的用户],当用户进行注册的时候,则将user_uuid绑定到用户创建这个用户表中来,如果是要进行登录,那么将用户表中本身存在user_uuid值取出,然后更新存储于浏览器的cookies中的user_uuid值。这样保证了用户是否登录,user_uuid追踪用户都是有效的。

ShoppingCart模型创建和User模型修改

#创建ShoppingCart模型
class CreateShoppingCarts < ActiveRecord::Migration[5.0]
  def change
    create_table :shopping_carts do |t|
      t.integer :user_id
      t.string :user_uuid
      t.integer :product_id
      t.integer :amount
      t.timestamps
    end

    add_index :shopping_carts, [:user_id]
    add_index :shopping_carts, [:user_uuid]
  end
end

#修改User模型
class AddUserUuidColumn < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :uuid, :string

    add_index :users, [:uuid], unique: true

    User.find_each do |user|
      user.uuid = RandomCode.generate_utoken
      user.save
    end
  end
end

用户未登录下购物车功能的实现
思路:整个购物车的实现主要是通过user_uuid和uuid两个字段来实现的。不管是登录还是注册,将用户的uuid和浏览器中cookies中的user_uuid进行绑定即可。
1、任何一个用户打开这个网站的时候,都在浏览器的cookies中设置一个user_uuid(唯一字符串)

#application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  before_action :set_browser_uuid

  protected

  def set_browser_uuid
    uuid = cookies[:user_uuid]

    unless uuid
      if logged_in?
        uuid = current_user.uuid
      else
        uuid = RandomCode.generate_utoken
      end
    end

    update_browser_uuid uuid
  end

  def update_browser_uuid uuid
    #添加cookies是为了长久保持在浏览器当中
    session[:user_uuid] = cookies.permanent['user_uuid'] = uuid
  end
end

2、用户注册的时候,需要将浏览器中user_uuid绑定到用户中,当用户进行登录,那么将用户表中本身存在user_uuid值取出,然后更新存储于浏览器的cookies中的user_uuid值。

#users_controller.rb,用户注册的情况,存在于create方法中
@user.uuid = session[:user_uuid]

#sessions_controller.rb,用户登录的情况下,参考application_controller.rb文件中方法
update_browser_uuid user.uuid

3、购物车添加、删除和更新功能实现

#routes.rb
resources :shopping_carts

#shopping_carts_controller.rb
class ShoppingCartsController < ApplicationController
  def index
    @
  end
end

你可能感兴趣的:(商城迁移)