如何获取 class 的属性并将它们以通用方式映射到 .csv headers?
How to get a class's attributes and map them to .csv headers in a generic way?
我有这个功能可以将几个 .csv
文件加载到 object 的数组中:
def load_csv_data_to_order_objects
orders = []
get_data_paths("../my/path", ".csv").each do |path|
CSV.foreach(path, :headers => :first_row, :col_sep => ',', encoding: "ISO8859-1:utf-8") do |row|
orders.push Order.new(
:date => row["ORDER_DATE"],
:seller_id => row["SELLER_ID"],
:order_number => row["ORDER_NUMBER"],
:product_id => row["PRODUCT_ID"],
:quantity => row["QUANTITY"].to_i,
:sales_price => row["SALES_PRICE"].to_f,
)
end
end
orders
end
这可行,但我需要将具有不同列数的 .csv
文件加载到不同类型的 object 中。通用的"shape"函数是一样的,只是object属性不同
要尽量减少代码重复,我如何创建此函数的更通用版本?
我想象的是这样的:
def load_csv_data_to_objects(search_path, file_extension, class_name)
objects = []
get_data_paths(search_path, file_extension).each do |path|
CSV.foreach(path, :headers => :first_row, :col_sep => ',', encoding: "ISO8859-1:utf-8") do |row|
objects.push class_name.new(
# How can I get a class's attributes and map them to .csv headers?
)
end
end
objects
end
我看到的最大问题是,您的列 headers 的命名与您的模型属性不完全相同。示例:'date' 与 'ORDER_DATE'。不难猜测,属性 date 应该填充列 'ORDER_DATE' 的内容。这使得任务方式更加复杂。
因此,我的第一个想法是在每个 class 上定义一个映射,例如
class Order < ActiveRecord::Base
CSV_IMPORT_MAPPING = {
date: 'ORDER_DATE',
seller_id: 'SELLER_ID',
# ...
}
并使用此映射在循环中初始化 object:
CSV.foreach(path, :headers => :first_row, :col_sep => ',', encoding: "ISO8859-1:utf-8") do |row|
objects.push class_name.new(class_name::CVS_IMPORT_MAPPING.tap { |hash| hash.map { |class_attr, csv_attr| hash[class_attr] = row[csv_attr] } })
)
end
这基本上会将 CSV_IMPORT_MAPPING 从
转换为
{ date: 'ORDER_DATE', seller_id: 'SELLER_ID' }
以列内容为值的散列
{ date: '2016-12-01', seller_id: 12345 }
并将此散列传递给新函数以实例化一个新的 object。
不太酷,但是您得到了一个很好的概览,哪个 csv 列与哪个模型属性相匹配,并且您获得了灵活性(例如,您可以决定不导入所有列,而只导入散列中的列)。
另一种方法是,将 'CSV.foreach' 替换为 'CSV.read' 以访问 headers,迭代 headers 并使用它们实例化您的 object:
csv = CSV.read(path, :headers => :first_row, :col_sep => ',', encoding: "ISO8859-1:utf-8") do |row|
instance = class_name.new
csv.headers.each do |header|
instance.send("#{header.downcase}=", row[header])
end
objects.push instance
)
结束
'header.downcase' 会将 'ORDER_DATE' 转换为 'order_date',例如您需要为模型上的每个列名添加一个 setter,这与您的模型属性不同(我认为这真的很难看):
class Object < ActiveRecord::Base
alias_method :order_date=, :date=
如果您可以获得一个 csv 文件,那么这就没有必要了,其中列 headers 的名称与您的模型属性完全相同。
我希望其中一个想法对您有用。
我有这个功能可以将几个 .csv
文件加载到 object 的数组中:
def load_csv_data_to_order_objects
orders = []
get_data_paths("../my/path", ".csv").each do |path|
CSV.foreach(path, :headers => :first_row, :col_sep => ',', encoding: "ISO8859-1:utf-8") do |row|
orders.push Order.new(
:date => row["ORDER_DATE"],
:seller_id => row["SELLER_ID"],
:order_number => row["ORDER_NUMBER"],
:product_id => row["PRODUCT_ID"],
:quantity => row["QUANTITY"].to_i,
:sales_price => row["SALES_PRICE"].to_f,
)
end
end
orders
end
这可行,但我需要将具有不同列数的 .csv
文件加载到不同类型的 object 中。通用的"shape"函数是一样的,只是object属性不同
要尽量减少代码重复,我如何创建此函数的更通用版本?
我想象的是这样的:
def load_csv_data_to_objects(search_path, file_extension, class_name)
objects = []
get_data_paths(search_path, file_extension).each do |path|
CSV.foreach(path, :headers => :first_row, :col_sep => ',', encoding: "ISO8859-1:utf-8") do |row|
objects.push class_name.new(
# How can I get a class's attributes and map them to .csv headers?
)
end
end
objects
end
我看到的最大问题是,您的列 headers 的命名与您的模型属性不完全相同。示例:'date' 与 'ORDER_DATE'。不难猜测,属性 date 应该填充列 'ORDER_DATE' 的内容。这使得任务方式更加复杂。
因此,我的第一个想法是在每个 class 上定义一个映射,例如
class Order < ActiveRecord::Base
CSV_IMPORT_MAPPING = {
date: 'ORDER_DATE',
seller_id: 'SELLER_ID',
# ...
}
并使用此映射在循环中初始化 object:
CSV.foreach(path, :headers => :first_row, :col_sep => ',', encoding: "ISO8859-1:utf-8") do |row|
objects.push class_name.new(class_name::CVS_IMPORT_MAPPING.tap { |hash| hash.map { |class_attr, csv_attr| hash[class_attr] = row[csv_attr] } })
)
end
这基本上会将 CSV_IMPORT_MAPPING 从
转换为{ date: 'ORDER_DATE', seller_id: 'SELLER_ID' }
以列内容为值的散列
{ date: '2016-12-01', seller_id: 12345 }
并将此散列传递给新函数以实例化一个新的 object。
不太酷,但是您得到了一个很好的概览,哪个 csv 列与哪个模型属性相匹配,并且您获得了灵活性(例如,您可以决定不导入所有列,而只导入散列中的列)。
另一种方法是,将 'CSV.foreach' 替换为 'CSV.read' 以访问 headers,迭代 headers 并使用它们实例化您的 object:
csv = CSV.read(path, :headers => :first_row, :col_sep => ',', encoding: "ISO8859-1:utf-8") do |row|
instance = class_name.new
csv.headers.each do |header|
instance.send("#{header.downcase}=", row[header])
end
objects.push instance
)
结束
'header.downcase' 会将 'ORDER_DATE' 转换为 'order_date',例如您需要为模型上的每个列名添加一个 setter,这与您的模型属性不同(我认为这真的很难看):
class Object < ActiveRecord::Base
alias_method :order_date=, :date=
如果您可以获得一个 csv 文件,那么这就没有必要了,其中列 headers 的名称与您的模型属性完全相同。
我希望其中一个想法对您有用。