Rails 5.2 使用 ActiveStorage 和 Creek 导入 XLSX
Rails 5.2 Import XLSX with ActiveStorage and Creek
我有一个名为 ImportTemp
的模型,用于将导入的 XLSX 文件存储到数据库中。我正在使用 ActiveStorage 来存储文件。
这是模型代码:
class ImportTemp < ApplicationRecord
belongs_to :user
has_one_attached :file
has_one_attached :log_result
end
这是我的导入控制器代码:
def import
# Check filetype
case File.extname(params[:file].original_filename)
when ".xlsx"
# Add File to ImportFile model
import = ImportTemp.new(import_type: 'UnitsUpload', user: current_user)
import.file.attach(params[:file])
import.save
# Import unit via sidekiq with background jobs
ImportUnitWorker.perform_async(import.id)
# Notice
flash.now[:notice] = "We are processing your xlsx, we will inform you after it's done via notifications."
# Unit.import_file(xlsx)
else flash.now[:error] = t('shared.info.unknown')+": #{params[:file].original_filename}"
end
end
上传 xlsx 文件后,将在 sidekiq 中处理导入。这是工作代码(实际上仍然没有执行导入):
class ImportUnitWorker
include Sidekiq::Worker
sidekiq_options retry: false
def perform(file_id)
import_unit = ImportTemp.find(file_id)
# Open the uploaded xlsx to Creek
creek = Creek::Book.new(Rails.application.routes.url_helpers.rails_blob_path(import_unit.file, only_path: true))
sheet = creek.sheets[0]
puts "Opening Sheet #{sheet.name}"
sheet.rows.each do |row|
puts row
end
units = []
# Unit.import(units)
end
但是在我尝试之后,它给我错误:
Zip::Error (File /rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBCZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--3960b6ba5b55f7004e09967d16dfabe63f09f0a9/2018-08-10_10_39_audit_gt.xlsx not found)
但是如果我尝试用我的浏览器打开它,link 看起来像这样:
http://localhost:3000/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBCZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--3960b6ba5b55f7004e09967d16dfabe63f09f0a9/2018-08-10_10_39_audit_gt.xlsx
正在运行,xlsx 已下载。我的问题是它有什么问题?为什么在 sidekiq 中找不到该文件?
Rails.application.routes.url_helpers.rails_blob_path
不是 return 磁盘上文件的路径。相反,它 return 是一个可以与主机名结合生成 URL 的路径,用于下载文件,用于链接。
您有两个选择:
如果您希望 ImportUnitWorker
对正在使用的存储服务无动于衷,请将文件“下载”到磁盘上的临时文件中。切换到 Rails master 并使用 ActiveStorage::Blob#open
:
def perform(import_id)
import = ImportTemp.find(import_id)
units = []
import.file.open do |file|
book = Creek::Book.new(file.path)
sheet = creek.sheets[0]
# ...
end
Unit.import(units)
end
如果您不介意ImportWorker
知道您使用磁盘服务,请向服务询问磁盘上文件的路径。 ActiveStorage::Service::DiskService#path_for(key)
在Rails5.2中是private的,所以要么强行用send
调用,要么升级到Railsmaster,这里是public:
def perform(import_id)
import = ImportTemp.find(import_id)
units = []
path = ActiveStorage::Blob.service.send(:path_for, import.file.key)
book = Creek::Book.new(path)
sheet = creek.sheets[0]
# ...
Unit.import(units)
end
我最终按照 George Claghorn 的建议使用了 Tempfile
。我不知道这是否是最佳解决方案或最佳实践,但它现在对我有用。我将在等待 Rails 6 稳定版推出 ActiveStorage::Blob#open
功能时使用此解决方案。
def perform(file_id)
import = ImportTemp.find(file_id)
temp_unit = Tempfile.new([ 'unit_import_temp', '.xlsx' ], :encoding => 'ascii-8bit')
units = []
begin
# Write xlsx from ImportTemp to Tempfile
temp_unit.write(import.file.download)
# Open the temp xlsx to Creek
book = Creek::Book.new(temp_unit.path)
sheet = book.sheets[0]
sheet.rows.each do |row|
# Skip the header
next if row.values[0] == 'Name' || row.values[1] == 'Abbreviation'
cells = row.values
# Add cells to new Unit
unit = Unit.new(name: cells[0], abbrev: cells[1], desc: cells[2])
units << unit
end
# Import the unit
Unit.import(units)
ensure
temp_unit.close
temp_unit.unlink # deletes the temp file
end
end
现在的答案似乎是(除非我遗漏了什么):
Creek::Book.new file.service_url, check_file_extension: false, remote: true
我有一个名为 ImportTemp
的模型,用于将导入的 XLSX 文件存储到数据库中。我正在使用 ActiveStorage 来存储文件。
这是模型代码:
class ImportTemp < ApplicationRecord
belongs_to :user
has_one_attached :file
has_one_attached :log_result
end
这是我的导入控制器代码:
def import
# Check filetype
case File.extname(params[:file].original_filename)
when ".xlsx"
# Add File to ImportFile model
import = ImportTemp.new(import_type: 'UnitsUpload', user: current_user)
import.file.attach(params[:file])
import.save
# Import unit via sidekiq with background jobs
ImportUnitWorker.perform_async(import.id)
# Notice
flash.now[:notice] = "We are processing your xlsx, we will inform you after it's done via notifications."
# Unit.import_file(xlsx)
else flash.now[:error] = t('shared.info.unknown')+": #{params[:file].original_filename}"
end
end
上传 xlsx 文件后,将在 sidekiq 中处理导入。这是工作代码(实际上仍然没有执行导入):
class ImportUnitWorker
include Sidekiq::Worker
sidekiq_options retry: false
def perform(file_id)
import_unit = ImportTemp.find(file_id)
# Open the uploaded xlsx to Creek
creek = Creek::Book.new(Rails.application.routes.url_helpers.rails_blob_path(import_unit.file, only_path: true))
sheet = creek.sheets[0]
puts "Opening Sheet #{sheet.name}"
sheet.rows.each do |row|
puts row
end
units = []
# Unit.import(units)
end
但是在我尝试之后,它给我错误:
Zip::Error (File /rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBCZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--3960b6ba5b55f7004e09967d16dfabe63f09f0a9/2018-08-10_10_39_audit_gt.xlsx not found)
但是如果我尝试用我的浏览器打开它,link 看起来像这样:
http://localhost:3000/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBCZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--3960b6ba5b55f7004e09967d16dfabe63f09f0a9/2018-08-10_10_39_audit_gt.xlsx
正在运行,xlsx 已下载。我的问题是它有什么问题?为什么在 sidekiq 中找不到该文件?
Rails.application.routes.url_helpers.rails_blob_path
不是 return 磁盘上文件的路径。相反,它 return 是一个可以与主机名结合生成 URL 的路径,用于下载文件,用于链接。
您有两个选择:
如果您希望
ImportUnitWorker
对正在使用的存储服务无动于衷,请将文件“下载”到磁盘上的临时文件中。切换到 Rails master 并使用ActiveStorage::Blob#open
:def perform(import_id) import = ImportTemp.find(import_id) units = [] import.file.open do |file| book = Creek::Book.new(file.path) sheet = creek.sheets[0] # ... end Unit.import(units) end
如果您不介意
ImportWorker
知道您使用磁盘服务,请向服务询问磁盘上文件的路径。ActiveStorage::Service::DiskService#path_for(key)
在Rails5.2中是private的,所以要么强行用send
调用,要么升级到Railsmaster,这里是public:def perform(import_id) import = ImportTemp.find(import_id) units = [] path = ActiveStorage::Blob.service.send(:path_for, import.file.key) book = Creek::Book.new(path) sheet = creek.sheets[0] # ... Unit.import(units) end
我最终按照 George Claghorn 的建议使用了 Tempfile
。我不知道这是否是最佳解决方案或最佳实践,但它现在对我有用。我将在等待 Rails 6 稳定版推出 ActiveStorage::Blob#open
功能时使用此解决方案。
def perform(file_id)
import = ImportTemp.find(file_id)
temp_unit = Tempfile.new([ 'unit_import_temp', '.xlsx' ], :encoding => 'ascii-8bit')
units = []
begin
# Write xlsx from ImportTemp to Tempfile
temp_unit.write(import.file.download)
# Open the temp xlsx to Creek
book = Creek::Book.new(temp_unit.path)
sheet = book.sheets[0]
sheet.rows.each do |row|
# Skip the header
next if row.values[0] == 'Name' || row.values[1] == 'Abbreviation'
cells = row.values
# Add cells to new Unit
unit = Unit.new(name: cells[0], abbrev: cells[1], desc: cells[2])
units << unit
end
# Import the unit
Unit.import(units)
ensure
temp_unit.close
temp_unit.unlink # deletes the temp file
end
end
现在的答案似乎是(除非我遗漏了什么):
Creek::Book.new file.service_url, check_file_extension: false, remote: true