web-dev-qa-db-fra.com

Ruby on Rails - Importer des données depuis un fichier CSV

J'aimerais importer des données d'un fichier CSV dans une table de base de données existante. Je ne veux pas enregistrer le fichier CSV, mais en extraire les données et les placer dans la table existante. J'utilise Ruby 1.9.2 et Rails 3.

Ceci est ma table:

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

Pouvez-vous me donner du code pour me montrer le meilleur moyen de le faire, merci.

184
freshest
require 'csv'    

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

La version simplifiée de la réponse de yfeldblum est plus simple et convient également aux fichiers volumineux:

require 'csv'    

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

Pas besoin de with_indifferent_access ni de symbolize_keys, et pas besoin de lire le fichier dans une chaîne en premier.

Il ne garde pas tout le fichier en mémoire en une fois, mais lit ligne par ligne et crée un moulage par ligne.

184
Tom De Leu

La gemme smarter_csv a été créée spécifiquement pour ce cas d'utilisation: lire des données à partir d'un fichier CSV et créer rapidement des entrées de base de données.

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

Vous pouvez utiliser l'option chunk_size pour lire simultanément N lignes-csv, puis utiliser Resque dans la boucle interne pour générer des travaux qui créeront les nouveaux enregistrements, plutôt que de les créer tout de suite. Vous pourrez ainsi répartir la génération entrées à plusieurs travailleurs.

Voir également: https://github.com/tilo/smarter_csv

10
Tilo

Vous pouvez essayer 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

Si vous le souhaitez, vous pouvez également vous débarrasser de la clé primaire à incrémentation automatique et définir la clé primaire sur name. Sinon, s'il existe une combinaison d'attributs formant une clé primaire, utilisez-la comme sélecteur. Aucun index n'est nécessaire, cela ne fera que l'accélérer.

4
Seamus Abshere

Cela peut aider. Il a aussi des exemples de code: 

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

Ou pour une tâche de rake pour faire la même chose: 

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

4
Kalyan Maddu

Il est préférable de placer le processus lié à la base de données dans un bloc transaction. Le coup de code est un processus complet d’ensemencement d’un ensemble de langages au modèle Langage,

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

Le fragment ci-dessous est une partie du fichier languages.csv,

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

Je sais que c'est une vieille question, mais cela reste dans les 10 premiers liens de Google.

Il n'est pas très efficace de sauvegarder les lignes une par une, car cela provoque un appel de la base de données et vous évitez de le faire, en particulier lorsque vous devez insérer de grandes quantités de données.

Il est préférable (et nettement plus rapide) d'utiliser l'insertion par lot.

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

Vous pouvez créer une telle requête manuellement et ensuite utiliser Model.connection.execute(RAW SQL STRING) (non recommandé) Ou utiliser gem activerecord-import (la première publication a eu lieu le 11 août 2010). Dans ce cas, il suffit de placer les données dans le tableau rows et d'appeler Model.import rows

reportez-vous à gem docs pour plus de détails

0
Yaroslav

Le meilleur moyen consiste à l'inclure dans une tâche de commission. Créez le fichier import.rake dans/lib/tasks/et mettez ce code dans ce fichier.

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

Après cela, lancez cette commande dans votre terminal rake csv_model_import[file.csv,Name_of_the_Model]

0
Ipsagel

Utilisez cette gemme: https://rubygems.org/gems/active_record_importer

class Moulding < ActiveRecord::Base
  acts_as_importable
end

Ensuite, vous pouvez maintenant utiliser:

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

Assurez-vous simplement que vos en-têtes correspondent aux noms de colonne de votre table.

0
Michael Nera