iT邦幫忙

2021 iThome 鐵人賽

DAY 18
0
自我挑戰組

Ruby on Rails 與它們相關的東西 II系列 第 18

Day18 - 匯入 excel-應用篇

前言

使用者除了有匯出報表的需求外,也會有需要大量匯入的情境,匯入會更需要驗證輸入的資料,有可能是空的資料、跟預期輸入 Excel 完全不同、重複的資料 (需看情境是否接受重複的資料)、部分資料輸入錯誤...等

實作

這邊會需要使用到 rooroo-xls 這 2 個 Gem ,可參考此 pr

# Gemfile

gem 'roo', '~> 2.8', '>= 2.8.3'
gem 'roo-xls', '~> 1.2'

以下範例有做簡單的驗證,像是判斷輸入的 Excel 標題列是否一樣、是否有資料、是否有重複的資料、是否與 DB 資料重複

大量匯入時,可改用 activerecord-import 處理,可參考這篇文章

# app/services/shops_excel/parser.rb

module ShopsExcel
  class ParseError < StandardError; end

  class Parser
    def execute(file_path)
      list = read_excel(file_path)
      sheet = list.sheet(0)
      validate_title_names!(sheet)
      validate_shop_names!(sheet)
      process_shops(sheet)
    rescue ParseError => e
      subject = "[#{self.class} Error] #{e.message}, \nbacktrace: #{e.backtrace}"
      Rails.logger.error subject
    end

    private

    def read_excel(file_path)
      Roo::Spreadsheet.open(file_path)
    end

    def validate_title_names!(sheet)
      return if sheet.row(1) == ShopsExcel::Generator::TITLES

      raise ParseError, '輸入資料有誤,比對 Excel 標頭與預期不同'
    end

    def validate_shop_names!(sheet)
      all_shop_names = sheet.column(1)[1..-1]
      raise ParseError, '無資料' if all_shop_names.blank?
      raise ParseError, '有重複的商家名稱,請檢查' if all_shop_names.uniq.size != all_shop_names.size

      existed_shop_names = Shop.where(name: all_shop_names).pluck(:name)
      return if existed_shop_names.blank?

      raise ParseError, "有 #{existed_shop_names.size} 筆已建立過: #{existed_shop_names.join(', ')}"
    end

    def process_shops(sheet)
      shops = []
      (2..sheet.last_row).each do |index|
        col_values = sheet_row(sheet, index)
        shop = Shop.new(col_values)
        shops << shop
      end
      Shop.import(shops)
    end

    def sheet_row(sheet, index)
      {
        name: sheet.row(index)[0],  # 商家名稱
        email: sheet.row(index)[1], # 信箱
        note: sheet.row(index)[2],  # 備註
      }
    end
  end
end

rails console 輸入以下

範例的 Excel 檔

# rails console

file = Rails.root.join("data/商家總表.xlsx").to_s
ShopsExcel::Parser.new.execute(file)

小結

匯入要做的基本驗證蠻多的,需考量各種情境,並盡可能地去預防處理,和匯出一樣,建議都是放 sidekiq 處理,完成後再寄信給使用者,告知匯入結果

參考資料

  1. Roo GitHub

鐵人賽文章連結:https://ithelp.ithome.com.tw/articles/10272591
medium 文章連結:https://link.medium.com/Teq2MzayRjb
本文同步發布於 小菜的 Blog https://riverye.com/

備註:之後文章修改更新,以個人部落格為主


上一篇
Day17 - 匯出 excel-測試篇
下一篇
Day19 - 匯入 excel-測試篇
系列文
Ruby on Rails 與它們相關的東西 II30

尚未有邦友留言

立即登入留言