返回首页 Rails 实践

第一章 Ruby on Rails 概述

第二章 Rails 中的资源

第三章 Rails 中的视图

第四章 Rails 中的模型

第五章 Rails 中的控制器

第六章 Rails 的配置及部署

5.2 控制器中的方法

概要

本课时讲解 Controller 中的回调,权限控制,及如何实现网店的购物车和支付功能,以及使用 datatable 查看订单数据。

知识点

  1. 回调
  2. 权限设置
  3. 状态变更
  4. 支付
  5. 带分页的数据列表
  6. datatable

正文

5.2.1 回调

和 Model 中的回调一样,Controller 中也有回调。Rails 4 之前,它称作过滤器,Filter,现在一些文档也在使用 filter 字样。

回调它之前的名字是 xxx_filter,但是这种称呼很是歧义,于是在 Rails 4 中改成了 xxx_action

Controller 中的回调有三个,before,after,around。并且可以通过 :only:except 指定在哪些方法上应用该回调。

在我们的项目里,为了使登录用户才能访问,我们在 application_controller.rb 中已经使用了一个前置回调:

class ApplicationController < ActionController::Base
  ...
  before_action :authenticate_user!
  ...
end

因为其他的 Controller 都继承自它,所以这个前置回调会在所有 Controller 中生效。也就是说,访问所有页面,都需要登录状态。

但是对于首页,展示页等,可以公开访问的页面,我们需要跳过这个登录校验,Controller 中还可以使用 skip_before_action :xxx 跳过回调。

class ProductsController < ApplicationController
  skip_before_action :authenticate_user!, only: [:index, :show, :top]
end

回调也可以使用 block 和单独的回调类,方法和 Model 中一样,或者参考这里。(注:它还在用 filter 字样)

5.2.2 权限控制

Controller 除了对请求作出相应,另一个重要的事情是做权限控制,只有拥有权限的用户才可以触发方法。权限管理有很多 gem 可用,常用的有 cancanpundit 等。

由于 cancan 已经两年没有维护了,所以Ruby社区推出cancan 的社区版 cancancan

% rails g cancan:ability
  create  app/models/ability.rb

编辑 ability.rb,我们的权限是:当一个 user(已登录)字段 role 是 admin 时,可以管理所有资源,否则,只能管理它自己的资源。

user ||= User.new # guest user (not logged in)
if user.admin?
  can :manage, :all
else
  can :read, :all [1]
  can :manage, Address, :user_id => user.id [2]
end

[1] 非管理员可读所有

[2] 用户管理自己的收货地址

我们给 users 表添加 role 字段:

rails g migration addRoleToUsers role:string

在视图中判断权限:

<%= link_to "Edit", edit_product_path(product) if can? :update, product %>

这里有四个动作可以判断::read:create:update:destroy

我们在 Controller 中增加 load_and_authorize_resource 回调,这个回调将自动加载一个资源,并且进行权限校验,这适合资源管理中的方法:

class ProductsController < ApplicationController
  load_and_authorize_resource

也可以将这个回调分成两个回调,这样方便覆写其中的方法:

class ProductsController < ApplicationController
  load_resource
  authorize_resource

更多文档详见 这里

也可以不实用回调,直接在方法上判断权限,比如判断当前用户是否可以创建商品:

class ProductsController < ApplicationController
  ...
  def create
    authorize! :create, @product
    ...

cancancan 更多用法,详见 wiki

5.2.3 购物车

购物车有多种设计思路,有的会把信息保存在 cookie 中,有的保存在数据库中。

我们将它保存到数据库中,使用 CartItem 这个 Model。当向购物车增加商品时,我们将商品的商品类型(Variant)以及数量保存到购物车中。如果再次购买,会增加该商品类型的数量。

我们将订单的创建过程分为三步,第一步:确认购物车,第二步:填写收货地址,第三部:形成订单,第四部:支付,第五步:支付成功后通知订单。

为了方便管理购物和支付流程,我把这个逻辑单独的放置在 checkout_controller.rb

当我们计算购物车和商品类型价格的时候,经常的出现 line_item.variant.price,这种查询可以通过 Model 中的 delegate 进行改进:

class LineItem < ActiveRecord::Base
  ...
  delegate :price, to: :variant, prefix: true

这样,刚才的查询可以改为 line_item.variant_pricedelegate 方法的 api 在 这里

但是,这种方法会造成过多的查询,所以在确定使用这种方法后,我们可以使用 has_many 中的 includes 选项:

class Order < ActiveRecord::Base
  has_many :line_items, -> { includes :variant }
end

当我们再次查询 line_items 时,会自动的检索关联的 variant,避免多余的 sql 查询。

我们编写代码的时候,有一些代码可能需要优化,有一些功能还待完成,这时可以在代码中增加特殊的注释:

def checkout
  # OPTIMIZE
  # TODO
  # FIXME

使用 rake 命令可以查看代码中的注解

rake notes:optimize/fixme/todo

关注购物车的其他环节,我们可以查看代码演示,它所使用的方法,我们之前已经介绍过了。

5.2.4 支付

订单创建时,它的 payment_stateconfirm,当完成支付后,它的状态改为 paid。这里我们使用支付宝来支付订单。

我们需要安装支付宝的 gem

并且增加初始配置文件 config/initializers/alipay.rb,这里需要填写从支付宝商家服务 申请 的 PID 和 KEY。

Alipay.pid = '申请的 PID'
Alipay.key = '申请的 KEY'

支付宝常用实时到账和担保交易,如果开通了支付宝快捷登陆,在使用实时到账时,可以扫描二维码支付。

支付成功后,通常设定为跳转回订单详细页面,支付宝会通过接口自动通知 notify 方法,我们应该在该方法中更新订单状态,并且通知支付宝是否成功,只需 render text: "success"render text: "fail"

这里有一份非常详尽的支付宝集成方案,欢迎参考。

5.2.5 带分页的数据列表

进入到“我的订单”页面,会有多条订单记录,这里需要对订单进行分页。常用的分页 gem 是 will_paginate。因为我们在使用 bootstrap,所以需要安装 will_paginate-bootstrap

分页的代码非常简单:

class OrdersController < ApplicationController
  ...
  def index
    @orders = Order.paginate(:page => params[:page], :per_page => 20)

页面上:

  <div class="well">
    <%= page_entries_info @orders %>
  </div>
  <%= will_paginate @orders, renderer: BootstrapPagination::Rails %>

为了让 page_entries_info 方法和分页按钮显示中文,我们增加一个新的语言包:

config/locales/will_paginate/zh-CN.yml

除了 will_paginate,还有 kaminari,以及 datatable

5.2.6 datatable

datatable 是传统分页方法的一个极好的替代,当数据量较多,且需要 ajax 加载数据时,可以使用 server 端 datatable 实现,具体请参考 示例列表

当我们的订单数量巨大的时候,我们需要使用 datatable 的 server-side,来减轻分页加载时的压力。这里有一个演示,供大家参考。

上一篇: 控制器中的方法 下一篇: Assets 管理