Ruby on rails 在csv列和ActiveRecord对象之间进行差异
我有一个简单的csv(电子邮件列表),我想上传到我的rails后端API,如下所示:Ruby on rails 在csv列和ActiveRecord对象之间进行差异,ruby-on-rails,ruby,csv,Ruby On Rails,Ruby,Csv,我有一个简单的csv(电子邮件列表),我想上传到我的rails后端API,如下所示: abd@gmail.com,cool@hotmail.com 我想要的是上传该文件,在用户表中检查是否有匹配的行(根据电子邮件地址),然后返回一个新下载的csv,其中包含两列:电子邮件以及电子邮件是否与现有用户匹配(布尔值true/false) 我想要流式输出,因为文件可能非常大。这就是我到目前为止所做的: 控制器 文件_inspection.rb 需要“csv” 类文件摄取 def self.process
abd@gmail.com,cool@hotmail.com
我想要的是上传该文件,在用户表中检查是否有匹配的行(根据电子邮件地址),然后返回一个新下载的csv,其中包含两列:电子邮件以及电子邮件是否与现有用户匹配(布尔值true/false)
我想要流式输出,因为文件可能非常大。这就是我到目前为止所做的:
控制器
文件_inspection.rb
需要“csv”
类文件摄取
def self.process_csv(文件)
电子邮件=[]
CSV.foreach(file.path,headers:true)do |行|
电子邮件为什么不从用户那里收集所有电子邮件,然后做类似的事情呢。这个例子很简单,但你明白了。如果我们可以假设您的输入文件只是一个带有逗号分隔值的电子邮件字符串,那么这应该是可行的:
emails = File.read('emails.csv').split(',')
def process_csv(emails)
user_emails = User.where.not(email: [nil, '']).pluck(:email)
CSV.open('emails_processed.csv', 'w') do |row|
row << ['email', 'present']
emails.each do |email|
row << [email, user_emails.include?(email) ? 'true' : 'false']
end
end
end
process_csv(emails)
emails=File.read('emails.csv').split(','))
def流程_csv(电子邮件)
user_emails=user.where.not(电子邮件:[nil,,]).pull(:email)
CSV.open('emails_processed.CSV','w')do |行|
行基本上,您要做的是将传入的CSV数据收集到批中-使用每个批查询数据库并将差异写入临时文件
然后将tempfile流式传输到客户端
require 'csv'
require 'tempfile'
class FileIngestion
BATCH_SIZE = 1000
def self.process_csv(file)
csv_tempfile = CSV.new(Tempfile.new('foo'))
CSV.read(file, headers: false).lazy.drop(1).each_slice(BATCH_SIZE) do |batch|
emails = batch.flatten
users = User.where(email: emails).pluck(:email)
emails.each do |e|
csv_tempfile << [e, users.include?(e)]
end
end
csv_tempfile
end
end
需要“csv”
需要“临时文件”
类文件摄取
批量大小=1000
def self.process_csv(文件)
csv_tempfile=csv.new(tempfile.new('foo'))
CSV.read(文件,标题:false)。lazy.drop(1)。每个片(批大小)都做批处理|
电子邮件=batch.flatte
users=User.where(电子邮件:email).pull(:email)
电子邮件。每封都可以|
csv_tempfile好的,这就是我想到的。这是一个基本上防止用户上传超过10000个数据点的文件的解决方案。可能不是最好的解决方案(我更喜欢@Max的解决方案),但无论如何,我都想分享我所做的:
def emails_exist
raise 'Missing file parameter' if !params[:file]
csv_path = params[:file].tempfile.path
send_data csv_of_emails_matching_users(csv_path), filename: 'emails.csv', type: 'text/csv'
end
private
def csv_of_emails_matching_users(input_csv_path)
total = 0
CSV.generate(headers: true) do |result|
result << %w{email exists}
emails = []
CSV.foreach(input_csv_path) do |row|
total += 1
if total > 10001
raise 'User Validation limited to 10000 emails'
end
emails.push(row[0])
if emails.count > 99
append_to_csv_info_for_emails(result, emails)
end
end
if emails.count > 0
append_to_csv_info_for_emails(result, emails)
end
end
end
def append_to_csv_info_for_emails(csv, emails)
user_emails = User.where(email: emails).pluck(:email).to_set
emails.each do |email|
csv << [email, user_emails.include?(email)]
end
emails.clear
end
def电子邮件\u存在
如果出现以下情况,则引发“缺少文件参数”!参数[:文件]
csv_path=params[:file].tempfile.path
发送\u数据\u电子邮件的csv\u匹配\u用户(csv\u路径),文件名:'emails.csv',键入:'text/csv'
结束
私有的
def csv_电子邮件_匹配_用户(输入_csv_路径)
总数=0
CSV.generate(headers:true)do | result|
结果10001
提出“用户验证限制为10000封电子邮件”
结束
emails.push(第[0]行)
如果电子邮件数>99
为电子邮件(结果、电子邮件)将\u附加到\u csv\u info\u
结束
结束
如果emails.count>0
为电子邮件(结果、电子邮件)将\u附加到\u csv\u info\u
结束
结束
结束
def将_附加到_csv_信息_以获取_电子邮件(csv,电子邮件)
user\u emails=user.where(email:emails)。点击(:email)。设置
电子邮件。每个人都发电子邮件|
csv你到底卡在哪里?生成新的csv文件(流)和电子邮件,无论它们是否匹配users
拥有所有匹配的对象,但现在需要创建一个新的csv,其中包含来自旧对象的所有电子邮件,以及它们是否匹配。这是一个巨大的文件,所以我希望它是高效的(如果可能的话)JFYI,即使CSV.foreach
是“流式”的,您通过在同一内存阵列中收集所有电子邮件来取消所有的效率。@SergioTulentsev是的,我想我需要展示一些东西。但一个可以接受的答案是改进这一代码。如果你有一个好的解决方案,请告诉我。我想避免过度查询数据库,并尽可能减少内存分配。@Cyzanfar:当然可以。我可能会在这里选择一个中间立场:将源电子邮件分组成小批量(100-1000封电子邮件),并在批量已满时查询用户。这样你就不会一次把整个文件弄脏,也不会单独查询每封邮件。这是一个解决方案。完成后,我如何将文件下载到我的计算机上?我正在使用curl命令发出请求:curl-F file=@wb.csvhttp://localhost:3000/api/v1/process_data\?api_key\=Dp9Kv7j1y-FYYYTD-tYsAsSNic3ox
。虽然这在记忆方面几乎是一样的。这个文件非常大,并且这个代码可能会耗尽内存输入,因此也会耗尽输出文件。我喜欢这个解决方案!它在这一行抛出一个错误(ArgumentError(错误的参数数(0代表1+)
):csv\u tempfile=csv.new(tempfile.new)
需要向它传递一个文件名。这很奇怪-我在2.3.1p112
上尝试了它,但没有收到错误。尝试调用Tempfile.new('foo')
而不是.yup。还缺少一个batch.length
,但这是次要的。还有,我如何将文件下载到我的计算机上?我认为在我的控制器中发送_数据会解决这个问题,我认为您可以将响应主体设置为返回的CSV文件。由于它是可枚举的,Rails可以自己对其进行流式处理。我试过了,但是文件没有下载到我的电脑上,我不知道发生了什么
def import_csv
send_data FileIngestion.process_csv(params[:file]),
filename: 'processed_emails.csv', type: 'text/csv'
end
require 'csv'
class FileIngestion
def self.process_csv(file)
emails = File.read('emails.csv').split(',')
CSV.open('emails_processed.csv', 'w') do |row|
emails.each do |email|
row << [email, user_emails.include?(email) ? 'true' : 'false']
end
end
File.read('emails_processed.csv')
end
end
require 'csv'
require 'tempfile'
class FileIngestion
BATCH_SIZE = 1000
def self.process_csv(file)
csv_tempfile = CSV.new(Tempfile.new('foo'))
CSV.read(file, headers: false).lazy.drop(1).each_slice(BATCH_SIZE) do |batch|
emails = batch.flatten
users = User.where(email: emails).pluck(:email)
emails.each do |e|
csv_tempfile << [e, users.include?(e)]
end
end
csv_tempfile
end
end
def emails_exist
raise 'Missing file parameter' if !params[:file]
csv_path = params[:file].tempfile.path
send_data csv_of_emails_matching_users(csv_path), filename: 'emails.csv', type: 'text/csv'
end
private
def csv_of_emails_matching_users(input_csv_path)
total = 0
CSV.generate(headers: true) do |result|
result << %w{email exists}
emails = []
CSV.foreach(input_csv_path) do |row|
total += 1
if total > 10001
raise 'User Validation limited to 10000 emails'
end
emails.push(row[0])
if emails.count > 99
append_to_csv_info_for_emails(result, emails)
end
end
if emails.count > 0
append_to_csv_info_for_emails(result, emails)
end
end
end
def append_to_csv_info_for_emails(csv, emails)
user_emails = User.where(email: emails).pluck(:email).to_set
emails.each do |email|
csv << [email, user_emails.include?(email)]
end
emails.clear
end