Ruby on rails RubyonRails-从CSV文件导入数据

Ruby on rails RubyonRails-从CSV文件导入数据,ruby-on-rails,csv,import,Ruby On Rails,Csv,Import,我想将数据从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

我想将数据从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

你能给我一些代码来告诉我最好的方法吗,谢谢。

这会有帮助。它也有代码示例:

或者对于执行相同操作的rake任务:


yfeldblum答案的更简单版本,更简单,也适用于大文件:

require 'csv'    

CSV.foreach(filename, headers: true) do |row|
  Moulding.create!(row.to_hash)
end
无需使用_interference _access或符号化_键,也无需先在文件中读取字符串

它不会一次将整个文件保存在内存中,而是逐行读取并每行创建一个模板。

您可以尝试:


如果这是你想要的,你也可以考虑从表中去掉自动增量主键,并将主键设置为<代码>名称< /代码>。或者,如果有一些属性组合构成主键,则将其用作选择器。无需索引,只需加快速度。

智能csvgem是专门为这个用例创建的:从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\u size
一次读取N个csv行,然后在内部循环中使用Resque生成将创建新记录的作业,而不是立即创建它们-这样您可以将生成条目的负载分散到多个工作人员

另见:

如果要使用SmartCSV

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

这表示每行
“\t”
中以制表符分隔的数据,行之间用新行分隔
“\n”

最好使用CSV::Table和
String.encode(universal\u newline:true)
。在将CRLF和CR转换为LF时,最好将与数据库相关的进程封装在
事务
块中。代码片段清理是将一组语言植入语言模型的完整过程

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
...
使用此gem:


只需确保标题与表的列名匹配,更好的方法是将其包含在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\u model\u import[file.csv,Name\u of the\u model]

我知道这是个老问题,但它仍然在谷歌的前10个链接中

逐个保存行不是很有效,因为这会导致循环中出现数据库调用,您最好避免这种情况,尤其是在需要插入大量数据时

使用批插入更好(而且更快)

INSERT INTO `mouldings` (suppliers_code, name, cost)
VALUES
    ('s1', 'supplier1', 1.111), 
    ('s2', 'supplier2', '2.222')
您可以手动构建这样的查询,而不是执行
Model.connection.execute(原始SQL字符串)
(不推荐) 或者使用gem
activerecord导入
(它于2010年8月11日首次发布)在这种情况下,只需将数据放入数组
,然后调用
Model.import行


以下模块可以在任何模型上扩展,它将根据CSV中定义的列标题导入数据

注意:

  • 这是一个很好的内部工具,对于客户使用,我建议添加保护和消毒
  • CSV中的列名必须与DB模式完全相同,否则将无法工作
  • 它可以通过使用表名来获取头文件而不是在文件中定义头文件来进一步改进
在您的
models/concerns
文件夹中创建一个名为
“csv\u importer.rb”
的文件

module CsvImporter
  extend ActiveSupport::Concern  
  require 'csv'
  
  def convert_csv_to_book_attributes(csv_path)
    csv_rows = CSV.open(csv_path).each.to_a.compact
    columns = csv_rows[0].map(&:strip).map(&:to_sym)
    csv_rows.shift
    
    return columns, csv_rows
  end
  
  def import_by_csv(csv_path)
    columns, attributes_array = convert_csv_to_book_attributes(csv_path)
    
    message = ""
    begin
      self.import columns, attributes_array, validate: false
      message = "Import Successful."
    rescue => e
      message = e.message
    end
    
    return message
  end
end
extend CsvImporter
添加到您希望将此功能扩展到的任何型号

在控制器中,可以执行如下操作来利用此功能:

def import_file
   model_name = params[:table_name].singularize.camelize.constantize
   csv = params[:file].path
   @message = model_name.import_by_csv(csv)
end

你可以把它放在Rake任务中,或者控制器动作中,或者任何你喜欢的地方……它工作得非常好。然而,我有一个初学者级别的问题——当我试图浏览Ruby和Rails API文档中描述的方法时,我没有找到它们(我查看了Ruby和Rails官方网站,API文档)。例如,我找不到返回CSV.parse()的对象,我找不到to_hash()和带有\u interference\u access()方法的对象。。。也许我找错了地方,或者错过了一些关于如何遍历Ruby&Rails API文档的基本原则。任何人都可以分享如何阅读Ruby API文档的最佳实践吗?@daveatflow:是的,请参阅下面我的答案,它一次读取文件中的一行。@lokeshjain2008,它指的是OP的模型。这种方法效率低下!在巨大的CSV文件上,ram的使用量猛增。下面的一个更好。这更适合管理大文件大小,对吗?它一次读一行吗?@西蒙:的确。它不会一次将整个文件保存在内存中,而是逐行读取并每行创建一个模板。我有这个错误,你知道原因吗?:ActiveModel::UnknownAttributeError:unknown属性“siren;名称;地址;补体地址;科普维尔;支付;区域部门;激进分子;日期;NBU工资;笔名;prenom;文明的;adr_邮件;诽谤行为;分类;电话Transaction@AlphaNico用你的问题提出一个问题。该错误与此无关,您的模型对象似乎不同步。在这种情况下,您如何为此编写测试用例?您建议的解决方案是什么?由于包含了CSV类,我觉得最好使用它,而不是添加或安装额外的gem。当然,您没有建议将新的gem添加到应用程序中。添加一系列单独的gem非常容易,每个gem都有特定的用途,而且在您知道您的应用程序有过多的依赖性之前。(我发现自己有意识地避免添加任何宝石。在我的店里,我们需要向我们的队友证明添加宝石的合理性。)@Tass添加一系列单独的方法也很容易,每个方法都有特定的用途
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
INSERT INTO `mouldings` (suppliers_code, name, cost)
VALUES
    ('s1', 'supplier1', 1.111), 
    ('s2', 'supplier2', '2.222')
module CsvImporter
  extend ActiveSupport::Concern  
  require 'csv'
  
  def convert_csv_to_book_attributes(csv_path)
    csv_rows = CSV.open(csv_path).each.to_a.compact
    columns = csv_rows[0].map(&:strip).map(&:to_sym)
    csv_rows.shift
    
    return columns, csv_rows
  end
  
  def import_by_csv(csv_path)
    columns, attributes_array = convert_csv_to_book_attributes(csv_path)
    
    message = ""
    begin
      self.import columns, attributes_array, validate: false
      message = "Import Successful."
    rescue => e
      message = e.message
    end
    
    return message
  end
end
def import_file
   model_name = params[:table_name].singularize.camelize.constantize
   csv = params[:file].path
   @message = model_name.import_by_csv(csv)
end