汇出 CSV 档案

需求:后台可以汇出报名资料

有时候后台功能做再多,也不如 Microsoft Excel 或 Apple Numbers 试算表软件提供的分析功能,这时候如果有汇出功能,就可以很方便地把资料汇出来,用软件来打开浏览。

CSV 逗号分隔值(Comma-separated-values)是一种简单的资料格式,其文件以纯文本形式存储表格数据(数字和文本)。一行一笔资料,不同字段用逗号隔开。

例子:

app/views/admin/event_registrations/index.html.erb

  <p><%= link_to "汇出 CSV", admin_event_registrations_path(:format => :csv)  %></p>

app/controllers/admin/event_registrations_controller.rb

+  require 'csv'class Admin::EventRegistrationsController < AdminControllerdef index# (略)+      respond_to do |format|
+        format.html
+        format.csv {
+          @registrations = @registrations.reorder("id ASC")
+          csv_string = CSV.generate do |csv|
+            csv << ["报名ID", "票种", "姓名", "状态", "Email", "报名时间"]
+            @registrations.each do |r|
+              csv << [r.id, r.ticket.name, r.name, t(r.status, :scope => "registration.status"), r.email, r.created_at]
+            end
+          end
+          send_data csv_string, :filename => "#{@event.friendly_id}-registrations-#{Time.now.to_s(:number)}.csv"
+        }
+      endend

CSV 是 Ruby 内建的库,这里第一行需要先 require 它。使用 CSV.generate 可以产生出 csv_string 字符串,也就是要输出的 CSV 资料,接着透过 send_data 传给浏览器进行档案下载。

Time.now.to_s(:number)生成"20180811174126", to_s是简写:to_formatted_s(format=:default)

time = Time.now                    # => 2007-01-18 06:10:17 -06:00time.to_formatted_s(:time)         # => "06:10"
time.to_s(:time)                   # => "06:10" time.to_formatted_s(:db) # => "2007-01-18 06:10:17" time.to_formatted_s(:number) # => "20070118061017" time.to_formatted_s(:short) # => "18 Jan 06:10"

send_data(data, options={})


Axlsx-Rails — Spreadsheet templates for Rails(470?)

安装看git

CSV 有个缺点,就是用 Microsoft Excel 打开时默认会变成乱码。这是因为汇出的 CSV 的字串编码是 UTF-8,但是 Excel 默认会用本地编码,例如中国大陆地区用 GB 2312

解决办法有二:

方法一: 以记事本开启后储存,再以 Excel 开启即可正常显示。

方法二: 开启 Excel 软件,新增空白活页簿(Workbook),然后在上方功能选项中点选「资料(Data)」->「取得外部资料 Get External Data」->「从文字档 From Text File...」→「选择汇出的 CSV 档案」→ 选择符号分隔(Delimited)、选择 File origin 编码是 Unicode (UTF-8) → 选择分隔符号是 Comma 逗点,即可正常显示。

如果上述 Excel 打开 CSV 档案的解法没办法接受的话,那只好想办法汇出 Excel 专用的 xlsx 格式了。这需要额外装 gem。

使用 axlsx_rails gem(470✨)。

⚠️ Rails 4.2, 5.0 or 5.1 (tested), 还不支持5.2

沿用上例子:安装3个gem后:

app/views/admin/event_registrations/index.html.erb

+ <%= link_to "汇出 Excel", admin_event_registrations_path(:format => :xlsx)

app/controllers/admin/event_registrations_controller.rb

在index中加上 format.xlsx

创建template:

app/views/admin/event_registrations/index.xlsx.axlsx

⚠️:需要使用action_name.xlsx.axlsx格式作为名字。

wb = xlsx_package.workbook
wb.add_worksheet(name: "Buttons") do |sheet| sheet.add_row ["报名ID", "票种", "姓名", "状态", "Email", "报名时间"] @registrations.each do |r| sheet.add_row [r.id, r.ticket.name, r.name, t(r.status, :scope => "registration.status"), r.email, r.created_at] end end



导入 CSV 档案 (Rake直接导入)

假设我们拿到这样的 CSV 档案,接下来要如果汇入数据库呢?总不能一笔一笔输入太慢了,当然要用写程序的方式来做汇入。

如果汇入是程序员的一次性的任务,我们可以不需要实作 Web UI,只需要一个 rake 任务可以执行就好了。

