Rails:上传没有标题的CSV文件

时间:2013-07-27 19:48:16

标签: ruby-on-rails ruby csv file-upload ruby-on-rails-3.2

我跟踪了railscast#396导入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: true替换headers: false时,我收到错误:

  

CustomersController#import

中的NoMethodError      

[“abc@wer.com”]的未定义方法`to_hash':数组

有人可以告诉你如何上传CSV文件而不需要标题行吗?

2 个答案:

答案 0 :(得分:1)

就上传和处理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文件并且必须弄清楚如何在没有任何键的情况下理解它,你就会知道为什么标题很重要。

答案 1 :(得分:-1)

您可以将CSV文件加载到数组数组中并删除第一行:

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

但是,这只会将数据存储为值数组。

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