Ruby on Rails - импорт данных из файла CSV

Я хотел бы импортировать данные из файла CSV в существующую таблицу базы данных. Я не хочу сохранять CSV файл, просто беру данные из него и помещаю его в существующую таблицу. Я использую Ruby 1.9.2 и Rails 3.

Это моя таблица:

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

Можете ли вы дать мне код, чтобы показать мне лучший способ сделать это, спасибо.

Ответ 1

require 'csv'    

csv_text = File.read('...')
csv = CSV.parse(csv_text, :headers => true)
csv.each do |row|
  Moulding.create!(row.to_hash)
end

Ответ 2

Упрощенная версия ответа yfeldblum, что проще и хорошо работает с большими файлами:

require 'csv'    

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

Нет необходимости в with_indifferent_access или symbolize_keys, и не нужно сначала читать файл в строке.

Он не сохраняет весь файл в памяти сразу, но читается в строковой последовательности и создает литье в строке.

Ответ 3

Камень smarter_csv был специально создан для этого прецедента: для чтения данных из файла CSV и быстрого создания записей в базе данных.

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

Вы можете использовать опцию chunk_size для чтения N csv-строк за раз, а затем использовать Resque во внутреннем цикле для создания заданий, которые будут создавать новые записи, а не создавать их сразу же - таким образом вы можете распространять нагрузку генерации записей на нескольких работников.

См. также:    https://github.com/tilo/smarter_csv

Ответ 5

Вы можете попробовать 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

Если это то, что вы хотите, вы можете также рассмотреть возможность избавиться от первичного ключа с автоматическим приращением из таблицы и установить первичный ключ на name. В качестве альтернативы, если есть некоторая комбинация атрибутов, которые образуют первичный ключ, используйте это как селектор. Индекс не нужен, он просто ускоряется.

Ответ 6

Лучше обернуть процесс, связанный с базой данных, внутри блока transaction. Удар фрагмента кода - это полный процесс посева набора языков в языковой модели,

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

Ниже представлен фрагмент файла languages.csv,

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

Ответ 7

Используйте этот драгоценный камень: https://rubygems.org/gems/active_record_importer

class Moulding < ActiveRecord::Base
  acts_as_importable
end

Затем вы можете использовать:

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

Просто убедитесь, что ваши заголовки соответствуют именам столбцов вашей таблицы

Ответ 8

Лучше всего включить его в граблированную задачу. Создайте файл import.rake внутри/lib/tasks/и поместите этот код в этот файл.

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

После этого запустите эту команду в терминале rake csv_model_import[file.csv,Name_of_the_Model]

Ответ 9

Я знаю этот старый вопрос, но он все еще находится в первых 10 ссылках в google.

Невозможно сохранить строки один за другим, потому что вызывает вызов базы данных в цикле, и вам лучше избежать этого, особенно если вам нужно вставить огромные части данных.

Лучше (и значительно быстрее) использовать пакетную вставку.

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

Вы можете создать такой запрос вручную, а не Model.connection.execute(RAW SQL STRING) (не рекомендуется) или использовать gem activerecord-import (он был выпущен 11 августа 2010 года), в этом случае просто поместите данные в массив rows и вызовите Model.import rows

подробнее см. в документах gem

Ответ 10

Лучше использовать CSV:: Table и использовать String.encode(universal_newline: true). Он преобразует CRLF и CR в LF

Ответ 11

Если вы хотите использовать SmartCSV

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

Это представляет данные с разделителями табуляции в каждой строке "\t" с строками, разделенными новыми строками "\n"