编辑 lib/tasks/dev.rake,让我们新增一个 import_registration_csv_file 任务

lib/tasks/dev.rake

+ require 'csv'namespace :dev do+   task :import_registration_csv_file => :environment do
+     event = Event.find_by_friendly_id("fullstack-meetup")
+     tickets = event.tickets
+
+     success = 0
+     failed_records = []
+
+     CSV.foreach("#{Rails.root}/tmp/registrations.csv") do |row|
+       registration = event.registrations.new( :status => "confirmed",
+                                    :ticket => tickets.find{ |t| t.name == row[0] },
+                                    :name => row[1],
+                                    :email => row[2],
+                                    :cellphone => row[3],
+                                    :website => row[4],
+                                    :bio => row[5],
+                                    :created_at => Time.parse(row[6]) )
+
+       if registration.save
+         success += 1
+       else
+         failed_records << [row, registration]
+       end
+     end
+
+     puts "总共汇入 #{success} 笔,失败 #{failed_records.size} 笔"
+
+     failed_records.each do |record|
+       puts "#{record[0]} ---> #{record[1].errors.full_messages}"
+     end
+
+   end

执行 rake dev:import_registration_csv_file 就会执行了汇入的操作到 fullstack-meetup 这个活动

解说:

  1. 和汇出 CSV 一样,Ruby 内建了 CSV 库可以解析 CSV,所以第一行先 require 'csv'
  2. CSV.foreach 会打开这个 CSV 档案跑循环,每笔资料就是一行 row,那一行的第一列是 row[0]、第二列是 row[1]。只要依序塞给 event.registrations.new 即可。
  3. CSV 中的票种是string,但是转进我们的数据库中需要转换成 Ticket model,因此这里写成 tickets.find{ |t| t.name == row[0] } 用票种名称去找是哪一个对象。
  4. 时间也是一样,透过 Time.parse 把string转成时间对象
  5. 因为汇入会一次汇入非常多笔,我们希望不管每笔资料 save 成功或失败,都能跑完全部资料,最后印出一个总结:告诉我们总共几笔成功,总共几笔失败,是哪些笔失败又是什么原因。

把汇入档案存下来纪录过程

如果这是一个面向终端用户的功能,会需要实作的更完整,例如:

  1. 把上传的档案先存下来,先给用户预览字段顺序是否正确、有多少数据要汇入、哪些数据有问题
  2. 确认后,才开始汇入数据库
  3. 可以浏览过往的汇入历史纪录

执行 rails g model registration_import,这个 Model 会存下上传的 CSV 档案,并记录汇入的结果。

编辑 db/migrate/2017XXXXXX4512_create_registration_imports.rb

db/migrate/2017XXXXXX4512_create_registration_imports.rb

  class CreateRegistrationImports < ActiveRecord::Migration[5.0]def changecreate_table :registration_imports do |t|
+       t.string :status
+       t.string :csv_file
+       t.integer :event_id, :index => true
+       t.integer :user_id
+       t.integer :total_count
+       t.integer :success_count
+       t.text :error_messagest.timestampsendendend

执行 rake db:migrate

执行 rails g uploader registration_import_csv

编辑 app/models/registration_import.rb,其中的 process! 方法就是要执行的汇入操作。

app/models/registration_import.rb

+ require 'csv'class RegistrationImport < ApplicationRecord+   mount_uploader :csv_file, RegistrationImportCsvUploader
+
+   validates_presence_of :csv_file
+
+   belongs_to :event
+   belongs_to :user
+
+   serialize :error_messages, JSON
+
+   def process!
+     csv_string = self.csv_file.read.force_encoding('utf-8')
+     tickets = self.event.tickets
+
+     success = 0
+     failed_records = []
+
+     CSV.parse(csv_string) do |row|
+       registration = self.event.registrations.new( :status => "confirmed",
+                                    :ticket => tickets.find{ |t| t.name == row[0] },
+                                    :name => row[1],
+                                    :email => row[2],
+                                    :cellphone => row[3],
+                                    :website => row[4],
+                                    :bio => row[5],
+                                    :created_at => Time.parse(row[6]) )
+
+       if registration.save
+         success += 1
+       else
+         failed_records << [row, registration.errors.full_messages]
+       end
+     end
+
+     self.status = "imported"
+     self.success_count = success
+     self.total_count = success + failed_records.size
+     self.error_messages = failed_records
+
+     self.save!
+   end  end

