现在摆在我面前有这样一个需求: 用户需要一个报表, 首先这个报表需要在网页上以 table 的形式展示, 然后用户可以将此报表以 CSV, Excel, PDF 的形式下载到本地, 最后用户还希望可以将报表转换为 JSON, XML 等数据以供其他程序使用。 这是一个很繁琐的需求,但是如果我们利用好 Rails 提供的 view 模版以及 respond_to 方法,我们可以很优雅的完成这个需求。 在实际需求中,报表可能会很复杂,但是今天我们的重点不是如何生成报表,而是如何优雅的响应用户的请求,所以我会建立一个简单的 demo 来叙述这一过程。

仅用于笔记学习!

format.html

这个响应很容易实现, 我们直接看代码。

路由的代码,

# config/routes.rb

Rails.application.routes.draw do
   resources :users
end

users 控制器的代码,

# app/controllers/users_controller.rb

class UsersController < ApplicationController
  
  def index
     @users = User.order(:name)
  end

end

相关视图的代码,

<table>
   <tr>
      <td>
            ....
      </td>
      <% users.each do |user| %>
          <td>
            ....
          </td>
      <% end %>
   </tr>
</table>

format.csv

为了实现 csv 的输出,我们不需要增加控制器或者 action, 也不需要增加判断逻辑,只需要为 respond_to 方法引入 format.csv 即可。

users 控制器代码,

# app/controllers/users_controller.rb


class UsersController < ApplicationController
   
  def index
     @users = User.all
   
     respond_to do |format|
        format.html
        format.csv
     end
   
  end

end

相关视图的代码, 此时我们增加了一个 index.csv.erb 文件,

# app/views/users/index.csv.erb


id,name,gender,age
<% @users.each do |user| %>
  <%= [user.id, user.name, user_human_gender, user.age].join(',') %>
<% end %>

现在我们使用浏览器访问 http://localhost:3000/users.csv, 注意 csv 后缀,这表明我们的 请求格式是 csv, 然后服务器会识别此格式,并且返回给我们一个 csv 文件。

返回的 users.csv 内容,

id, name, gender, age
3,aaa,男,25

format.xls

为了实现 format.xls 我们首先需要将 xls 注册到 Mime Type 中去。

# config/initializers/mime_types.rb

Mime::Type.register "application/vnd.ms-excel", :xls

然后在 users 控制器里增加 format.xls,

# app/controllers/users_controller.rb


class UsersController < ApplicationController
   
  def index
     @users = User.all
   
     respond_to do |format|
        format.html
        format.csv
        format.xls
     end
   
  end

end

最后实现 xls 视图, 其实这个视图的内容是一个 xml 文档,浏览器收到此 xml 文档时会将其转换为 xls 文件。

# app/views/users/index.xls.erb

<?xml version="1.0"?>
<workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:html="http://www.w3.org/TR/REC-html40">
<worksheet ss:Name='Sheet1'>
<Table>
  <Row>
      <Cell><Data ss:Type="String">ID</Data></Cell>
      <Cell><Data ss:Type="String">Name</Data></Cell>
      <Cell><Data ss:Type="String">genda</Data></Cell>
      <Cell><Data ss:Type="String">age</Data></Cell>
  </Row>
  <% users.each do |user|%>
     <Row>
      <Cell><Data ss:Type="Number"><%= user.id %></Data></Cell>
      <Cell><Data ss:Type="String"><%= user.name %></Data></Cell>
      <Cell><Data ss:Type="String"><%= user.human_gender %></Data></Cell>
      <Cell><Data ss:Type="Number"><%= user.age %></Data></Cell>
  </Row>    
  <% end %>
</Table>
</worksheet>
</workbook>

使用浏览器访问 http://localhost:3000/users.xls, 我们将下载到一个 xls 文件: users.xls。

format.pdf

我们使用 prawn 和 prawn-table 两个 gem 共同工作来生成 PDF。

# Gemfile

gem 'prawn'
gem 'prawn-table'

users 控制器代码,

# app/controllers/users_controller.rb


class UsersController < ApplicationController
   
  def index
     @users = User.all
   
     respond_to do |format|
        format.html
        format.csv
        format.xls
# 将响应的Content-Disposition 设置为 attachment
# 这样浏览器会主动下载PDF文档 format.pdf {response.headers['Content-Disposition'] = 'attachment' } end end end

我们注意到在 format.pdf 的 block 中我们将响应头的 Content-Disposition 设置为了 attachment 这样可以确保浏览器会主动下载 PDF 文档。

建立视图文件 index.pdf.ruby, 这个视图文件的内容其实就是一段 ruby 代码, 并且这个视图最后会直接由 ruby 解释器处理。

# app/views/users/index.pdf.ruby


pdf = Prawn::Document.new

pdf.font Rails.root.join("app/assets/fonts/simhei.ttf")

pdf.text "users list", size: 80

table_data = []
headers = ["ID", "姓名", "性别", "年龄"]
table_data << headers
items = @users.map {|user|
   table_data << [user.id, user.name, user.human_gender, user.age]
}

pdf.table table_data

pdf.render

我们需要用到字体文件路劲:app/assets/fonts/simhei.ttf

format.json

我们使用 jbuilder 来渲染 json 模版。

users 控制器代码,

# app/controllers/users_controller.rb


class UsersController < ApplicationController
   
  def index
     @users = User.all
   
     respond_to do |format|
        format.html
        format.csv
        format.xls
# ... format.pdf {response.headers['Content-Disposition'] = 'attachment' } format.json end end end

建立 index.json.jbuilder 模版,

# app/views/users/index.json.jbuilder


json.users @users, :id, :name, :human_gender, :age

使用浏览器访问 http://localhost:3000/users.json, 我们将得到: 

{
   users: [
      {id: 3,
        name: "aaa",
        ....
      },
      {
      }
     ...
   ]
}

format.xml

我们使用 builder 来渲染 xml 模版。

users 控制器代码,

# app/controllers/users_controller.rb


class UsersController < ApplicationController
   
  def index
     @users = User.all
   
     respond_to do |format|
        format.html
        format.csv
        format.xls
        # ... 
        format.pdf {response.headers['Content-Disposition'] = 'attachment' }
        format.json
        format.xml
     end
   
  end

end

建立 index.xml.builder,

# app/views/users/index.xml.builder


xml.instruct!
xml.users do 
   @users.each do |user|
      xml.user do 
         xml.id       user.id
         xml.name  user.name
         xml.gender  user.human_gender
         xml.age  user.age
      end
   end
end

使用浏览器访问 http://localhost:3000/users.xml, 我们可以得到,

<?xml version="1.0" encoding="UTF-8"?>
<users>
   <user>
      <id>3</id>
      <name>3</name>
      <gender>3</gender>
      <age>3</age>
   </user>
    .....
</users>

小结

  1. 视图模版的命名规则是, :action.:format.:handler, 比如 index.html.erb 表示 模版对应的 action 是 index, 响应格式是 html, 模版处理器是 erb。

  2. 利用好 Rails 提供的 MVC(Model-View-Controller) 架构能够让程序变的优雅且易于维护。