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

2
あなたは....どこでもあなたが好きなrakeタスクに入れて、またはコントローラのアクションで、またはすることができます
yfeldblum

1
それは完全に機能しました。しかし、初心者レベルの質問があります。RubyおよびRails APIのドキュメントで説明されているメソッドを参照しようとしたところ、適切な場所で見つかりませんでした(RubyおよびRailsの公式サイト、APIドキュメントを調べました)。たとえば、CSV.parse()を返すオブジェクトが見つからなかった、to_hash()およびwith_indifferent_access()メソッドが見つからなかった... Ruby&Rails APIをトラバースする方法について、間違った場所を調べたり、いくつかの基本的な原則を見落としたりしたdocs。Ruby APIドキュメントの読み方のベストプラクティスを誰かと共有できますか?
Vladimir Kroz、2012年

2
@daveatflow:はい、下記の私の回答を参照してください。ファイルは一度に1行ずつ読み込まれます。
トム・デ・レイ

1
@ lokeshjain2008、それはOPのモデルを指します。
ジャスティンD.

3
この方法は非効率的です!巨大なCSVファイルでは、RAMの使用量が急増します。以下の方が良いです。
unom 2017

206

yfeldblumの回答のより単純なバージョン。これはより単純で、大きなファイルでもうまく機能します。

require 'csv'    

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

with_indifferent_accessやsymbolize_keysは必要なく、最初にファイルを文字列に読み込む必要もありません。

ファイル全体を一度にメモリに保持するのではなく、1行ずつ読み取り、行ごとにモールディングを作成します。


1
これは大きなファイルサイズを管理するのに適していますか?一度に1行ずつ読みますか?
NotSimon 2014

1
@サイモン:確かに。ファイル全体を一度にメモリに保持するのではなく、1行ずつ読み取り、行ごとにモールディングを作成します。
トム・デ・レイ

私はこのエラーを抱えていますが、理由を知っていますか?:ActiveModel :: UnknownAttributeError:unknown attribute 'siren; nom_ent; adresse; complement_adresse; cp_ville; pays; region; departement; activite; date; nb_salaries; nom; prenom; civilite; adr_mail; libele_acti ; categorie; tel 'for Transaction
nico_lrx

1
@AlphaNico問題のある質問を作成します。このエラーはこれとは無関係であり、モデルオブジェクトは同期していないようです。
unom 2017

この場合、このためにTestCaseをどのように記述しますか?
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クラスが含まれているので、gemを追加したりインストールしたりするのではなく、CSVクラスを使用する方が良いと感じています。確かに、あなたは新しい宝石をアプリケーションに追加することを提案しませんでした。一連の個別の宝石を追加するのは非常に簡単で、それぞれが特定の目的のためであり、アプリケーションが過度の依存関係を持っていることに気付く前に追加する必要があります。(私は意識的に宝石の追加を避けています。私の店では、チームメイトへの追加を正当化する必要があります。)
Tass

1
@Tass一連の個別のメソッドをそれぞれ特定の目的のために追加することも、アプリケーションに維持する必要のある過剰なロジックがあることに気付く前に、非常に簡単です。gemが機能し、十分に維持され、リソースをほとんど使用しない場合、または関連環境(つまり、本番タスクのステージング)に隔離できる場合は、gemを使用する方が常に良い方法であると思われます。RubyとRailsはすべて、より少ないコードを記述することについてです。
zrisher、2015

次のエラーがあります。理由がわかりますか?ActiveModel :: UnknownAttributeError:unknown attribute 'siren; nom_ent; adresse; complement_adresse; cp_ville; pays; region; departement; activite; date; nb_salaries; nom; prenom; civilite; adr_mail; libele_acti; categorie; tel' for Transaction
nico_lrx

私はこれをrakeタスクで試しました、コンソールは返します:rakeは中止されました!NoMethodError:未定義のメソッドはnilのための`近い」:NilClass stackoverflow.com/questions/42515043/...
マルコスR.ゲバラ

1
@TassはCSV処理をチャンク化し、速度を改善し、メモリを節約することは、新しい宝石を追加する正当な理由かもしれません;)
Tilo

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

より良い方法は、rakeタスクに含めることです。/ lib / tasks /内にimport.rakeファイルを作成し、このコードをそのファイルに配置します。

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リンクにあります。

行を1つずつ保存することは効率的ではありません。ループでデータベース呼び出しが発生するため、特にデータの巨大な部分を挿入する必要がある場合は、それを回避する方がよいからです。

バッチ挿入を使用することをお勧めします(大幅に高速化します)。

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(2010年8月11日に最初にリリースされた)を使用することができます。この場合、データを配列に入れrowsて呼び出すだけです。Model.import rows

詳細はgem docsを参照してください


-2

CSV :: Tableを使用してを使用することをお勧めしますString.encode(universal_newline: true)。CRLFとCRをLFに変換する


1
あなたの提案する解決策は何ですか?
Tass

-3

SmartCSVを使用する場合

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

これは、行"\t"が改行で区切られた各行のタブ区切りデータを表します"\n"

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.