Ruby on Rails - Impor Data dari file CSV

205

Saya ingin mengimpor data dari file CSV ke tabel database yang ada. Saya tidak ingin menyimpan file CSV, cukup ambil datanya dan masukkan ke tabel yang ada. Saya menggunakan Ruby 1.9.2 dan Rails 3.

Ini meja saya:

create_table "mouldings", :force => true do |t|
  t.string   "suppliers_code"
  t.datetime "created_at"
  t.datetime "updated_at"
  t.string   "name"
  t.integer  "supplier_id"
  t.decimal  "length",         :precision => 3, :scale => 2
  t.decimal  "cost",           :precision => 4, :scale => 2
  t.integer  "width"
  t.integer  "depth"
end

Bisakah Anda memberi saya beberapa kode untuk menunjukkan kepada saya cara terbaik untuk melakukan ini, terima kasih.

segar
sumber

Jawaban:

381
require 'csv'    

csv_text = File.read('...')
csv = CSV.parse(csv_text, :headers => true)
csv.each do |row|
  Moulding.create!(row.to_hash)
end
yfeldblum
sumber
2
Anda dapat meletakkannya dalam tugas Rake, atau dalam aksi pengontrol, atau di mana pun Anda suka ....
yfeldblum
1
Itu bekerja dengan sempurna. Namun saya memiliki pertanyaan tingkat pemula - ketika saya mencoba menelusuri metode yang dijelaskan dalam dokumentasi Ruby dan Rails API saya tidak dapat menemukannya di tempat (saya melihat situs Ruby dan Rails resmi, API docs). Misalnya saya tidak dapat menemukan objek apa yang mengembalikan metode CSV.parse (), saya tidak menemukan to_hash () dan with_indifferent_access () ... Mungkin saya mencari di tempat yang salah atau melewatkan beberapa prinsip dasar tentang cara melintasi Ruby & Rails API docs. Adakah yang bisa berbagi praktik terbaik cara membaca dokumen Ruby API?
Vladimir Kroz
2
@daveatflow: ya, lihat jawaban saya di bawah ini, yang terbaca di file satu baris setiap kali.
Tom De Leu
1
@ lokeshjain2008, mengacu pada model OP.
Justin D.
3
Metode ini tidak efisien! Pada file CSV yang besar, ram penggunaannya meroket. yang di bawah ini lebih baik.
unom
206

Versi sederhana dari jawaban yfeldblum, yaitu lebih sederhana dan berfungsi dengan baik juga dengan file besar:

require 'csv'    

CSV.foreach(filename, :headers => true) do |row|
  Moulding.create!(row.to_hash)
end

Tidak perlu dengan with_indifferent_access atau symbolize_keys, dan tidak perlu membaca file ke string terlebih dahulu.

Itu tidak menyimpan seluruh file dalam memori sekaligus, tetapi membaca baris demi baris dan membuat Molding per baris.

Tom De Leu
sumber
1
Ini lebih baik untuk mengelola ukuran file besar bukan? Apakah itu dibaca dalam satu baris sekaligus?
NotSimon
1
@Simon: memang. Itu tidak menyimpan seluruh file dalam memori sekaligus, tetapi membaca baris demi baris dan membuat Molding per baris.
Tom De Leu
Saya memiliki kesalahan ini, tahukah Anda mengapa ?: ActiveModel :: UnknownAttributeError: sirene atribut yang tidak diketahui; nom_ent; adresse; melengkapi_adresse; cp_ville; membayar; wilayah; departemen; aktivitas; tanggal; nb_salaries; nom; prenom; sipil; adr_mail; libele_acti ; kategorie; tel 'untuk Transaksi
nico_lrx
1
@AlphaNico Buat pertanyaan dengan masalah Anda. Kesalahan itu tidak terkait dengan ini, objek Model Anda tampaknya tidak sinkron.
unom
Dalam hal ini, bagaimana Anda menulis TestCases untuk ini?
Afolabi Olaoluwa Akinwumi
11

