文章目录
  1. 1. 课程目标
    1. 1.1. 1. admin权限控制
      1. 1.1.1. Step 1: 必须要先登入才能进入
      2. 1.1.2. Step 2: 必须要有 admin 权限才能进入
      3. 1.1.3. Step 3: 建立 admin 判断式
      4. 1.1.4. Step 4: 加入 admin? 判断式
      5. 1.1.5. Step 5: 新增 is_admin 栏位(boolean)
      6. 1.1.6. Step 6: 在 rails console 操作新增一个 admin 使用者
      7. 1.1.7. Step 7: 新增一个 user 种子档
    2. 1.2. 2. 上传图片
      1. 1.2.1. Step 1: 安装carrierwave and mini_magick
      2. 1.2.2. Step 2: 在product新增image栏位
      3. 1.2.3. Step 3: 挂上 image uploader
      4. 1.2.4. Step 4: 设定图片上传后裁切的尺寸
      5. 1.2.5. Step 5: 新增商品时可以上传商品照片
      6. 1.2.6. Step 6: 商品修改页也能修改图片
      7. 1.2.7. Step 7: 将 uploads 资料夹放入 .gitignore
      8. 1.2.8. Step 8: 前台 products#show, products#index 实作
      9. 1.2.9. Step 9: 重构后台商品列表

课程目标

这一课商店网站,你将会学到至少以下的主题:

  • 购物车设计
  • 结帐订单
  • 第三方整合
  • 代码重构
    熟练掌握这个课程的主题后,能够让你在未来写出绝大多数的网站功能。

技术议题包含:

  • 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.rb
1
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.rb
1
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.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
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.rb
1
2
3
4
5
6
7
8
9
10
11
12
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
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.rb
1
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" # 可以改成自己的 email
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

Gemfile
1
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.rb
1
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.rb
1
2
3
class Product < ApplicationRecord
+ mount_uploader :image, ImageUploader
end

Step 4: 设定图片上传后裁切的尺寸

app/uploaders/image_uploader.rb
1
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 RMagick or MiniMagick support:
# include CarrierWave::RMagick
- # include CarrierWave::MiniMagick
+ include CarrierWave::MiniMagick
# Choose what kind of storage to use for this uploader:
storage :file
# storage :fog
# Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted:
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.rb
1
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.erb
1
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 里面

.gitignore
1
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.rb
1
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.erb
1
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.rb
1
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.rb
1
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.erb
1
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.erb
1
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.erb
1
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.erb
1
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"
文章目录
  1. 1. 课程目标
    1. 1.1. 1. admin权限控制
      1. 1.1.1. Step 1: 必须要先登入才能进入
      2. 1.1.2. Step 2: 必须要有 admin 权限才能进入
      3. 1.1.3. Step 3: 建立 admin 判断式
      4. 1.1.4. Step 4: 加入 admin? 判断式
      5. 1.1.5. Step 5: 新增 is_admin 栏位(boolean)
      6. 1.1.6. Step 6: 在 rails console 操作新增一个 admin 使用者
      7. 1.1.7. Step 7: 新增一个 user 种子档
    2. 1.2. 2. 上传图片
      1. 1.2.1. Step 1: 安装carrierwave and mini_magick
      2. 1.2.2. Step 2: 在product新增image栏位
      3. 1.2.3. Step 3: 挂上 image uploader
      4. 1.2.4. Step 4: 设定图片上传后裁切的尺寸
      5. 1.2.5. Step 5: 新增商品时可以上传商品照片
      6. 1.2.6. Step 6: 商品修改页也能修改图片
      7. 1.2.7. Step 7: 将 uploads 资料夹放入 .gitignore
      8. 1.2.8. Step 8: 前台 products#show, products#index 实作
      9. 1.2.9. Step 9: 重构后台商品列表