2016-06-02 11 views
1

Ich versuche, eine tsv (Tab getrennt Datei) Datei in meine Datenbank zu importieren, nur ist es nicht richtig formatiert. Die Spalten price und count sind nur durch ein Leerzeichen getrennt (mit Ausnahme der Kopfzeile) und die Werte werden beide in den Schlüssel price eingegeben, wobei alle Daten in falsche Schlüsselwertpaare verschoben werden.Schienen normalisieren csv-Dateidaten

tsv-Datei:

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 

in /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 

Wenn ich die Datei und Kommentar im Debugger im Modell importieren und geben Sie dann row es zurückgibt:

#<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 kehrt:

"#<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>"

Wie Sie die price (10.0) und count (2) gestaucht wurden in den gleichen Wert sehen können, weil sie in der Datei nicht getrennt Tab waren.

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 

Ich hatte ursprünglich price als Dezimal-Datentyp und count als Integer wechselte aber sie String zurück, um zu versuchen, eine Lösung zu finden. Ich kann sie zurück ändern, wenn es hilft (und würde es vorziehen, sie zurück zu ändern, wenn möglich)

Antwort

1

Die Lösung hierfür ist zweifach. Zuerst definiert einen Konverter, der das Feld in zwei Teile aufgespalten wird (und an Zahlen in dem Prozess umwandeln) während der Analyse:

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 

Dies schaltet das price Feld in eine Anordnung, z.B. "10.0 2" wird [10.0, 2].

Zweitens wird ein Verfahren definieren, das, nach dem Parsen, den unangebrachten Wert fixiert und eine korrekte Hash zurückgeben:

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

Der über den Preis abflacht/Array in seine Mutter Array zählen (der Rest der Reihe) und reißt es dann mit dem Header-Array hoch. Da es jetzt mehr Felder als Header gibt, wird das extra nil am Ende gelöscht.

Sie werden sie wie folgt verwendet werden:

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" 
#  }, 
#  # ... 
# ] 

Sie es in Aktion sehen können hier: http://ideone.com/08wTPT

P. S. Erwägen Sie, Datensätze in großen Mengen anstatt einzeln zu erstellen. Angesichts der oben genannten könnten Sie einfach tun Purchase.create!(data_out) seit create! accepts an array of hashes.

+0

Genau das, was ich brauchte, vielen Dank – alisontague

0

Sie könnten versuchen, Merchant_address und Merchant_name Werte verschieben dann teilen Sie den gequetschten Preis und zählen fileds durch ein Leerzeichen und assing die beiden Werte zu price und zählen:

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)