The smarter_csvpermata secara khusus diciptakan untuk digunakan-hal ini: untuk membaca data dari file CSV dan cepat membuat entri database.

  require 'smarter_csv'
  options = {}
  SmarterCSV.process('input_file.csv', options) do |chunk|
    chunk.each do |data_hash|
      Moulding.create!( data_hash )
    end
  end

Anda dapat menggunakan opsi chunk_sizeuntuk membaca N csv-rows pada satu waktu, dan kemudian menggunakan Resque di loop dalam untuk menghasilkan pekerjaan yang akan membuat catatan baru, daripada membuatnya segera - dengan cara ini Anda dapat menyebarkan beban entri yang menghasilkan ke beberapa pekerja.

Lihat juga: https://github.com/tilo/smarter_csv

Tilo
sumber
3
Karena kelas CSV disertakan, saya merasa lebih baik menggunakannya daripada menambahkan atau menginstal permata tambahan. Memang, Anda tidak mengusulkan agar permata baru ditambahkan ke aplikasi. Sangat mudah untuk menambahkan serangkaian permata individual, masing-masing untuk tujuan tertentu dan sebelum Anda menyadarinya aplikasi Anda memiliki ketergantungan yang berlebihan. (Saya menemukan diri saya secara sadar menghindari penambahan permata. Di toko saya, kami perlu membenarkan penambahan pada rekan satu tim kami.)
Tass
1
@Tass juga cukup mudah untuk menambahkan serangkaian metode individual, masing-masing untuk tujuan tertentu dan sebelum Anda menyadarinya aplikasi Anda memiliki logika berlebihan yang harus Anda pertahankan. Jika sebuah permata bekerja, terpelihara dengan baik, dan menggunakan sedikit sumber daya atau dapat dikarantina ke lingkungan yang relevan (yaitu, Staging untuk tugas-tugas produksi), bagi saya selalu merupakan pilihan yang lebih baik untuk menggunakan permata. Ruby dan Rails adalah tentang menulis lebih sedikit kode.
zrisher
Saya memiliki kesalahan berikut, apakah Anda tahu mengapa? ActiveModel :: UnknownAttributeError: sirene atribut yang tidak diketahui '; sirene; nom_ent; adresse; melengkapi_adresse; cp_ville; membayar; wilayah; departemen; aktif; tanggal; nb_salaries; nom; prenom; sipil; adr_mail; libele_acti; kategorie; tel' untuk Transaksi
nico_lrx
Saya mencoba ini pada tugas menyapu, mengembalikan konsol: menyapu dibatalkan! NoMethodError: metode yang tidak ditentukan `tutup 'untuk nihil: NilClass stackoverflow.com/questions/42515043/...
Marcos R. Guevara
1
@Tass memotong pemrosesan CSV, meningkatkan kecepatan dan menghemat memori mungkin menjadi pembenaran yang baik untuk menambahkan permata baru;)
Tilo
5

Anda mungkin mencoba Upsert:

require 'upsert' # add this to your Gemfile
require 'csv'    

u = Upsert.new Moulding.connection, Moulding.table_name
CSV.foreach(file, headers: true) do |row|
  selector = { name: row['name'] } # this treats "name" as the primary key and prevents the creation of duplicates by name
  setter = row.to_hash
  u.row selector, setter
end

Jika ini yang Anda inginkan, Anda mungkin juga mempertimbangkan untuk menyingkirkan kunci primer kenaikan-otomatis dari tabel dan mengatur kunci primer ke name. Atau, jika ada beberapa kombinasi atribut yang membentuk kunci utama, gunakan itu sebagai pemilih. Tidak diperlukan indeks, itu hanya akan membuatnya lebih cepat.

Seamus Abshere
sumber
2

Lebih baik untuk membungkus proses terkait database di dalam transactionblok. Pukulan cuplikan kode adalah proses penuh penyemaian serangkaian bahasa ke model Bahasa,

require 'csv'

namespace :lan do
  desc 'Seed initial languages data with language & code'
  task init_data: :environment do
    puts '>>> Initializing Languages Data Table'
    ActiveRecord::Base.transaction do
      csv_path = File.expand_path('languages.csv', File.dirname(__FILE__))
      csv_str = File.read(csv_path)
      csv = CSV.new(csv_str).to_a
      csv.each do |lan_set|
        lan_code = lan_set[0]
        lan_str = lan_set[1]
        Language.create!(language: lan_str, code: lan_code)
        print '.'
      end
    end
    puts ''
    puts '>>> Languages Database Table Initialization Completed'
  end
end

Cuplikan di bawah ini adalah sebagian languages.csvfile,

aa,Afar
ab,Abkhazian
af,Afrikaans
ak,Akan
am,Amharic
ar,Arabic
as,Assamese
ay,Aymara
az,Azerbaijani
ba,Bashkir
...
Lorem Ipsum Dolor
sumber
0

Gunakan permata ini: https://rubygems.org/gems/active_record_importer

class Moulding < ActiveRecord::Base
  acts_as_importable
end

Maka sekarang Anda dapat menggunakan:

Moulding.import!(file: File.open(PATH_TO_FILE))

Pastikan bahwa tajuk Anda cocok dengan nama kolom pada tabel Anda

Michael Nera
sumber
0

Cara yang lebih baik adalah memasukkannya dalam tugas menyapu. Buat file import.rake di dalam / lib / task / dan masukkan kode ini ke file itu.

desc "Imports a CSV file into an ActiveRecord table"
task :csv_model_import, [:filename, :model] => [:environment] do |task,args|
  lines = File.new(args[:filename], "r:ISO-8859-1").readlines
  header = lines.shift.strip
  keys = header.split(',')
  lines.each do |line|
    values = line.strip.split(',')
    attributes = Hash[keys.zip values]
    Module.const_get(args[:model]).create(attributes)
  end
end

Setelah itu jalankan perintah ini di terminal Anda rake csv_model_import[file.csv,Name_of_the_Model]

Ipsagel
sumber
0

Saya tahu ini pertanyaan lama tetapi masih di 10 tautan pertama di google.

Sangat tidak efisien untuk menyimpan baris satu per satu karena menyebabkan panggilan basis data dalam loop dan Anda lebih baik menghindarinya, terutama ketika Anda perlu memasukkan bagian data yang sangat besar.

Lebih baik (dan secara signifikan lebih cepat) untuk menggunakan bets insert.

INSERT INTO `mouldings` (suppliers_code, name, cost)
VALUES
    ('s1', 'supplier1', 1.111), 
    ('s2', 'supplier2', '2.222')

Anda dapat membuat kueri seperti itu secara manual dan daripada melakukan Model.connection.execute(RAW SQL STRING)(tidak direkomendasikan) atau menggunakan permata activerecord-import(pertama kali dirilis pada 11 Agustus 2010) dalam hal ini hanya memasukkan data dalam array rowsdan panggilanModel.import rows

lihat dokumentasi permata untuk detailnya

Yaroslav
sumber
-2

Lebih baik menggunakan CSV :: Table dan gunakan String.encode(universal_newline: true). Ini mengubah CRLF dan CR ke LF

ya
sumber
1
Apa solusi yang Anda ajukan?
Tass
-3

Jika Anda ingin Menggunakan SmartCSV

all_data = SmarterCSV.process(
             params[:file].tempfile, 
             { 
               :col_sep => "\t", 
               :row_sep => "\n" 
             }
           )

Ini mewakili data yang dibatasi tab di setiap baris "\t"dengan baris yang dipisahkan oleh baris baru"\n"

Maged Makled
sumber