2016-06-02 6 views
1

Я пытаюсь импортировать файл с tsv (tab разделенными данными) в мою базу данных, только он не отформатирован должным образом. Столбцы price и count разделены только пробелом (за исключением строки заголовка), и значения оба помещаются в ключ price, перемещая все данные в неправильные пары значений ключа.Rails нормализует данные CSV-файла

TSV файл:

purchaser name item description price count merchant address merchant name 
Alice Bob $10 off $20 of food 10.0 2 987 Fake St  Bob's Pizza 
Example Name $30 of awesome for $10 10.0 5 456 Unreal Rd Tom's Awesome Shop 
Name Three $20 Sneakers for $5 5.0 1  123 Fake St  Sneaker Store Emporium 
John Williams $20 Sneakers for $5 5.0 4  123 Fake St  Sneaker Store Emporium 

в /models/purchase.rb:

class Purchase < ActiveRecord::Base 
    # validates :item_price, :numericality => { :greater_than_or_equal_to => 0 } 

    def self.import(file) 
    CSV.foreach(file.path, :headers => true, 
         :header_converters => lambda { |h| h.downcase.gsub(' ', '_')}, 
         :col_sep => "\t" 
         ) do |row| 
         # debugger 
         purchase_hash = row.to_hash 
     Purchase.create!(purchase_hash) 
    end 
    end 
end 

Если я импортировать файл и комментарий в отладчике в модели, а затем введите row возвращается:

#<CSV::Row "purchaser_name":"Alice Bob" "item_description":"$10 off $20 of food" "price":"10.0 2" "count":" 987 Fake St" "merchant_address":" Bob's Pizza" "merchant_name":nil>

row.inspect возвращается:

"#<CSV::Row \"purchaser_name\":\"Alice Bob\" \"item_description\":\"$10 off $20 of food\" \"price\":\"10.0 2\" \"count\":\" 987 Fake St\" \"merchant_address\":\" Bob's Pizza\" \"merchant_name\":nil>"

Как вы можете видеть price (10,0) и count (2), были сплющенные в то же значение, потому что они не были разделены вкладки в файле.

db/schema.rb:

ActiveRecord::Schema.define(version: 20160601205154) do 

    create_table "purchases", force: :cascade do |t| 
    t.string "purchaser_name" 
    t.string "item_description" 
    t.string "price" 
    t.string "count" 
    t.string "merchant_address" 
    t.string "merchant_name" 
    t.datetime "created_at",  null: false 
    t.datetime "updated_at",  null: false 
    end 

end 

Я изначально был price как десятичный тип данных и count как Integer, но переключился их обратно в строку, чтобы попытаться найти решение. Я могу изменить их обратно, если это поможет (и предпочтет их поменять, если это возможно)

ответ

1

Решение в этом случае двоякое. Во-первых, определить конвертер, который будет разделить поле на две части (и преобразовать его в количестве в процессе) во время синтаксического анализа:

CONVERTER_SPLIT_PRICE_COUNT = lambda do |value, info| 
    next value unless info.header == "price" 
    price, count = value.split 
    [ price.to_f, count.to_i ] 
end 

Это превращает price поле в массив, например, "10.0 2" становится [10.0, 2].

Во-вторых, определить метод, который, после разбора, исправит неуместны значение и возвращает правильный Hash:

def row_to_hash_fixing_price_count(row) 
    row.headers.zip(row.fields.flatten).to_h 
end 

выше сглаживает цена/кол-массив в его родительский массив (остальная часть строки), а затем застегивает молнию массивом заголовков. Поскольку в настоящее время больше полей, чем заголовков, дополнительные nil в конце отбрасываются.

Вы будете использовать их, как это:

csv_opts = { 
    headers: true, 
    col_sep: "\t", 
    header_converters: ->(h) { h.downcase.tr(" ", "_") }, 
    converters: CONVERTER_SPLIT_PRICE_COUNT 
} 

data_out = CSV.new(data, csv_opts).map do |row| 
    row_to_hash_fixing_price_count(row) 
end 
# => [ { "purchaser_name" => "Alice Bob", 
#  "item_description" => "$10 off $20 of food", 
#  "price" => 10.0, 
#  "count" => 2, 
#  "merchant_address" => "987 Fake St", 
#  "merchant_name" => "Bob's Pizza" 
#  }, 
#  # ... 
# ] 

Вы можете увидеть его в действии здесь: http://ideone.com/08wTPT

P.S. Рассмотрите возможность создания записей навалом, а не по одному. Учитывая вышесказанное, вы можете просто сделать Purchase.create!(data_out) с create! accepts an array of hashes.

+0

Точно, что мне нужно, спасибо вам большое – alisontague

0

Вы можете попробовать переместить значения merchant_address и merchant_name, а затем разделить хлюпаемую цену и количество отсчетов на пробел и оценить два значения по цене и кол-во:

purchase_hash = row.to_hash 
purchase_hash[:merchant_name] = purchase_hash[:merchant_address] 
purchase_hash[:merchant_address] = purchase_hash[:count] 
splitted_price_count = purchase_hash[:price].split(" ") 
purchase_hash[:price] = splitted_price_count.first 
purchase_hash[:count] = splitted_price_count.last 
Purchase.create!(purchase_hash)