如何使用Active Storage读取.xlsx文件数据
我懂你现在的困扰——用Active Storage存了XLSX文件后,那些依赖物理文件路径的读写gem(比如creek、SimpleXlsxReader)没法直接处理Active Storage返回的二进制数据,之前存本地的方法又显得繁琐。下面给你几个实用的解决方案:
先回顾下你的场景
你的模型定义是这样的:
class DataSheet < ApplicationRecord belongs_to :admin has_one_attached :excel_file after_save :create_user end
之前尝试直接用SimpleXlsxReader.open(self.excel_file.blob.download)时,遇到了string contains null byte的错误,本质是因为这些gem需要的是文件路径/IO流,而不是 raw 的二进制字符串。
方案一:用临时文件中转(兼容所有依赖路径的gem)
Ruby的Tempfile可以帮我们把二进制数据临时写入一个文件,用完自动清理,完美适配需要文件路径的gem:
def create_user # 从Active Storage下载二进制数据 excel_binary = self.excel_file.download # 创建临时XLSX文件,后缀确保gem能识别格式 Tempfile.open(["temp_xlsx", ".xlsx"]) do |temp_file| temp_file.write(excel_binary) temp_file.rewind # 重置文件指针到开头,方便读取 # 这里替换成你要用的gem,比如Creek book = Creek::Book.new(temp_file.path) book.sheets.each do |sheet| sheet.rows.each do |row| # 执行你的用户创建逻辑 # User.create(...) puts row.inspect end end end end
这个方法的优势是兼容性拉满,不管你用creek、roo还是SimpleXlsxReader,都能直接套用,而且临时文件会自动销毁,不会留下垃圾文件。
方案二:用支持IO流的gem(更高效,无需写文件)
有些XLSX解析gem支持直接读取IO流,比如roo,这样就不用绕临时文件了,效率更高:
首先确保安装roo:
gem install roo
然后代码可以这么写:
def create_user excel_binary = self.excel_file.download # 把二进制字符串包装成IO流对象 io_stream = StringIO.new(excel_binary) # 用roo直接打开这个流 xlsx = Roo::Excelx.new(io_stream) # 遍历工作表和数据行 xlsx.sheets.each do |sheet_name| xlsx.sheet(sheet_name).each_row_streaming do |row| # 处理每行数据,比如提取值 row_values = row.map(&:value) puts row_values.inspect # 这里写你的用户创建逻辑 end end end
StringIO把二进制数据转换成了gem能识别的IO流格式,跳过了物理文件的写入步骤,性能更好。
为什么之前的尝试会报错?
你之前直接传download返回的二进制字符串给SimpleXlsxReader.open,是因为这个方法只接受文件路径字符串或者IO对象,而raw二进制字符串里包含的null字节会让底层的rubyzip库解析失败。用Tempfile或StringIO把数据包装成符合要求的格式,就能解决这个问题。
内容的提问来源于stack exchange,提问作者TheVinspro

