如何获取类的属性并以通用方式将它们映射到.csv头文件?

时间:2017-01-04 14:32:49

标签: ruby refactoring generic-programming

我有这个函数可以将几个.csv文件加载到一个对象数组中:

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个文件加载到不同类型的对象中。一般"形状"函数的含义相同,但对象属性不同。

为了最大限度地减少代码重复,我该如何创建此函数的更通用版本?

我想象这样的事情:

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

1 个答案:

答案 0 :(得分:1)

我看到的最大问题是,您的列标题的名称与您的模型属性不完全相同。示例:' date'与' ORDER_DATE'。很难猜测,属性日期应该填充列的内容' ORDER_DATE'。这使得任务变得更加复杂。

因此,我的第一个想法是在每个类上定义一个像

这样的映射
class Order < ActiveRecord::Base

  CSV_IMPORT_MAPPING = {
    date: 'ORDER_DATE',
    seller_id: 'SELLER_ID',
    # ...
  }

并使用此映射初始化循环中的对象:

    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 }

并将此哈希传递给新函数以实例化新对象。

不酷,但你得到一个很好的概述,csv列与哪个模型属性匹配,你获得了灵活性(例如,你可以决定不导入所有列,但只是哈希中的那些列。)

另一种方法可能是,取代“CSV.foreach”&#39;使用&#39; CSV.read&#39;要访问标题,遍历标题并使用它们来实例化对象:

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
  )

&#39; header.downcase&#39;将转换&#39; ORDER_DATE&#39;到&#39; order_date&#39;,例如你需要在你的模型上为每个列名添加一个setter,它不同于你的模型属性(我认为它非常难看):

class Object < ActiveRecord::Base
  alias_method :order_date=, :date=

如果您可以获得一个csv文件,则不需要这样做,其中列标题的名称与模型属性完全相同。

我希望,其中一个想法适合你。