public
Description: Import CSV files when you don't know exactly what the headers will be called.
Homepage:
Clone URL: git://github.com/chrisjpowers/flexible_csv.git
Chris Powers (author)
Thu Feb 19 05:32:16 -0800 2009
commit  7c9bcb1f3a1c95bfabb3f35cb6c93c197e3dce82
tree    86422436fea10b5e2a507b461d68c9b1e774a151
parent  2f354677b1023cea9a2ce54402e9be06c88bf285
name age message
file CHANGELOG.rdoc Loading commit data...
file LICENSE
file README.rdoc
file flexible_csv.gemspec
directory lib/
directory test/
README.rdoc

FlexibleCsv

Chris Powers, The Killswitch Collective (killswitchcollective.com/blog/authors/chris_powers)

The FlexibleCsv gem uses the FasterCSV gem to parse user created CSV files that may not have standard headers. For example, you know there’s an email address column somewhere in there, but it may be called "Email" or "Email Address" or "email-address", and it might be in the first column, but it might be in the third.

Examples

These CSV data snippets have the same data but are formatted much differently. Using FlexibleCsv to map possible column names to data nodes, these differences become negligible.

  require 'flexible_csv'

  # Arbitrary CSV data
  csv_data1 = %Q{Full Name, Email Address\nJohn Doe, john@doe.com}
  csv_data2 = %Q{Email, Name\njohn@doe.com, John Doe}

  parser = FlexibleCsv.new do |csv|
    csv.column :full_name, "Name", "Full Name", "Client Name"
    csv.column :email, "Email", "Email Address"
  end

  parser.parse(csv_data1).each do |row|
    puts row.full_name #=> 'John Doe'
    puts row.email     #=> 'john@doe.com'
  end

  parser.parse(csv_data2).each do |row|
    puts row.full_name #=> 'John Doe'
    puts row.email     #=> 'john@doe.com'
  end

Using Adapters

What if simply mapping header names to values is not enough? What if you need more translation logic? Instead of baking in complex rule functionality into FlexibleCsv, I am strongly suggesting users use their own adapter classes to wrap up this logic.

For example, let’s say that some of your CSV files have a column for each contact’s full name, but other have a separate column for first and last name:

  require 'flexible_csv'

  # Arbitrary CSV data
  csv_data1 = %Q{Full Name\nJohn Doe}
  csv_data2 = %Q{First Name, Last Name\nJohn,Doe}

  parser = FlexibleCsv.new do |csv|
    csv.column :full_name, "Name", "Full Name", "Client Name"
    csv.column :first_name, "First Name", "First"
    csv.column :last_name, "Last Name", "Last", "Surname"
  end

  class CsvAdapter
    def initialize(row)
      @row = row
    end

    def full_name
      row.full_name || "#{row.first_name} #{row.last_name}"
    end

    def last_name
      row.last_name || row.full_name.split(' ').last
    end

    def first_name
      row.first_name || row.full_name.split(' ').first
    end

    def method_missing(method_name, *args)
      row.send(method_name, *args)
    end
  end

  parser.parse(csv_data1).each do |row|
    ad_row = CsvAdapter.new(row)
    puts ad_row.full_name  #=> 'John Doe'
    puts ad_row.first_name #=> 'John'
    puts ad_row.last_name  #=> 'Doe'
  end

  parser.parse(csv_data2).each do |row|
    ad_row = CsvAdapter.new(row)
    puts ad_row.full_name  #=> 'John Doe'
    puts ad_row.first_name #=> 'John'
    puts ad_row.last_name  #=> 'Doe'
  end