课程目标
这一课商店网站,你将会学到至少以下的主题:
- 购物车设计
- 结帐订单
- 第三方整合
- 代码重构
熟练掌握这个课程的主题后,能够让你在未来写出绝大多数的网站功能。
技术议题包含:
- Model 设计原则
- 自定义 before_action
- 自定义 Routing
- validation
- session 操作
- find_by 操作
- where / order
- 巢状表单
- State Machine
- ActiveJob
- Mailer
- Partial / Helper
1. admin权限控制
Step 1: 必须要先登入才能进入
app/controllers/admin/products_controller.rb1 2 3 4 5 6 7 8 9
| class Admin::ProductsController < ApplicationController + before_action :authenticate_user! def index @products = Product.all end ...(略)
|
在浏览器测试是否强制验证
http://localhost:3000/admin/products/new
Step 2: 必须要有 admin 权限才能进入
app/controllers/admin/products_controller.rb1 2 3 4 5 6 7 8 9 10
| class Admin::ProductsController < ApplicationController before_action :authenticate_user! + before_action :admin_required def index @products = Product.all end ...(略)
|
Step 3: 建立 admin 判断式
app/controllers/application_controller.rb1 2 3 4 5 6 7 8 9 10 11 12 13
| class ApplicationController < ActionController::Base protect_from_forgery with: :exception + def admin_required + if !current_user.admin? + redirect_to "/", alert: "You are not admin." + end + end end
|
Step 4: 加入 admin? 判断式
app/models/user.rb1 2 3 4 5 6 7 8 9 10 11 12
| class User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable + def admin? + is_admin + end end
|
Step 5: 新增 is_admin 栏位(boolean)
rails g migration add_is_admin_to_user
修改里面的档案
db/migrate/xxx(一堆数字)_add_is_admin_to_user.rb1 2 3 4 5
| class AddIsAdminToUser < ActiveRecord::Migration[5.0] def change + add_column :users, :is_admin, :boolean, default: false end end
|
执行rake db:migrate
重开rails server
测试admin是否能进后台
存取
http://localhost:3000/admin/products/new
Step 6: 在 rails console 操作新增一个 admin 使用者
1 2 3 4 5 6
| rails c u = User.new(email: "admin@test.com", password: "123456", password_confirmation: "123456") u.save u.is_admin = true u.save
|
再次测试admin是否能进后台
存取
http://localhost:3000/admin/products/new
Step 7: 新增一个 user 种子档
1 2 3 4 5 6 7 8 9 10
| db/seeds.rb u = User.new u.email = "admin@test.com" u.password = "123456" u.password_confirmation = "123456" u.is_admin = true u.save
|
然后 rake db:seed
即可自动建一个有 admin 权限的帐号
补充: 日后资料库设定 ( migrate ) 重建时发生错误时的 bug fix
rake db:reset
2. 上传图片
Step 1: 安装carrierwave and mini_magick
Gemfile1 2 3 4 5 6 7 8 9
| ...(略) gem 'font-awesome-rails' + gem 'carrierwave' + gem 'mini_magick' group :development, :test do ...(略)
|
bundle install
rails g uploader image
重开 rails s
Step 2: 在product新增image栏位
rails g migration add_image_to_product
db/migrate/XXX(一堆数字)_add_image_to_product.rb1 2 3 4 5
| class AddImageToProduct < ActiveRecord::Migration[5.0] def change + add_column :products, :image, :string end end
|
rake db:migrate
Step 3: 挂上 image uploader
app/models/product.rb1 2 3
| class Product < ApplicationRecord + mount_uploader :image, ImageUploader end
|
Step 4: 设定图片上传后裁切的尺寸
app/uploaders/image_uploader.rb1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class ImageUploader < CarrierWave::Uploader::Base - + include CarrierWave::MiniMagick storage :file def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end + process resize_to_fit: [800, 800] + version :thumb do + process resize_to_fill: [200,200] + end + version :medium do + process resize_to_fill: [400,400] + end ...(略)
|
Step 5: 新增商品时可以上传商品照片
app/controllers/admin/products_controller.rb1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| ...(略) def new @product = Product.new end ...(略) def product_params - params.require(:product).permit(:title, :description, :quantity, :price) + params.require(:product).permit(:title, :description, :quantity, :price, :image) end ...(略) app/views/admin/products/new.html.erb ...(略) <div class="group"> <%= f.input :price %> </div> + <div class="group"> + <%= f.input :image, as: :file %> + </div> <%= f.submit "Submit", data: { disable_with: "Submitting..." } %> ...(略)
|
Step 6: 商品修改页也能修改图片
app/views/admin/products/edit.html.erb1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| ...(略) <div class="group"> <%= f.input :price %> </div>
+ <% if @product.image.present? %> + <span>目前商品图</span> <br> + <%= image_tag(@product.image.thumb.url) %> + <% end %> + <div class="group"> + <%= f.input :image, as: :file %> + </div>
<%= f.submit "Submit", data: { disable_with: "Submitting..." } %> ...(略)
|
Step 7: 将 uploads 资料夹放入 .gitignore
由于我们实作的图片上传功能,会将图片档案存在 public/uploads/xxx 的资料夹里面
我们不希望这些档案放进 git,所以要像 database.yml 一样将整个资料夹放入 .gitignore 里面
.gitignore1 2 3
| ...(略) config/database.yml + public/uploads
|
git存档
1 2
| git add . git commit -m "implement image upload"
|
Step 8: 前台 products#show, products#index 实作
rails g controller products
建立 products/index
app/controllers/products_controller.rb1 2 3 4 5
| class ProductsController < ApplicationController + def index + @products = Product.all + end end
|
touch app/views/products/index.html.erb
app/views/products/index.html.erb1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <div class="row"> <% @products.each do |product| %> <div class="col-xs-6 col-md-3"> <%= link_to product_path(product) do %> <% if product.image.present? %> <%= image_tag(product.image.thumb.url, class: "thumbnail") %> <% else %> <%= image_tag("http://placehold.it/200x200&text=No Pic", class: "thumbnail") %> <% end %> <% end %> <%= product.title %> ¥ <%= product.price %> </div> <% end %> </div>
|
修改首页路径
config/routes.rb1 2 3 4 5 6 7 8 9 10 11
| Rails.application.routes.draw do - root 'welcome#index' + root 'products#index' devise_for :users namespace :admin do resources :products end + resources :products ...(略)
|
建立 products/show
app/controllers/products_controller.rb1 2 3 4 5 6 7 8 9
| class ProductsController < ApplicationController def index @products = Product.all end + def show + @product = Product.find(params[:id]) + end end
|
touch app/views/products/show.html.erb
app/views/products/show.html.erb1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <div class="row"> <div class="col-md-6"> <% if @product.image.present? %> <%= image_tag(@product.image.medium.url, class: "thumbnail") %> <% else %> <%= image_tag("http://placehold.it/400x400&text=No Pic", class: "thumbnail") %> <% end %> </div> <div class="col-md-6"> <h2> <%= @product.title %> </h2> <div style="height:100px;"> <p> <%= @product.description %> </p> </div> <div> 数量 : <%= @product.quantity %> </div> <div class="product-price"> ¥ <%= @product.price %> </div> <div class="pull-right"> <%= link_to("加入购物车", "#", :class => "btn btn-danger btn-lg") %> </div> </div> </div>
|
在navbar加上product链接
app/views/common/_navbar.html.erb1 2 3 4 5 6 7 8 9
| ...(略) <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> + <ul class="nav navbar-nav"> + <li class="active"> + <%= link_to("Products", products_path) %> + </li> + </ul> ...(略)
|
git存档
1 2
| git add . git commit -m "implement product show"
|
Step 9: 重构后台商品列表
将 app/views/admin/products/index.html.erb
换成以下的 code
app/views/admin/products/index.html.erb1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <h2> Product List </h2> <div class="pull-right" style="padding-bottom: 20px;"> <%= link_to("新增产品", new_admin_product_path, class: "btn btn-primary btn-sm") %> </div> <table class="table table-bordered"> <thead> <tr> <th>#</th> <th width="220">Product Pic</th> <th>Name</th> <th>Price</th> <th width="100"> Options</th> </tr> </thead> <tbody> <% @products.each do |product| %> <tr> <td> <%= product.id %> </td> <td> <%= link_to product_path(product) do %> <% if product.image.present? %> <%= image_tag(product.image.thumb.url, class: "thumbnail") %> <% else %> <%= image_tag("http://placehold.it/200x200&text=No Pic", class: "thumbnail") %> <% end %> <% end %> </td> <td> <%= product.title %> </td> <td> <%= product.price %> </td> <td> <%= link_to("Edit", edit_admin_product_path(product)) %> </td> </tr> <% end %> </tbody> </table>
|
navbar 的下拉选单放入后台链接
app/views/common/_navbar.html.erb1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ...(略) <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown"> Hi!, <%= current_user.email %> <b class="caret"></b> </a> <ul class="dropdown-menu"> + <% if current_user.admin? %> + <li> + <%= link_to("Admin 选单", admin_products_path ) %> + </li> + <% end %> <li> <%= link_to(content_tag(:i, '登出', class: 'fa fa-sign-out'), destroy_user_session_path, method: :delete) %> </li> </ul> </li> ...(略)
|
git存档
1 2
| git add . git commit -m "change admin/product backend panel"
|