编辑 app/models/event.rb

app/models/event.rb

   has_many :registrations, :dependent => :destroy
+  has_many :registration_imports, :dependent => :destroy

编辑 config/routes.rb

config/routes.rb

  namespace :admin do# (略)resources :events do
+      resources :registration_imports

编辑 app/views/admin/event_registrations/index.html.erb 加上一个按钮

app/views/admin/event_registrations/index.html.erb

  <p class="text-right"><%= link_to "New Registration", new_admin_event_registration_path(@event), :class => "btn btn-primary" %>
+   <%= link_to "Import Registration", admin_event_registration_imports_path(@event), :class => "btn btn-primary" %></p>

执行 rails g controller admin::registration_imports

编辑 app/controllers/admin/registration_imports_controller.rb

app/controllers/admin/registration_imports_controller.rb

- class Admin::RegistrationImportsController < ApplicationController
+ class Admin::RegistrationImportsController < AdminController+   before_action :require_editor!
+   before_action :find_event
+
+   def index
+     @imports = @event.registration_imports.order("id DESC")
+   end
+
+   def create
+     @import = @event.registration_imports.new(registration_import_params)
+     @import.status = "pending"
+     @import.user = current_user
+
+     if @import.save
+       @import.process!
+       flash[:notice] = "汇入完成"
+     end
+
+     redirect_to admin_event_registration_imports_path(@event)
+   end
+
+   protected
+
+   def find_event
+     @event = Event.find_by_friendly_id!(params[:event_id])
+   end
+
+   def registration_import_params
+     params.require(:registration_import).permit(:csv_file)
+   endend

在 @import.save 之后,随即呼叫 process! 开始汇入。

另外,⚠️:这里的用户需求第1步和第2步这里并没有实现。

如果要实现的话,标红的4行代码,去掉@import.process!

把process方法单独放入一个controller#action中,并在index.html.erb中添加一个确认的button

在点击button后才会汇入数据到数据库中。

(或者,新增一个Model,数据临时存入这个model层,然后预览一下,该删除的删除,该保留的保留

然后再,存入真正储存数据的Model。)

新增 app/views/admin/registration_imports/index.html.erb 显示档案上传的输入框,以及历史汇入纪录。

app/views/admin/registration_imports/index.html.erb

<h1><%= @event.name %> / Registrations Import</h1> <%= form_for [:admin, @event, RegistrationImport.new] do |f| %> <div class="form-group"> <%= f.label :csv_file %> <%= f.file_field :csv_file, :required => true, :class => "form-control" %> </div> <div class="form-group"> <%= f.submit "送出", :class => "btn btn-primary" %> </div> <% end %> <table class="table"> <tr> <th>ID</th> <th>状态</th> <th>CSV档案</th> <th>总笔数</th> <th>汇入成功笔数</th> <th>错误讯息</th> </tr> <% @imports.each do |import| %> <tr> <td><%= import.id %></td> <td><%= import.status %></td> <td><%= link_to import.csv_file.url, import.csv_file.url %></td> <td><%= import.total_count %></td> <td><%= import.success_count %></td> <td> <ul> <% Array(import.error_messages).each do |e| %> <li><%= e[0] %> ----> <strong><%= e[1] %></strong></li> <% end %> </ul> </td> </tr> <% end %> </table> 

这样就完工啦。

转载于:https://www.cnblogs.com/chentianwei/p/9460616.html

