我正在编写一个接受CSV文件的类,对其进行转换,然后将新数据写出。
module Transformer
class Base
def initialize(file)
@file = file
end
def original_data(&block)
opts = { headers: true }
CSV.open(file, 'rb', opts, &block)
end
def transformer
# complex manipulations here like modifying columns, picking only certain
# columns to put into new_data, etc but simplified to `+10` to keep
# example concise
-> { |row| new_data << row['some_header'] + 10 }
end
def transformed_data
self.original_data(self.transformer)
end
def write_new_data
CSV.open('new_file.csv', 'wb', opts) do |new_data|
transformed_data
end
end
end
end
我想做的是:
transformer
的基类,以便实例仅需要实现自己的转换器,而读写行为则由基类给出。但是显然以上方法不起作用,因为我在new_data
中实际上没有引用transformer
。
我如何优雅地做到这一点?
答案 0 :(得分:1)
我可以根据您的需求和个人喜好推荐两种方法之一。
为清楚起见,我特意将代码精简到最低限度(没有包装类)。
由于您不想对文件进行处理,因此请使用CSV::Foreach
。例如,对于快速调试会话,请执行以下操作:
CSV.foreach "source.csv", headers: true do |row|
row["name"] = row["name"].upcase
row["new column"] = "new value"
p row
end
如果您希望在同一迭代中写入文件:
require 'csv'
csv_options = { headers: true }
# Open the target file for writing
CSV.open("target.csv", "wb") do |target|
# Add a header
target << %w[new header column names]
# Iterate over the source CSV rows
CSV.foreach "source.csv", **csv_options do |row|
# Mutate and add columns
row["name"] = row["name"].upcase
row["new column"] = "new value"
# Push the new row to the target file
target << row
end
end
CSV::Converters
内置的功能可能会有所帮助-CSV::Converters
-(请参阅CSV::New
文档中的:converters
定义)
require 'csv'
# Register a converter in the options hash
csv_options = { headers: true, converters: [:stripper] }
# Define a converter
CSV::Converters[:stripper] = lambda do |value, field|
value ? value.to_s.strip : value
end
CSV.open("target.csv", "wb") do |target|
# same as above
CSV.foreach "source.csv", **csv_options do |row|
# same as above - input data will already be converted
# you can do additional things here if needed
end
end
根据您的评论,并且由于您希望最大程度地减少I / O和迭代次数,因此可能有兴趣从变压器的职责中提取读/写操作。像这样的东西。
require 'csv'
class NameCapitalizer
def self.call(row)
row["name"] = row["name"].upcase
end
end
class EmailRemover
def self.call(row)
row.delete 'email'
end
end
csv_options = { headers: true }
converters = [NameCapitalizer, EmailRemover]
CSV.open("target.csv", "wb") do |target|
CSV.foreach "source.csv", **csv_options do |row|
converters.each { |c| c.call row }
target << row
end
end
请注意,如果更改了标题,上面的代码仍然无法处理标题。您可能必须保留最后一行(在所有转换之后),并将其#headers
放在输出CSV之前。
可能还有很多其他方法,但是Ruby中的CSV类没有最干净的接口,因此我尝试使处理它的代码保持尽可能的简单。