💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 5.2 控制器中的方法 ## 概要 本课时讲解 Controller 中的回调,权限控制,及如何实现网店的购物车和支付功能,以及使用 datatable 查看订单数据。 ## 知识点 1. 回调 1. 权限设置 1. 状态变更 1. 支付 1. 带分页的数据列表 1. 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 中一样,或者参考[这里](http://guides.rubyonrails.org/action_controller_overview.html#filters)。(注:它还在用 filter 字样) ### 5.2.2 权限控制 Controller 除了对请求作出相应,另一个重要的事情是做权限控制,只有拥有权限的用户才可以触发方法。权限管理有很多 gem 可用,常用的有 [cancan](https://github.com/ryanb/cancan),[pundit](https://github.com/elabs/pundit) 等。 由于 cancan 已经两年没有维护了,所以Ruby社区推出cancan 的社区版 [cancancan](https://github.com/CanCanCommunity/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 ~~~ 更多文档详见 [这里](https://github.com/CanCanCommunity/cancancan/wiki/authorizing-controller-actions)。 也可以不实用回调,直接在方法上判断权限,比如判断当前用户是否可以创建商品: ~~~ class ProductsController < ApplicationController ... def create authorize! :create, @product ... ~~~ cancancan 更多用法,详见 [wiki](https://github.com/CanCanCommunity/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_price`。`delegate` 方法的 api 在 [这里](http://api.rubyonrails.org/classes/Module.html#method-i-delegate)。 但是,这种方法会造成过多的查询,所以在确定使用这种方法后,我们可以使用 `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 ~~~ 关注购物车的其他环节,我们可以查看[代码演示](https://github.com/liwei78/rails-practice-code/tree/master/chapter_5/shop),它所使用的方法,我们之前已经介绍过了。 ### 5.2.4 支付 订单创建时,它的 `payment_state` 为 `confirm`,当完成支付后,它的状态改为 `paid`。这里我们使用支付宝来支付订单。 我们需要安装支付宝的 [gem](https://github.com/chloerei/alipay)。 并且增加初始配置文件 `config/initializers/alipay.rb`,这里需要填写从支付宝商家服务 [申请](https://app.alipay.com/market/index.htm) 的 PID 和 KEY。 ~~~ Alipay.pid = '申请的 PID' Alipay.key = '申请的 KEY' ~~~ 支付宝常用实时到账和担保交易,如果开通了支付宝快捷登陆,在使用实时到账时,可以扫描二维码支付。 支付成功后,通常设定为跳转回订单详细页面,支付宝会通过接口自动通知 `notify` 方法,我们应该在该方法中更新订单状态,并且通知支付宝是否成功,只需 `render text: "success"` 或 `render text: "fail"`。 这里有一份非常详尽的[支付宝集成](https://ruby-china.org/topics/26354)方案,欢迎参考。 ### 5.2.5 带分页的数据列表 进入到“我的订单”页面,会有多条订单记录,这里需要对订单进行分页。常用的分页 gem 是 [will_paginate](https://github.com/mislav/will_paginate)。因为我们在使用 bootstrap,所以需要安装 [will_paginate-bootstrap](https://github.com/bootstrap-ruby/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](https://github.com/amatsuda/kaminari),以及 [datatable](https://datatables.net) ### 5.2.6 datatable datatable 是传统分页方法的一个极好的替代,当数据量较多,且需要 ajax 加载数据时,可以使用 server 端 datatable 实现,具体请参考 [示例列表](#)。 当我们的订单数量巨大的时候,我们需要使用 datatable 的 server-side,来减轻分页加载时的压力。这里有一个[演示](https://github.com/liwei78/datatable-rails4-bootstrap-on-server-side),供大家参考。