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

汇出 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 < AdminController

     def 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"
+        }
+      end
     end

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:00

time.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 change
      create_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_messages
        t.timestamps
      end
    end
  end

执行 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)
+   end

  end

 

在 @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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值