导出csv文件,导出axlsx文件。gem 'Axlsx-Rails' (470);导入csv文件。相关推荐

  1. Android修改R文件包名程,以及批量修改导入R文件

    当我们创建Android工程时,R文件所在包名即初始创建的包名,当我们后期想修改R文件的包名时,其实只需要在 AndroidManifest.xml中将package的名字修改了保存即可, 或者通过 ...

  2. mysql的indata文件_【数据分析】MySQL之不能导入本地文件“Loading local data is disable;”...

    今天在使用"利用MySQL的命令行进行CSV文件的导入"时,遇到了这样一个问题: 提示本地文件无法导入,必须要"同时获得客户端和服务器端的许可". 很难受,于是 ...

  3. thinkphh5导入php文件,Thinkphp5.0如何导入导出

    ThinkPHP是一个免费开源的,快速.简单的面向对象的轻量级PHP 开发框架,遵循 Apache2 开源协议发布,是为了简化企业级应用开发和敏捷WEB应用开发而诞生的.本篇文章我们主要跟大家分享Th ...

  4. MySQL导入csv文件内容到Table及数据库的自增主键设置

    写在前面 目的是测试将csv文件内容导入到表中, 同时记录一下自增主键的设置. 测试采用MySQL8.0. 新建表customer_info如下, 未设置主键. 修改上表, 添加主键id, 并设置为自 ...

  5. mysql linux导入csv主键,MySQL导入csv文件内容到Table及数据库的自增主键设置

    写在前面 目的是测试将csv文件内容导入到表中, 同时记录一下自增主键的设置. 测试采用MySQL8.0. 新建表customer_info如下, 未设置主键. 修改上表, 添加主键id, 并设置为自 ...

  6. 关于使用Java后台导入excel文件,读取数据后,更新数据库,并返回数据给到前端的相关问题总结

    在之前的项目中,使用到了Java后台读取excel文件数据的功能点,本想着该功能点已经做过了,这一类的应该都大差不离,不过在刚结束的一个项目中,现实给我深深的上了一课,特此编写此片博客,以作记录,并给 ...

  7. Linux下导入SQL文件及MySQL常用命令

    Linux系统下可以直接复制SQL语句进行导入,但是这个方法容易出现导入失败的现象,我们可以直接导入sql文件,可以大大提高成功率. 在导入sql文件前,先说一下MySQL的常用命令. MySQL的登 ...

  8. mysql命令行导入dmp文件_cmd 命令行 imp导入dmp oracle数据库

    简介:  开发者在开发过程中使用到Oracle数据库时,总是避免不了要还原数据库.通常Oracle导出的备份数据库文件为dmp文件.本篇介绍了下怎么导入dmp文件到对应的Oracle数据库中. 步骤一 ...

  9. 如何导入asl文件?ps制作知识

    asl文件是Photoshop里的图层样式文件!一个图层包里可以有很多个图层样式! -fuSCj   T j9;".   如何导入asl文件? 6c>t|=Ss(   FiJJe   ...

  10. Perl导入代码文件

    从函数复用开始:eval和do执行perl文件 当我们定义了一个功能比较通用的子程序,比如获取数值的绝对值.想要到处使用这个子程序,就得不断复制.粘贴这段绝对值函数的定义文本.显然,这是不太理想的方式 ...

最新文章

  1. api.533.net 文章迁移计划
  2. C语言 实现一个函数判断year是不是润年
  3. k8s部署nacos集群:3个pod、连接外部mysql读写分离集群
  4. java程序员面试中的5个杀手锏问题
  5. uwp连接mysql数据库_在 UWP 应用中使用 SQLite 数据库
  6. mvc json 乱码_你了解JSON吗?——Jackson、FastJson在SpringMVC中的简单使用
  7. java cxf服务端_webservice概述及cxf在Java开发中应用(二) 简单搭建cxf服务端
  8. mysql 免安装 自启动_MYSQL在Win下免安装zip
  9. Oracle的expdp导出、impdp导出命令
  10. 顶会两篇论文连发,华为云医疗AI低调中崭露头角
  11. 网络安全渗透测试自学
  12. iTunes降级操作
  13. 卫星控制类操作系统VAX/VMS简介
  14. 索尼Z2(L50t)刷CM12教程
  15. 机器学习中的置信区间与置信度
  16. Android开发: 美化输入框EditText
  17. 结构体对齐和补齐(详细解释)
  18. 合格资本工具_《商业银行资本管理办法附件》附件1——资本工具合格标准.doc...
  19. 掌财社:津投期货新股为什么容易出现天地板?
  20. vue3实现活跃度(热力图)

热门文章

  1. Hibernate多列作为联合主键(六)
  2. 【渝粤教育】国家开放大学2018年秋季 0727-22T思想道德修养与法律基础 参考试题
  3. [渝粤教育] 中国地质大学 高层建筑施工 复习题 (2)
  4. [渝粤教育] 西南科技大学 管理信息系统 在线考试复习资料(2)
  5. 软件类配置(六)【ubuntu16.04安装opencv3.4.1】
  6. 车道线检测-python实现
  7. 梯度下降来龙去脉附matlab代码
  8. 那些聪明人都是怎么提高情商的?
  9. NOIP2018初赛 解题报告
  10. POJ1064 Cable master 【二分找最大值】