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

205

Я хотел бы импортировать данные из файла 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

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

свежайшие
источник

Ответы:

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
источник
2
Вы можете поместить его в задачу Rake, или в действие контроллера, или где угодно ...
yfeldblum
1
Это сработало отлично. Однако у меня есть вопрос начального уровня - когда я попытался просмотреть описанные методы в документации по Ruby и Rails API, я не смог найти их на месте (я посмотрел на официальных сайтах Ruby и Rails, документацию по API). Например, я не смог найти, какой объект возвращает CSV.parse (), я не нашел методы to_hash () и with_indifferent_access () ... Возможно, я искал не в том месте или пропустил какой-то базовый принцип о том, как пройти через Ruby & Rails API Docs. Кто-нибудь может поделиться лучшей практикой, как читать документы Ruby API?
Владимир Кроз
2
@daveatflow: да, смотрите мой ответ ниже, который читает в файле по одной строке за раз.
Том Де Ле
1
@ lokeshjain2008, это относится к модели ОП.
Джастин Д.
3
Этот метод неэффективен! На огромных CSV-файлах использование оперативной памяти стремительно растет. тот, что ниже, лучше.
17
206

Более простая версия ответа yfeldblum, более простая и хорошо работающая также с большими файлами:

require 'csv'    

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

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

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

Том Де Леу
источник
1
Это лучше для управления большими размерами файлов, верно? Он читает по одной строке за раз?
NotSimon
1
@ Симон: действительно. Он не хранит весь файл в памяти сразу, но читает построчно и создает Формирование каждой строки.
Том Де Ле
У меня есть эта ошибка. Знаете ли вы, почему ?: ActiveModel :: UnknownAttributeError: неизвестный атрибут 'siren; nom_ent; adresse ;compment_adresse; cp_ville; платит; регион; отправление; activite; дата; nb_salaries; nom; prenom; гражданский; adr_mail; libele_acti ;
telie
1
@AlphaNico Создайте вопрос с вашей проблемой. Эта ошибка не связана с этим, ваши объекты Model кажутся несинхронными.
17
В этом случае, как вы пишете TestCases для этого?
Афолаби Олаолува Акинвуми
11

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

Тило
источник
3
Поскольку класс CSV включен, я чувствую, что лучше использовать его вместо добавления или установки дополнительного драгоценного камня. Конечно, вы не предлагали добавить новый драгоценный камень в приложение. Очень легко добавить серию отдельных драгоценных камней, каждый для определенной цели, и, прежде чем вы узнаете об этом, ваше приложение имеет чрезмерные зависимости. (Я сознательно избегаю добавления каких-либо драгоценных камней. В моем магазине нам нужно оправдать добавление к нашим товарищам по команде.)
Tass
1
@Tass также довольно легко добавить серию отдельных методов, каждый для определенной цели, и, прежде чем вы узнаете об этом, ваше приложение имеет чрезмерную логику, которую вы должны поддерживать. Если драгоценный камень работает, в хорошем состоянии и использует мало ресурсов или может быть помещен в карантин в соответствующие среды (например, подготовка для производственных задач), мне кажется, всегда лучше использовать драгоценный камень. Ruby и Rails предназначены для написания меньшего количества кода.
Zrisher
У меня следующая ошибка, знаете почему? ActiveModel :: UnknownAttributeError: неизвестный атрибут 'сирена; nom_ent; адресует; complement_adresse; cp_ville; платит; регион;; activité вас департамент, дату; nb_salaries; Ном_проц_ставку; Prénom; civilite; adr_mail; libele_acti; Категория; тела' для сделки
nico_lrx
Я попробовал это на грабле, консоль возвращается: грабли прерваны! NoMethodError: неопределенный метод `close 'для nil: NilClass stackoverflow.com/questions/42515043/…
Маркос Р. Гевара
1
@Tass разделение обработки 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. Альтернативно, если есть некоторая комбинация атрибутов, которые формируют первичный ключ, используйте это как селектор. Индекс не нужен, он просто сделает это быстрее.

Симус Абшер
источник
2

Лучше обернуть процесс, связанный с базой данных, внутри 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
...
Лорем Ипсум Долор
источник
0

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

class Moulding < ActiveRecord::Base
  acts_as_importable
end

Тогда вы можете теперь использовать:

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

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

Майкл Нера
источник
0

Лучше всего включить его в задание на грабли. Создайте файл 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]

Ipsagel
источник
0

Я знаю, что это старый вопрос, но он все еще в первых 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

обратитесь к документации по драгоценным камням для деталей

Ярослав
источник
-2

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

YSK
источник
1
Какое ваше предлагаемое решение?
ТАСС
-3

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

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

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

Магед Маклед
источник