Rails:上传无头文件的CSV文件

问题描述:

我跟随了railscasts#396导入CSV并在我的rails项目中实现了CSV上传。Rails:上传无头文件的CSV文件

这是我的看法文件:

<%= form_tag import_customers_path, multipart: true do %> 
    <%= file_field_tag :file %> 
    <%= submit_tag "Import" %> 
<% end %> 

这是我的控制器操作:

def import 
    current_user.customers.import(params[:file]) 
    redirect_to customers_path, notice: "Users imported." 
end 

这些都是我的模型方法:

def self.to_csv(options = {}) 
    CSV.generate(options) do |csv| 
     csv << column_names 
     all.each do |customer| 
      csv << customer.attributes.values_at(*column_names) 
     end 
    end 
end 

def self.import(file) 
    CSV.foreach(file.path, headers: true) do |row| 
    Customer.create! row.to_hash 
    end 
end 

在这里我不想用户在CSV中包含标题。当我与headers: false取代headers: true,我得到错误:

NoMethodError in CustomersController#import

undefined method `to_hash' for ["[email protected]"]:Array

能有人告诉如何上传CSV文件,无需标题行的?

您可以在CSV文件只加载到一个数组的数组,并删除第一行:

data = CSV.read("path/to/file.csv") 
data = data[1..-1] 

然而,这将在数据存储为唯一值的数组。

当您使用headers: true时,它使用一个散列,其中的键是列标题名称。

只要上传和处理CSV文件,你就非常非常接近。您只是在阅读数据行以填充数据库时遇到问题,通过Customer.create!致电

看起来您已经使用仅包含一行数据的CSV文件进行测试。通过headers: true,该单行被转换为头文件,随后在CSV.foreach迭代器中被忽略。所以,实际上,文件中没有数据,并且没有发生迭代。如果输入文件中有两行数据,则无论如何,您都会遇到错误。

现在,当您使用headers: false时,该行数据被视为数据。这就是问题出在哪里:处理数据没有正确完成。

由于在你的问题中没有模式,所以我会假设在田间有一点余地;你应该能够很容易地推断,以使其适用于你的情况。这段代码显示它是如何工作:

CSV.parse(csv_data, headers: false) do |row| 
    hash = { 
    first_name: row[0], 
    last_name: row[1], 
    age: row[2], 
    phone: row[3], 
    address: row[4] 
    } 
    Customer.create!(hash) 
end 

如果你想要一个CSV版本的标题,这将在这种情况下工作得很好,并没有允许不应从分配栏任意访问的好处外部来源:

CSV.parse(csv_data, headers: true, header_converters: :symbol) do |row| 
    hash = { 
    first_name: row[:first_name], 
    surname: row[:last_name], 
    birth_year: Date.today - row[:age], 
    phone: row[:phone], 
    street_address: row[:address] 
    } 
    Customer.create!(hash) 
end 

请注意,模型中的Customer#to_csv也不完全正确。首先,它会创建带有标题的CSV文件,因此您将无法导出,然后再使用此实现导入。接下来,头字段变量column_names实际上并未在此代码中定义。最后,代码不控制写入CSV的列的顺序,这意味着标头和值可能不同步。这方面的一个正确的(非首)版本是非常简单的:

csv_data = CSV.generate do |csv| 
    csv.each do |customer| 
    csv << [customer.first_name, customer.last_name, customer.age, customer.phone, customer.address] 
    end 
end 

的基于头的版本是这样的:

csv_data = CSV.generate do |csv| 
    csv << ["First Name","Last Name","Age","Phone","Address"] 
    csv.each do |customer| 
    csv << [customer.first_name, customer.last_name, customer.age, customer.phone, customer.address] 
    end 
end 

就个人而言,我会使用的基于头的版本,因为它是更强大,并且很容易理解哪些列是哪些列。如果你曾经收到一个无头文件的CSV文件,并且必须弄清楚如何在没有任何键的情况下理解它,你就会知道为什么头文件很重要。