5.2 控制器中的方法

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

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

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

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

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

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

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

    1. class ProductsController < ApplicationController
    2. skip_before_action :authenticate_user!, only: [:index, :show, :top]

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

    5.2.2 权限控制

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

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

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

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

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

    [1] 非管理员可读所有

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

    我们给 users 表添加 role 字段:

    1. rails g migration addRoleToUsers role:string

    在视图中判断权限:

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

    1. class ProductsController < ApplicationController
    2. load_and_authorize_resource

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

    1. class ProductsController < ApplicationController
    2. load_resource
    3. authorize_resource

    更多文档详见 。

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

    1. class ProductsController < ApplicationController
    2. ...
    3. authorize! :create, @product
    4. ...

    cancancan 更多用法,详见 wiki

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

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

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

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

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

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

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

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

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

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

    1. def checkout
    2. # OPTIMIZE
    3. # TODO
    4. # FIXME
    1. rake notes:optimize/fixme/todo

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

    5.2.4 支付

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

    我们需要安装支付宝的 gem

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

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

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

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

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

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

    分页的代码非常简单:

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

    页面上:

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

    1. config/locales/will_paginate/zh-CN.yml

    除了 will_paginate,还有 ,以及 datatable

    5.2.6 datatable

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

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