如何获取 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 的名称与您的模型属性完全相同。

我希望其中一个想法对您有用。