2017-01-04 1 views
0

У меня есть эта функция для загрузки несколько .csv файлов в массив объектов:Как получить атрибуты класса и сопоставить их с заголовками .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

Самая большая проблема, которую я вижу, это, что ваши заголовки столбцов не названы так же, как атрибуты вашей модели. Пример: «дата» или «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» с «CSV.read», чтобы получить доступ к заголовкам, перебирать заголовки и использовать их, чтобы создать экземпляр объекта:

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', например вам нужно будет добавить сеттера для каждого имени столбца, который отличается от вашей модели атрибутов, на вашей модели (который я считаю очень некрасиво):

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

Это не было бы необходимо, если вы могли бы получить в формате CSV файл, в котором заголовки столбцов называются точно так же, как и ваши атрибуты модели.

Надеюсь, что одна из идей работает для вас.

Смежные вопросы