A Ruby on Rails Engine plugin that makes it easy to import CSV files, complete with error reporting and customization.
Ruby
Pull request Compare This branch is 7 commits ahead, 18 commits behind jarrett:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.
app/helpers
lib
tasks
MIT-LICENSE
README.rdoc
Rakefile
init.rb
install.rb
uninstall.rb

README.rdoc

CsvImport

This is a Rails plugin.

It provides a controller method called each_csv_row and an accompanying view helper called csv_fields.

You can set the name of the file field param by passing it as the first argument to the csv_fields helper (it is 'csv_file' by default):

<%= csv_fields :csv_file_upload %>

You may also specify the file field param as the first argument to the each_csv_row method in the controller (it too is 'csv_file' by default):

each_csv_row(:csv_file_upload) do |row_hash|
  # …
end

By default, when each_csv_row is called, any exceptions raised in the block will be rescued. The rows that triggered the exceptions will then be displayed in a table as part of the output from csv_fields.

You can turn off rescuing by passing false as the second argument to each_csv_row:

each_csv_row(:csv_file_upload, false) do |row_hash|
  # …
end

each_csv_row extracts the names of the fields from the CSV and applies them to each row in the CSV creating a hash of key / value pairs. The resulting hash for a row from the example below would look something like this:

{:first_name => 'Bob', :last_name => 'Smith'}

If the first row does not contain the field names, each_csv_row names them from 0 to the length (- 1) of the first row:

{0 => 'Bob', 1 => 'Smith'}

Example

In your controller:

class EmployeesController < ActionController::Base
  include CsvImport

  def import_employee_records
    each_csv_row do |row_hash|
      Employee.destroy_all if params[:destroy_all_first]
      Employee.create!(row_hash)
    end
  end
end

In your view (don't forget to set :multipart => true):

<h2>Import Employee Records</h2>

<%- form_tag import_employees_path, {:multipart => true} do -%>

  <%= csv_fields %>

  <p>
    <%= check_box_tag :destroy_all_first %>
    <%= label :destroy_all_first, 'Destroy all existing employee records first.' %>
  </p>
<%- end -%>

Other View Helpers Included

All of these are called in csv_fields. These exist for flexibility's sake. Feel free to mix and match or even override csv_fields in your own helper.

csv_file_label(name = 'csv_file', text = 'CSV file to use', options = {})

csv_file_tag(name = 'csv_file', options = {})

csv_field_names_label(name = 'csv_field_names_in_first_row', text = 'Field name labels are in the first row.', options = {})

csv_field_names_check_box_tag(name = 'csv_field_names_in_first_row', value = '1', checked = true, options = {})

csv_rows_imported_content(wrapping_tag = :div, html_options = {})

csv_bad_rows_table(header = 'Bad CSV rows', html_options = {})

To-do

Allow for mapping of incoming field name labels to expected or preferred keys:

each_csv_row(:non_useful_key_name => :key_expected, :other_key => :better_key) do |row_hash|
  # …
end

Or even…

each_csv_row(0 => :first_name, 1 => :last_name, 2 => :email) do |row_hash|
  # …
end

Move the view helper methods to FormHelper, FormHelper::InstanceTag, and FormBuilder

Specifically FormBuilder so that things like this are made possible:

<%- form_for @company do |f| -%>
  <p>
    <%= f.label :name, 'Company Name' %><br />
    <%= f.text_field :name %>
  </p>

  # yadda, yadda

  <p>
    <%= f.csv_file_label :employees_csv_file, 'Import Employees from a CSV' %><br />
    <%= f.csv_file :employees_csv_file %>
  </p>

  <p><%= f.submit 'Save' %></p>
<%- end -%>