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

Чи можете ви дати мені якийсь код, щоб показати мені найкращий спосіб зробити це, дякую.

Відповіді:


380
require 'csv'    

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

2
Ви можете помістити його у завдання Rake або в дію контролера, або де завгодно ....
yfeldblum

1
Це спрацювало чудово. Однак у мене є питання для початківців - коли я намагався переглядати описані методи в документації API Ruby і Rails, я не зміг їх знайти на місці (я подивився на офіційних сайтах Ruby і Rails, документах API). Наприклад, я не міг знайти, який об’єкт повертає CSV.parse (), я не знайшов методів to_hash () та with_indifferent_access () ... Можливо, я заглянув у неправильне місце або пропустив якийсь основний принцип щодо переходу API Ruby & Rails док. Чи може хтось поділитися найкращою практикою, як читати документи Ruby API?
Володимир Кроз

2
@daveatflow: так, дивіться мою відповідь нижче, яка читає у файлі по одному рядку.
Том Де Леу

1
@ lokeshjain2008, це стосується моделі ОП.
Джастін Д.

3
Цей метод неефективний! На величезних CSV-файлах скачки з використанням барана. той, що нижче, краще.
unom

206

Простіша версія відповіді yfeldblum, що простіша і добре працює також з великими файлами:

require 'csv'    

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

Не потрібно for_indifferent_access або symbolize_keys, і не потрібно спочатку читати у файлі рядок.

Він не зберігає весь файл в пам’яті одразу, але читає по черзі і створює ліплення за рядком.


1
Це краще для управління великими розмірами файлів, правда? Чи читається воно в одному рядку за раз?
NotSimon

1
@Simon: справді. Він не зберігає весь файл в пам’яті одразу, але читає по черзі і створює ліплення за рядком.
Том Де Леу

У мене ця помилка, ви знаєте, чому ?: ActiveModel :: UnknownAttributeError: невідомий атрибут 'сирена; nom_ent; adresse; ; categorie; tel 'для транзакції
nico_lrx

1
@AlphaNico Створіть запитання щодо своєї проблеми. Ця помилка не пов’язана з цим, ваші об'єкти Model здаються не синхронізованими.
unom

У цьому випадку, як ви пишете для цього TestCases?
Afolabi Olaoluwa Akinwumi

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: невизначений метод `закрити» для нуль: NilClass stackoverflow.com/questions/42515043 / ...
Маркос Р. Guevara

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. Крім того, якщо є якась комбінація атрибутів, які утворюють первинний ключ, використовуйте це як селектор. Індекс не потрібен, він просто зробить швидше.


4

Це може допомогти. Він також має приклади коду:

http://csv-mapper.rubyforge.org/

Або для завдання граблі для того ж:

http://erikonrails.snowedin.net/?p=212


erikonrails.snowedin.net/?p=212 зламаний, будь ласка, я відкрив питання щодо виконання завдань граблі тут stackoverflow.com/questions/42515043/…
Маркос Р. Гевара

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 / task / і додайте цей код до цього файлу.

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]


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

для детальної інформації зверніться до gem docs


-2

Краще використовувати CSV :: Таблиця та використовувати String.encode(universal_newline: true). Це перетворення CRLF і CR в LF


1
Яке ваше запропоноване рішення?
Тасс

-3

Якщо ви хочете використовувати SmartCSV

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

Це відображає дані з обмеженими вкладками у кожному рядку "\t"з рядками, розділеними новими рядками"\n"

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.